ATL COM 元件若錯誤返回 _RecordsetPtr 給 ASP,易導致「物件已關閉操作不允許」、「型別不匹配」、「ASP 0115 外部物件錯誤」。本文提供 Microsoft 官方建議模式:**建立斷開連線的複製 Recordset (Disconnected Clone)**,確保 ASP 安全迭代、元件端資源即時釋放。
PassRsPassRsObjTestMethod([out, retval] LPDISPATCH* ppRecordset)stdafx.h 引入 ADO:#import "msado15.dll" no_namespace rename("EOF", "adoEOF")
STDMETHODIMP CPassRsObj::TestMethod(LPDISPATCH* ppRecordset)
{
_ConnectionPtr pConn;
_RecordsetPtr pRs;
HRESULT hr = pConn.CreateInstance(__uuidof(Connection));
if (FAILED(hr)) return hr;
hr = pRs.CreateInstance(__uuidof(Recordset));
if (FAILED(hr)) return hr;
// 開啟連線 (請替換實際 UID/PWD)
pConn->Open("DSN=pubs;uid=;pwd=;",
"", "", -1);
// 關鍵:Client-side cursor 支援斷開連線
pRs->CursorLocation = adUseClient;
pRs->Open("SELECT * FROM authors",
pConn.GetInterfacePtr(),
adOpenKeyset, adLockOptimistic, -1);
// 1. 斷開連線:將 ActiveConnection 設為 NULL
pRs->PutRefActiveConnection(NULL);
// 2. 複製 Recordset (Clone 需支援書籤,adUseClient 滿足)
_RecordsetPtr pRsClone = pRs->Clone(adLockOptimistic);
// 3. 回傳 IDispatch 介面給 ASP
hr = pRsClone->QueryInterface(IID_IDispatch, (void**)ppRecordset);
// 4. 立即釋放元件端資源
pRs->Close();
pConn->Close();
pRs = NULL;
pConn = NULL;
pRsClone = NULL; // 智慧指標自動 Release
return hr;
}
| 步驟 | 原因 |
|---|---|
CursorLocation = adUseClient | 唯有客戶端游標支援 Clone 與斷開連線 |
PutRefActiveConnection(NULL) | 將 Recordset 從連線物件分離,變為純記憶體資料集 |
Clone(adLockOptimistic) | 建立獨立副本,ASP 迭代不影響元件端原始集 |
QueryInterface(IID_IDispatch) | ASP 僅能使用 IDispatch (晚期綁定) 存取 Recordset |
| 元件端立即 Close/Release | 避免連線佔用、記憶體洩漏、游標鎖定 |
<%
Dim rsTest, oTestPassRs
Set oTestPassRs = Server.CreateObject("PassRs.PassRsObj")
Set rsTest = oTestPassRs.TestMethod()
Do Until rsTest.adoEOF
Response.Write("Value = " & rsTest(1) & "
")
rsTest.MoveNext
Loop
rsTest.Close
Set rsTest = Nothing
Set oTestPassRs = Nothing
%>
注意:ASP 端使用 adoEOF (因 rename("EOF", "adoEOF") 避免與 ASP 內建 EOF 衝突)。
pRs 不 Clone → ASP 迭代時元件端已關閉連線 → 「物件已關閉」adUseClient → Clone 失敗 → 型別不匹配_RecordsetPtr* 而非 LPDISPATCH* → ASP 無法識別型別
STDMETHODIMP CPassRsObj::GetAuthorsByState(BSTR state, LPDISPATCH* ppRs)
{
_CommandPtr cmd; cmd.CreateInstance(__uuidof(Command));
cmd->ActiveConnection = pConn;
cmd->CommandText = "SELECT * FROM authors WHERE state = ?";
cmd->CommandType = adCmdText;
_ParameterPtr param = cmd->CreateParameter("state", adVarChar, adParamInput, 2, state);
cmd->Parameters->Append(param);
_RecordsetPtr rs; rs.CreateInstance(__uuidof(Recordset));
rs->CursorLocation = adUseClient;
rs->Open(cmd, vtMissing, adOpenKeyset, adLockOptimistic, -1);
rs->PutRefActiveConnection(NULL);
_RecordsetPtr clone = rs->Clone(adLockOptimistic);
return clone->QueryInterface(IID_IDispatch, (void**)ppRs);
}
regsvr32 PassRs.dll「Client Cursor → Disconnect → Clone → Return IDispatch」是 COM 返回 Recordset 給 ASP 的黃金法則。嚴格遵循資源獲取即釋放 (RAII),智慧指標 (_ConnectionPtr, _RecordsetPtr) 自動管理參考計數,配合 #import 產生的智慧指標包裝類,程式碼簡潔且無洩漏。完整 ATL 專案原始碼請下載附件。