2009-05-13 6 views
0

MFC/C++로 작성된 레거시 응용 프로그램을 지원하고 있습니다. 이 응용 프로그램의 데이터베이스는 SQL Server 2000에 있습니다. 우리는 최근 몇 가지 새로운 기능을 사용하여 SQL Provider를 SQLOLEDB.1에서 SQLNCLI.1로 변경하면 저장 프로 시저를 통해 테이블에서 데이터를 검색하려고하는 일부 코드를 발견했습니다 실패합니다.SQL 공급자를 SQLOLEDB.1에서 SQLNCLI.1로 변경하면 저장 프로 시저를 통해 데이터에 액세스 할 때 응용 프로그램이 실패합니다.

는 문제의 테이블은 매우 간단 다음과 같은 스크립트를 통해 생성 :

Bascially
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은 ID 열이있는 다른 모든 것들 다음 스크립트를 통해 채워집니다와 :

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 
위의 코드에서

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

다음 SQL 공급자가 SQLNCLI.1로 설정하면 46,510,403,210는 응용 프로그램에서 불면.

를 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); 
} 
>를 GetItem 것은 우리에게 가져다 필드 -에 스테핑

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

:

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")) 
     } 
    } 
} 

범인은 여기에 있습니다 : 그래서 다음과 같이 보이는, GetField에 들어갔다

다음은 다음과 같습니다.

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 = "달걀"해야 다음 줄, 텔레비젼의 내용에 과거 단계 지금 :

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

때 GetField 기능 반환 값을 tv.BSTR의 값으로 설정하려고 시도합니다.

_bstr_t bStr = tv; 
sValue = bStr; 

놀랍지 않게 질식하고 사망합니다.

그래서 BSTR의 가치는 어떻게되었으며 공급자가 SQLNCLI.1로 설정된 경우에만 발생합니까?

나는 그것을 최상위 코드의 저장 프로 시저를 사용하여 주석 처리하고 저장 프로 시저가 사용하는 동일한 SQL SELECT 문을 하드 코딩하여 반환하고 반환 된 값이 정확하다는 것을 발견했습니다.

또한 사용자가 응용 프로그램을 통해 테이블에 행을 추가 할 수도 있습니다. 응용 프로그램에서 해당 테이블에 새 행을 만들고 저장 프로 시저를 통해 해당 행을 검색하는 경우 설명에 특수 문자를 포함 시키면 해당 행이 올바르게 저장되지만 위와 완전히 똑같은 방식으로 다시 불면 올바르게 작동합니다 그 행을 검색하면

요약하면 INSERT 스크립트를 통해 표에 삽입 된 행은 저장 프로 시저 (특수 문자가 포함되어 있는지 여부에 관계없이)에 액세스 할 때 항상 앱을 폭파시킵니다. 런타임에 사용자가 응용 프로그램 내에서 테이블에 넣은 행은 설명에 특수 문자가 포함되어 있지 않으면 저장 프로 시저를 통해 올바르게 검색됩니다. 설명에 특수 문자가 포함되어 있으면 앱이 폭발합니다. 저장 프로 시저 대신 런타임시 코드에서 SQL을 사용하여 테이블의 행에 액세스하면 설명에 특수 문자가 있는지 여부에 관계없이 작동합니다.

여기에 표시 될 수있는 모든 표시등은 크게 감사하겠습니다. 미리 감사드립니다.

답변

1

이 줄은 문제가 될 수 있습니다

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

내가 바로 읽는다면 -> 값이 스마트 포인터 인 _variant_t을 반환합니다. 스마트 포인터는이 라인 바로 뒤의 범위를 벗어나면 변형을 해제합니다. 그러나 tagVARIANT는 스마트 포인터가 아니므로 할당되면 참조 횟수가 증가하지 않습니다. 그래서이 줄 뒤에 TV는 효과적으로 배포 된 변형을 가리킬 수 있습니다.

이와 같은 코드를 작성하면 어떻게됩니까? 나는 C++ 코딩하고,이 게시물 읽기, 내가 멀리 이동 후회하지 않기 때문에 그것은 오랜 시간이 지났

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

:

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

또는 대안은, 페이로드를 해제하지 스마트 포인터를 말해 !

+0

우수! 두 가지 해결책 모두 문제를 해결하고 해결합니다. 매우 고맙습니다. 나는 지혜 끝에 있었고, 아직도 이것을 배우고있는 한계가있는 C++ 코더 였기 때문에 나는 완전히 당혹 스러웠다. SQL Provider가이 점을 밝혀낸 이유는 아직도 혼란 스럽습니다. SQL Provider와 관계없이 참조 횟수 문제가 여전히 문제라고 생각합니다. 다시 한번 감사드립니다. – Scott

+0

변형을 기본으로하는 BSTR은 다른 사람이 참조하는 한 유효합니다. 아마도 새로운 공급자는 BSTR에 대한 자체 참조를 해제하는 것에 대해 더 공격적 일 수 있습니다. 어쨌든 내가 도울 수있어서 기뻐, 나는 네 위치에 한 번 기억하고있다! – Andomar