gpt4 book ai didi

c++ - 将 SQL Provider 从 SQLOLEDB.1 更改为 SQLNCLI.1 会导致应用程序在通过存储过程访问数据时失败

转载 作者:行者123 更新时间:2023-11-28 08:33:15 27 4
gpt4 key购买 nike

我支持用 MFC/C++ 编写的旧版应用程序。该应用程序的数据库位于 SQL Server 2000 中。我们最近加入了一些新功能,发现当我们将 SQL 提供程序从 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->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);
}

然后将我们带到:

获取值(value)

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 值是正确的,其值为表“描述”字段中的“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 而不是存储过程来访问表中的任何行,那么无论描述中是否有特殊字符,它都可以正常工作。

如果能对此有所启发,我们将不胜感激,在此先感谢您。

最佳答案

这一行可能有问题:

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

如果我没看错,->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++ 编写代码了,读了这篇文章,我不后悔搬走!

关于c++ - 将 SQL Provider 从 SQLOLEDB.1 更改为 SQLNCLI.1 会导致应用程序在通过存储过程访问数据时失败,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/858624/

27 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com