将SQL Provider从SQLOLEDB.1更改为SQLNCLI.1会导致app在通过存储过程访问数据时失败

时间:2009-05-13 15:24:14

标签: c++ sql-server sqlncli

我支持用MFC / C ++编写的遗留应用程序。该应用程序的数据库位于SQL Server 2000中。我们最近使用了一些新功能,发现当我们将SQL Provider从SQLOLEDB.1更改为SQLNCLI.1时,一些代码试图通过存储过程从表中检索数据失败。

有问题的表非常简单,是通过以下脚本创建的:

SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE TABLE [dbo].[UAllergenText](
    [TableKey] [int] IDENTITY(1,1) NOT NULL,
    [GroupKey] [int] NOT NULL,
    [Description] [nvarchar](150) NOT NULL,
    [LanguageEnum] [int] NOT NULL,
CONSTRAINT [PK_UAllergenText] PRIMARY KEY CLUSTERED
(
    [TableKey] ASC) WITH (PAD_INDEX  = OFF, STATISTICS_NORECOMPUTE  = OFF,
    IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS  = ON, ALLOW_PAGE_LOCKS  = ON) ON [PRIMARY]
) ON [PRIMARY]

GO
ALTER TABLE [dbo].[UAllergenText]  WITH CHECK ADD  CONSTRAINT 
FK_UAllergenText_UBaseFoodGroupInfo] FOREIGN KEY([GroupKey])
REFERENCES [dbo].[UBaseFoodGroupInfo] ([GroupKey])
GO
ALTER TABLE [dbo].[UAllergenText] CHECK CONSTRAINT 
FK_UAllergenText_UBaseFoodGroupInfo]

基本上是四列,其中TableKey是一个标识列,其他所有内容都通过以下脚本填充:

INSERT INTO UAllergenText (GroupKey, Description, LanguageEnum)
VALUES (401, 'Egg', 1)

有一长串其他INSERT INTO,跟随上面的那个。插入的某些行在其描述中具有特殊字符(如字母上方的重​​音符号)。我原本以为包含特殊字符是问题的一部分,但如果我完全清除表格然后只用上面没有特殊字符的单个INSERT INTO重新填充它,它仍然会失败。

所以我继续......

然后通过以下代码访问此表中的数据:

std::wstring wSPName = SP_GET_ALLERGEN_DESC;
_variant_t  vtEmpty1 (DISP_E_PARAMNOTFOUND, VT_ERROR);
_variant_t  vtEmpty2(DISP_E_PARAMNOTFOUND, VT_ERROR);

_CommandPtr pCmd = daxLayer::CDataAccess::GetSPCommand(pConn, wSPName); 
pCmd->Parameters->Append(pCmd->CreateParameter("@intGroupKey", adInteger, adParamInput, 0, _variant_t((long)nGroupKey)));
pCmd->Parameters->Append(pCmd->CreateParameter("@intLangaugeEnum", adInteger, adParamInput, 0, _variant_t((int)language)));

_RecordsetPtr pRS = pCmd->Execute(&vtEmpty1, &vtEmpty2, adCmdStoredProc);            

//std::wstring wSQL = L"select Description from UAllergenText WHERE GroupKey = 401 AND LanguageEnum = 1";
//_RecordsetPtr pRS = daxLayer::CRecordsetAccess::GetRecordsetPtr(pConn,wSQL);

if (pRS->GetRecordCount() > 0)
{
    std::wstring wDescField = L"Description";
    daxLayer::CRecordsetAccess::GetField(pRS, wDescField, nameString);
}   
else
{
    nameString = "";
}

daxLayer是应用程序正在使用的第三方数据访问库,虽然我们有它的源代码(其中一些将在下面看到。)SP__GET_ALLERGEN_DESC是用于从表中获取数据的存储过程,它是通过这个脚本创建的:

SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO

CREATE PROCEDURE [dbo].[spRET_AllergenDescription] 
-- Add the parameters for the stored procedure here
    @intGroupKey int, 
    @intLanguageEnum int
AS
BEGIN
    -- SET NOCOUNT ON added to prevent extra result sets from
    -- interfering with SELECT statements.
    SET NOCOUNT ON;

    -- Insert statements for procedure here
    SELECT Description FROM UAllergenText WHERE GroupKey = @intGroupKey AND LanguageEnum = @intLanguageEnum
END

当SQL Provider设置为SQLNCLI.1时,应用程序会爆炸:

daxLayer::CRecordsetAccess::GetField(pRS, wDescField, nameString);

来自上面的代码段。所以我进入GetField,看起来如下:

void daxLayer::CRecordsetAccess::GetField(_RecordsetPtr pRS,
const std::wstring wstrFieldName, std::string& sValue, std::string  sNullValue)
{
    if (pRS == NULL)
    {
        assert(false);
        THROW_API_EXCEPTION(GetExceptionMessageFieldAccess(L"GetField", 
        wstrFieldName, L"std::string", L"Missing recordset pointer."))
    }
    else
    {
        try
        {
            tagVARIANT tv = pRS->Fields->GetItem(_variant_t(wstrFieldName.c_str()))->Value;

            if ((tv.vt == VT_EMPTY) || (tv.vt == VT_NULL))
            {
                sValue = sNullValue;
            }
            else if (tv.vt != VT_BSTR)
            {
                // The type in the database is wrong.
                assert(false);
                THROW_API_EXCEPTION(GetExceptionMessageFieldAccess(L"GetField", 
                wstrFieldName, L"std::string", L"Field type is not string"))
            }
            else
            {
                 _bstr_t bStr = tv ;//static_cast<_bstr_t>(pRS->Fields->GetItem(_variant_t(wstrFieldName.c_str()))->Value);                     
                 sValue = bStr;
            }
        }
        catch( _com_error &e )
        {
            RETHROW_API_EXCEPTION(GetExceptionMessageFieldAccess(L"GetField", 
            wstrFieldName, L"std::string"), e.Description())
        }
        catch(...)
        {        
            THROW_API_EXCEPTION(GetExceptionMessageFieldAccess(L"GetField",
            wstrFieldName, L"std::string", L"Unknown error"))
        }
    }
}

罪魁祸首是:

tagVARIANT tv = pRS->Fields->GetItem(_variant_t(wstrFieldName.c_str()))->Value;

踏入Fields-&gt; GetItem将我们带到:

的GetItem

inline FieldPtr Fields15::GetItem ( const _variant_t & Index ) {
    struct Field * _result = 0;
    HRESULT _hr = get_Item(Index, &_result);
    if (FAILED(_hr)) _com_issue_errorex(_hr, this, __uuidof(this));
    return FieldPtr(_result, false);
}

然后将我们带到:

的GetValue

inline _variant_t Field20::GetValue ( ) {
    VARIANT _result;
    VariantInit(&_result);
    HRESULT _hr = get_Value(&_result);
    if (FAILED(_hr)) _com_issue_errorex(_hr, this, __uuidof(this));
    return _variant_t(_result, false);
}

如果你在运行时单步查看_result,_result的BSTR值是正确的,它的值是表格“Description”字段中的“Egg”。继续逐步浏览所有COM释放调用等的痕迹。当我最终回到:

tagVARIANT tv = pRS->Fields->GetItem(_variant_t(wstrFieldName.c_str()))->Value;

然后走到下一行,tv的内容,应该是BSTR =“Egg”现在:

tv BSTR = 0x077b0e1c "ᎀݸﻮﻮﻮﻮﻮﻮﻮﻮﻮﻮﻮﻮ㨼㺛帛᠄"

当GetField函数尝试将其返回值设置为tv.BSTR

中的值时
_bstr_t bStr = tv;
sValue = bStr;
毫不奇怪,窒息而死。

那么BSTR的值是怎么回事?为什么只有当提供者设置为SQLNCLI.1时才会发生?

对于它,我注释掉了使用最顶层代码中的存储过程,只是对存储过程使用的相同SQL SELECT语句进行了硬编码,发现它工作得很好并且返回的值是正确的。

此外,用户可以通过应用程序向表中添加行。如果应用程序在该表中创建一个新行并通过存储过程检索该行,它也可以正常工作,除非您在描述中包含一个特殊字符,在这种情况下它正确地保存行但是以与上面完全相同的方式再次爆炸在检索那一行时。

总而言之,如果可以的话,通过INSERT脚本放入表中的行总是在存储过程访问应用程序时炸毁应用程序(无论它们是否包含任何特殊字符)。用户在运行时从应用程序内放入表中的行将通过存储过程正确检索,除非它们在描述中包含特殊字符,此时它们会炸毁应用程序。如果您在运行时使用SQL而不是存储过程访问表中的任何行,则无论描述中是否有特殊字符,它都可以工作。

任何可以解决此问题的灯光都将非常感谢,我提前感谢你。

1 个答案:

答案 0 :(得分:1)

这一行可能有问题:

tagVARIANT tv = pRS->Fields->GetItem(_variant_t(wstrFieldName.c_str()))->Value;

如果我读得正确,那么&gt; Value会返回_variant_t,这是一个智能指针。智能指针将在超出范围时释放其变体,就在此行之后。但是,tagVARIANT不是智能指针,因此在分配时不会增加引用计数。所以在这一行之后,tv可能会指向一个已经有效释放的变体。

如果您编写这样的代码会怎样?

_variant_t tv = pRS->Fields->GetItem(_variant_t(wstrFieldName.c_str()))->Value;

或者,告诉智能指针不要释放其有效负载:

_tagVARIANT tv = pRS->Fields->GetItem(
    _variant_t(wstrFieldName.c_str()))->Value.Detach();

自从我用C ++编写代码已经很长时间了,阅读这篇文章,我不后悔搬走了!