首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> C語言 >> VC >> 關於VC++

Visual C++ COM 元件返回 ADO Recordset 給 ASP:正確實作避免記憶體洩漏與型別不匹配

     編輯:關於VC++

ATL COM 元件若錯誤返回 _RecordsetPtr 給 ASP,易導致「物件已關閉操作不允許」、「型別不匹配」、「ASP 0115 外部物件錯誤」。本文提供 Microsoft 官方建議模式:**建立斷開連線的複製 Recordset (Disconnected Clone)**,確保 ASP 安全迭代、元件端資源即時釋放。

專案建立步驟

  1. 新建 ATL DLL 專案:PassRs
  2. 插入 ATL 簡單物件:PassRsObj
  3. 新增方法:TestMethod([out, retval] LPDISPATCH* ppRecordset)
  4. 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避免連線佔用、記憶體洩漏、游標鎖定

ASP 呼叫範例


<%
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 失敗 → 型別不匹配
  • ❌ 不斷開連線 → 連線物件隨元件釋放 → Recordset 失效
  • ❌ 回傳 _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);
}

部署注意事項

  • 目標機需安裝 MDAC 2.8+ / Windows 內建 ADO
  • DLL 註冊:regsvr32 PassRs.dll
  • IIS 應用程式集區身分識別需具資料庫存取權限
  • 連線字串勿硬編碼密碼,建議從登錄表/組態檔讀取或使用 Windows 整合驗證

總結

「Client Cursor → Disconnect → Clone → Return IDispatch」是 COM 返回 Recordset 給 ASP 的黃金法則。嚴格遵循資源獲取即釋放 (RAII),智慧指標 (_ConnectionPtr, _RecordsetPtr) 自動管理參考計數,配合 #import 產生的智慧指標包裝類,程式碼簡潔且無洩漏。完整 ATL 專案原始碼請下載附件。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved