0

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]

基本的に 4 つの列で、TableKey は ID 列であり、それ以外はすべて次のスクリプトによって設定されます。

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

上記の INSERT INTO に続く他の 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 プロバイダーが 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 にステップインすると、次のようになります。

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 値は正しく、その値はテーブルの「説明」フィールドの「卵」です。すべての COM リリース コールなどのトレースをステップ実行し続けます。最終的に戻ると、次のようになります。

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

それを過ぎて次の行に進むと、BSTR="Egg" であるはずの tv の内容が次のようになります。

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

GetField 関数がその戻り値を tv.BSTR の値に設定しようとしたとき

_bstr_t bStr = tv;
sValue = bStr;

当然のことながら、窒息して死にます。

では、BSTR の値はどうなったのでしょうか? また、プロバイダが SQLNCLI.1 に設定されている場合にのみ、BSTR の値が発生するのはなぜでしょうか?

なんと、最上位のコードでストアド プロシージャを使用してコメント アウトし、ストアド プロシージャが使用する同じ SQL SELECT ステートメントをハード コーディングしたところ、問題なく動作し、返される値が正しいことがわかりました。

また、ユーザーがアプリケーションを介してテーブルに行を追加することもできます。アプリケーションがそのテーブルに新しい行を作成し、ストアド プロシージャを介してその行を取得する場合、説明に特殊文字を含めない限り正しく機能します。その場合、行は正しく保存されますが、上記とまったく同じ方法で再び爆発します。その行の取得時。

要約すると、可能であれば、INSERT スクリプトを介してテーブルに挿入された行は、ストアド プロシージャによってアクセスされると、常にアプリを爆破します (特殊文字が含まれているかどうかに関係なく)。実行時にユーザーがアプリケーション内からテーブルに挿入した行は、説明に特殊文字が含まれていない限り、ストアド プロシージャを介して正しく取得されます。ストアド プロシージャではなく実行時にコードから SQL を使用してテーブル内の行にアクセスすると、説明に特殊文字があるかどうかに関係なく機能します。

これに光を当てることができれば大歓迎です。事前に感謝します。

4

1 に答える 1

1

この行は問題がある可能性があります。

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 ++でコーディングしてから久しぶりですが、この投稿を読んで、後悔していません。

于 2009-05-13T16:29:25.507 に答える