用命令对象的方法来创建一个参数对象,其中的长度参数(第三个)如果是固定长度的类型,就填-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); |