블로그 이미지
fiadot_old

칼퇴근을 위한 게임 서버 개발 방법론에 대한 심도있는 고찰 및 성찰을 위한 블로그!

Rss feed Tistory
Technical Article 2007. 7. 24. 10:41

VS2005에서 Wizard를 통해 OLEDB Consumer생성시 Stored Procedure Accessor bind 실패 원인 분석

VS2005에서 Wizard를 통해 OLEDB Consumer생성시 Stored Procedure Accessor bind 실패 원인 분석

Visual Studio 6에서는 Insert - new ATL Object - Data Access - Consumer를 통해
Stored Procedure(이하SP)에 대한 Accessor를 자동으로 생성할수 있다.
이를 통해 다음과 같은 코드를 얻게된다.


// dboSBuddyList1.H : Declaration of the CdboSBuddyList1 class

#ifndef __DBOSBUDDYLIST1_H_
#define __DBOSBUDDYLIST1_H_

class CdboSBuddyList1Accessor
{
public:
 LONG m_RETURNVALUE;
 TCHAR m_userid[13];
 BYTE m_usertype;
 TCHAR m_coluserid[13];
 BYTE m_colusertype;
 TCHAR m_colnick[13];
 BYTE m_collev;
 TCHAR m_colcomment[61];

BEGIN_PARAM_MAP(CdboSBuddyList1Accessor)
 SET_PARAM_TYPE(DBPARAMIO_OUTPUT)
 COLUMN_ENTRY(1, m_RETURNVALUE)
 SET_PARAM_TYPE(DBPARAMIO_INPUT)
 COLUMN_ENTRY(2, m_userid)
 COLUMN_ENTRY(3, m_usertype)
END_PARAM_MAP()

BEGIN_COLUMN_MAP(CdboSBuddyList1Accessor)
 COLUMN_ENTRY(1, m_coluserid)
 COLUMN_ENTRY(2, m_colusertype)
 COLUMN_ENTRY(3, m_colnick)
 COLUMN_ENTRY(4, m_collev)
 COLUMN_ENTRY(5, m_colcomment)
END_COLUMN_MAP()

DEFINE_COMMAND(CdboSBuddyList1Accessor, _T("{ ? = CALL dbo.S_BuddyList;1 (?,?) }"))

 // You may wish to call this function if you are inserting a record and wish to
 // initialize all the fields, if you are not going to explicitly set all of them.
 void ClearRecord()
 {
  memset(this, 0, sizeof(*this));
 }
};

class CdboSBuddyList1 : public CCommand<CAccessor<CdboSBuddyList1Accessor> >
{
public:
 HRESULT Open()
 {
  HRESULT  hr;

  hr = OpenDataSource();
  if (FAILED(hr))
   return hr;

  return OpenRowset();
 }
 HRESULT OpenDataSource()
 {
  HRESULT  hr;
  CDataSource db;
  CDBPropSet dbinit(DBPROPSET_DBINIT);

  dbinit.AddProperty(DBPROP_AUTH_PASSWORD, OLESTR("wing_access"));
  dbinit.AddProperty(DBPROP_AUTH_USERID, OLESTR("xxxxx"));
  dbinit.AddProperty(DBPROP_INIT_CATALOG, OLESTR("xxxxx"));
  dbinit.AddProperty(DBPROP_INIT_DATASOURCE, OLESTR("xx.xx.xx.xx"));
  dbinit.AddProperty(DBPROP_INIT_LCID, (long)1042);
  dbinit.AddProperty(DBPROP_INIT_PROMPT, (short)4);
  hr = db.Open(_T("SQLOLEDB.1"), &dbinit);
  if (FAILED(hr))
   return hr;

  return m_session.Open(db);
 }
 HRESULT OpenRowset()
 {
  return CCommand<CAccessor<CdboSBuddyList1Accessor> >::Open(m_session);
 }
 CSession m_session;
};

#endif // __DBOSBUDDYLIST1_H_


이를 사용 하는 방법은

#include "dboSBuddyList1.H"
#define DB_STRING_FIELD_COPY(dest, src)  _tcsncpy(dest, src, sizeof(dest))


void CDBTT2Dlg::OnButton1()
{
 // TODO: Add your control notification handler code here
 
 CdboSBuddyList1 rs;
 HRESULT hr;

 DB_STRING_FIELD_COPY(rs.m_userid,  _T("lovydayz"));
 rs.m_usertype = 0;

 hr = rs.Open();

 hr = rs.MoveFirst();

 rs.Close();

}



이런식이다. 아이디와 타입을 SP의 인자로 넘기고 레코드셋을 받아오는 SP이다.


VS2005에서는 추가-클래스-ATL OLEDB 소비자 를 통해서 생성하는데
SP에 대해서는  insert, delete, update옵션이 적용되지 않으면 Accessor인지, Table인지
설정하는 옵션이 disable되고 파일을 생성해준다.


class CS_BuddyListAccessor
{
public:

 // 다음 마법사 생성 데이터 멤버에는  열 맵의 해당
 // 필드에 대한 상태 값이 들어 있습니다. 이 값을
 // 사용하여 데이터베이스에서반환하는 NULL 값을
 // 보유하거나 컴파일러에서 오류를 반환할 때
 // 오류 정보를 보유할 수 있습니다. 이러한 필드 사용에
 // 대한 자세한 내용은 Visual C++ 설명서의
 //  "마법사 생성 접근자"에서 "필드 상태 데이터 멤버"를 참조하십시오.
 // 참고: 데이터를 설정/삽입하기 전에 이들 필드를 초기화해야 합니다.

 DBSTATUS m_dwuserid1Status;
 DBSTATUS m_dwusertype1Status;
 DBSTATUS m_dwnickStatus;
 DBSTATUS m_dwlevStatus;
 DBSTATUS m_dwcommentStatus;

 // 다음 마법사 생성 데이터 멤버에는 열 맵의 해당 필드에 대한
 // 길이 값이 들어 있습니다.
 // 참고: 가변 길이 열의 경우 데이터를 설정/삽입하기 전에
 //       이러한 필드를 초기화해야 합니다.

 DBLENGTH m_dwuserid1Length;
 DBLENGTH m_dwusertype1Length;
 DBLENGTH m_dwnickLength;
 DBLENGTH m_dwlevLength;
 DBLENGTH m_dwcommentLength;


 TCHAR m_userid1[13];
 BYTE m_usertype1;
 TCHAR m_nick[13];
 BYTE m_lev;
 TCHAR m_comment[61];

 // 인자들은 리턴되는 레코드셋보다 상단에 있어야 한다!!! - 이근호 070724
 LONG m_RETURN_VALUE;
 TCHAR m_userid[13];
 BYTE m_usertype;


 void GetRowsetProperties(CDBPropSet* pPropSet)
 {
  pPropSet->AddProperty(DBPROP_CANFETCHBACKWARDS, true, DBPROPOPTIONS_OPTIONAL);
  pPropSet->AddProperty(DBPROP_CANSCROLLBACKWARDS, true, DBPROPOPTIONS_OPTIONAL);
  pPropSet->AddProperty(DBPROP_UPDATABILITY, DBPROPVAL_UP_CHANGE | DBPROPVAL_UP_INSERT | DBPROPVAL_UP_DELETE);
 }

 HRESULT OpenDataSource()
 {
  CDataSource _db;
  HRESULT hr;
// #error Security Issue: The connection string may contain a password
// 아래 연결 문자열에 일반 텍스트 암호 및/또는
// 다른 중요한 정보가 포함되어 있을 수 있습니다.
// 보안 관련 문제가 있는지 연결 문자열을 검토한 후에 #error을(를) 제거하십시오.
// 다른 형식으로 암호를 저장하거나 다른 사용자 인증을 사용하십시오.
  hr = _db.OpenFromInitializationString(L"Provider=SQLOLEDB.1;Password=xxxxx;Persist Security Info=True;User ID=xxxxx;Initial Catalog=xxxx;Data Source=xx.xx.xx.xx;Use Procedure for Prepare=1;Auto Translate=True;Packet Size=4096;Workstation ID=LEEGUNHO;Use Encryption for Data=False;Tag with column collation when possible=False");
  if (FAILED(hr))
  {
#ifdef _DEBUG
   AtlTraceErrorRecords(hr);
#endif
   return hr;
  }
  return m_session.Open(_db);
 }

 void CloseDataSource()
 {
  m_session.Close();
 }

 operator const CSession&()
 {
  return m_session;
 }

 CSession m_session;

 // 일부 공급자와 관련된 몇몇 문제점을 해결하기 위해 아래 코드에서는
 // 공급자가 보고하는 것과 다른 순서로 열을 바인딩할 수 있습니다.

 BEGIN_COLUMN_MAP(CS_BuddyListAccessor)
  COLUMN_ENTRY_LENGTH_STATUS(1, m_userid1, m_dwuserid1Length, m_dwuserid1Status)
  COLUMN_ENTRY_LENGTH_STATUS(2, m_usertype1, m_dwusertype1Length, m_dwusertype1Status)
  COLUMN_ENTRY_LENGTH_STATUS(3, m_nick, m_dwnickLength, m_dwnickStatus)
  COLUMN_ENTRY_LENGTH_STATUS(4, m_lev, m_dwlevLength, m_dwlevStatus)
  COLUMN_ENTRY_LENGTH_STATUS(5, m_comment, m_dwcommentLength, m_dwcommentStatus)
 END_COLUMN_MAP()

 BEGIN_PARAM_MAP(CS_BuddyListAccessor)
  SET_PARAM_TYPE(DBPARAMIO_OUTPUT)
  COLUMN_ENTRY(1, m_RETURN_VALUE)
  SET_PARAM_TYPE(DBPARAMIO_INPUT)
  COLUMN_ENTRY(2, m_userid)
//  SET_PARAM_TYPE(DBPARAMIO_INPUT)
  COLUMN_ENTRY(3, m_usertype)
 END_PARAM_MAP()

 DEFINE_COMMAND_EX(CS_BuddyListAccessor, L"{ ? = CALL dbo.S_BuddyList;1 (?,?) }")

};

class CS_BuddyList : public CCommand<CAccessor<CS_BuddyListAccessor> >
{
public:
 HRESULT OpenAll()
 {
  HRESULT hr;
  hr = OpenDataSource();
  if (FAILED(hr))
   return hr;
  __if_exists(GetRowsetProperties)
  {
   CDBPropSet propset(DBPROPSET_ROWSET);
   __if_exists(HasBookmark)
   {
    if( HasBookmark() )
     propset.AddProperty(DBPROP_IRowsetLocate, true);
   }
   GetRowsetProperties(&propset);
   return OpenRowset(&propset);
  }
  __if_not_exists(GetRowsetProperties)
  {
   __if_exists(HasBookmark)
   {
    if( HasBookmark() )
    {
     CDBPropSet propset(DBPROPSET_ROWSET);
     propset.AddProperty(DBPROP_IRowsetLocate, true);
     return OpenRowset(&propset);
    }
   }
  }
  return OpenRowset();
 }

 HRESULT OpenRowset(DBPROPSET *pPropSet = NULL)
 {
  HRESULT hr = Open(m_session, NULL, pPropSet);
#ifdef _DEBUG
  if(FAILED(hr))
   AtlTraceErrorRecords(hr);
#endif
  return hr;
 }

 void CloseAll()
 {
  Close();
  ReleaseCommand();
  CloseDataSource();
 }
};


 

이와 같이 생성된 코드를 테스트 해보면 결과값을 얻어오지 못함을 알수 있다.

Bind부분에서 ATLASSERT(m_pAccessor != NULL) 에서 걸린다.


왜 바인딩을 못하는것인가??

Wizard page를 통해 생성한 코드가 제대로 실행이 안된다는것은 말이 안된다는 가정하에
문제에 접근해 보았다. ATL코드를 죽 훑어보며 DynamicAccssor를 사용하니 문제없이 되었다.
그렇다면 문제는 ATL코드 자체에 있거나 생성한 코드에 있다는 2가지 결론에 도달할수 있었다.

VS6에서 생성한 코드와 비교하며 매크로의 차이, 멤버변수의 차이, 메소드의 차이 등에 대해서
비교를 해보았다.
DEFINE_COMMAND가 DEFINE_COMMAND_EX로 변경되고 CALL시에 SP이름뒤에 ;가 붙는다는것 이외에는 별 문제가 없었다. 결국 binding시의 변수 순서에 문제가 있는것으로 생각하고,
몇번의 삽질을 통해 SP의 인자로 들어가는 멤버변수는 결과 레코드셋이 담기는 변수들 위에
위치해야 된다는 결과를 얻게 되었다.

거의 3일동안 삽질하다가 오늘도 안되면 그냥 DynamicAccessor를 이용해서 돌아가려고 했는데,
다행히 문제가 해결됐다 ^^;

,
TOTAL TODAY