用命令对象的方法来创建一个参数对象,其中的长度参数(第三个)如果是固定长度的类型,就填-1,如果是字符串等可变长度的就填其实际长度。Parameters是命令对象的一个容器,它的Append方法就是把创建的参数对象追加到该容器里。Append进去的参数按先后顺序与SQL语句中的问号从左至右一一对应。 (3). 执行命令打开记录集
// Open Recordset Object Rs1 = Cmd1->Execute( &vtEmpty, &vtEmpty2, adCmdText ); |
但要注意,用Command和Connection对象的Execute方法得到的Recordset是只读的。因为在打开Recordset之前,我们无法设置它的LockType属性(其默认值为只读)。而在打开之后设置LockType不起作用。 我发现用上述方法得到记录集Rs1后,不但Rs1中的记录无法修改,即使直接用SQL语句修改同一表中任何记录都不行。 要想能修改数据,还是要用Recordset自己的Open方法才行,如:
try{ m_pRecordset->Open((IDispatch *) Cmd1, vtMissing, adOpenStatic, adLockOptimistic, adCmdUnspecified); } catch (_com_error &e) { ::MessageBox(NULL,"mytable表不存在。","提示",MB_OK │ MB_ICONWARNING); } |
Recordset对象的Open方法真是太好了,其第一个参数可以是SQL语句、表名字、命令对象指针等等。 9、响应ADO的通知事件 通知事件就是当某个特定事件发生时,由Provider通知客户程序,换句话说,就是由Provider调用客户程序中的一个特定的方法(即事件的处理函数)。所以为了响应一个事件,最关键的就是要实现事件的处理函数。 (1). 从ConnectionEventsVt接口派生出一个类 为了响应_Connection的通知事件,应该从ConnectionEventsVt接口派生出一个类:
class CConnEvent : public ConnectionEventsVt { private: ULONG m_cRef; public: CConnEvent() { m_cRef = 0; }; ~CConnEvent() {}; STDMETHODIMP QueryInterface(REFIID riid, void ** ppv); STDMETHODIMP_(ULONG) AddRef(void); STDMETHODIMP_(ULONG) Release(void); STDMETHODIMP raw_InfoMessage( struct Error *pError, EventStatusEnum *adStatus, struct _Connection *pConnection); STDMETHODIMP raw_BeginTransComplete( LONG TransactionLevel, struct Error *pError, EventStatusEnum *adStatus, struct _Connection *pConnection); ...... }; |
(2). 实现每一个事件的处理函数(凡是带raw_前缀的方法都把它实现了):
STDMETHODIMP CConnEvent::raw_InfoMessage( struct Error *pError, EventStatusEnum *adStatus, struct _Connection *pConnection) { *adStatus = adStatusUnwantedEvent; return S_OK; }; |
有些方法虽然你并不需要,但也必须实现它,只需简单地返回一个S_OK即可。但如果要避免经常被调用,还应在其中将adStatus参数设置为adStatusUnwantedEvent,则在本次调用后,以后就不会被调用了。 另外还必须实现QueryInterface, AddRef, 和Release三个方法:
STDMETHODIMP CConnEvent::QueryInterface(REFIID riid, void ** ppv) { *ppv = NULL; if (riid == __uuidof(IUnknown) ││ riid == __uuidof(ConnectionEventsVt)) *ppv = this; if (*ppv == NULL) return ResultFromScode(E_NOINTERFACE); AddRef(); return NOERROR; } STDMETHODIMP_(ULONG) CConnEvent::AddRef() { return ++m_cRef; }; STDMETHODIMP_(ULONG) CConnEvent::Release() { if (0 != --m_cRef) return m_cRef; delete this; return 0; } |
(3). 开始响应通知事件
// Start using the Connection events IConnectionPointContainer *pCPC = NULL; IConnectionPoint *pCP = NULL; hr = pConn.CreateInstance(__uuidof(Connection)); if (FAILED(hr)) return; hr = pConn->QueryInterface(__uuidof(IConnectionPointContainer), (void **)&pCPC); if (FAILED(hr)) return; hr = pCPC->FindConnectionPoint(__uuidof(ConnectionEvents), &pCP); pCPC->Release(); if (FAILED(hr)) return; pConnEvent = new CConnEvent(); hr = pConnEvent->QueryInterface(__uuidof(IUnknown), (void **) &pUnk); if (FAILED(hr)) return rc; hr = pCP->Advise(pUnk, &dwConnEvt); pCP->Release(); if (FAILED(hr)) return; pConn->Open("dsn=Pubs;", "sa", "", adConnectUnspecified); |
也就是说在连接(Open)之前就做这些事。 (4). 停止响应通知事件
pConn->Close(); // Stop using the Connection events hr = pConn->QueryInterface(__uuidof(IConnectionPointContainer), (void **) &pCPC); if (FAILED(hr)) return; hr = pCPC->FindConnectionPoint(__uuidof(ConnectionEvents), &pCP); pCPC->Release(); if (FAILED(hr)) return rc; hr = pCP->Unadvise( dwConnEvt ); pCP->Release(); if (FAILED(hr)) return; |
在连接关闭之后做这件事。
10、邦定数据 定义一个绑定类,将其成员变量绑定到一个指定的记录集,以方便于访问记录集的字段值。 (1). 从CADORecordBinding派生出一个类:
class CCustomRs : public CADORecordBinding { BEGIN_ADO_BINDING(CCustomRs) ADO_VARIABLE_LENGTH_ENTRY2(3, adVarChar, m_szau_fname, sizeof(m_szau_fname), lau_fnameStatus, false) ADO_VARIABLE_LENGTH_ENTRY2(2, adVarChar, m_szau_lname, sizeof(m_szau_lname), lau_lnameStatus, false) ADO_VARIABLE_LENGTH_ENTRY2(4, adVarChar, m_szphone, sizeof(m_szphone), lphoneStatus, true) END_ADO_BINDING() public: CHAR m_szau_fname[22]; ULONG lau_fnameStatus; CHAR m_szau_lname[42]; ULONG lau_lnameStatus; CHAR m_szphone[14]; ULONG lphoneStatus; }; |
其中将要绑定的字段与变量名用BEGIN_ADO_BINDING宏关联起来。每个字段对应于两个变量,一个存放字段的值,另一个存放字段的状态。字段用从1开始的序号表示,如1,2,3等等。 特别要注意的是:如果要绑定的字段是字符串类型,则对应的字符数组的元素个数一定要比字段长度大2(比如m_szau_fname[22],其绑定的字段au_fname的长度实际是20),不这样绑定就会失败。我分析多出的2可能是为了存放字符串结尾的空字符null和BSTR字符串开头的一个字(表示BSTR的长度)。这个问题对于初学者来说可能是一个意想不到的问题。 CADORecordBinding类的定义在icrsint.h文件里,内容是:
class CADORecordBinding { public: STDMETHOD_(const ADO_BINDING_ENTRY*, GetADOBindingEntries) (VOID) PURE; }; BEGIN_ADO_BINDING宏的定义也在icrsint.h文件里,内容是: #define BEGIN_ADO_BINDING(cls) public: / typedef cls ADORowClass; / const ADO_BINDING_ENTRY* STDMETHODCALLTYPE GetADOBindingEntries() { / static const ADO_BINDING_ENTRY rgADOBindingEntries[] = { ADO_VARIABLE_LENGTH_ENTRY2宏的定义也在icrsint.h文件里: #define ADO_VARIABLE_LENGTH_ENTRY2(Ordinal, DataType, Buffer, Size, Status, Modify)/ {Ordinal, / DataType, / 0, / 0, / Size, / offsetof(ADORowClass, Buffer), / offsetof(ADORowClass, Status), / 0, / classoffset(CADORecordBinding, ADORowClass), / Modify}, #define END_ADO_BINDING宏的定义也在icrsint.h文件里: #define END_ADO_BINDING() {0, adEmpty, 0, 0, 0, 0, 0, 0, 0, FALSE}};/ return rgADOBindingEntries;} |
(2). 绑定
_RecordsetPtr Rs1; IADORecordBinding *picRs=NULL; CCustomRs rs; ...... Rs1->QueryInterface(__uuidof(IADORecordBinding), (LPVOID*)&picRs)); picRs->BindToRecordset(&rs); |