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

VC++ 調用 ADO 完整指南:#import 導入、COM 初始化、連接池、參數化命令與錯誤處理

     編輯:VC++

ADO 在 VC++ 中的 COM 本質

ADO (ActiveX Data Objects) 是 COM 組件。VC++ 使用 `#import` 導入類型庫生成智能指針 (`_ConnectionPtr`, `_RecordsetPtr` 等),自動管理 `AddRef/Release`,極大簡化 COM 開發。

步驟 1:導入類型庫


// stdafx.h 或公共頭文件
#import "C:\\Program Files\\Common Files\\System\\ADO\\msado15.dll" \
    no_namespace rename("EOF", "adoEOF") // 避免 EOF 宏衝突

// 常用智能指針類型
// _ConnectionPtr, _CommandPtr, _RecordsetPtr, _ParameterPtr, ErrorsPtr, ErrorPtr

關鍵屬性:`no_namespace` 省去 `ADODB::` 前綴;`rename` 解決宏衝突。

步驟 2:COM 庫初始化 (線程級)


HRESULT InitCOM() {
    HRESULT hr = CoInitializeEx(NULL, COINIT_APARTMENTTHREADED); // STA 模式
    // 或 CoInitialize(NULL) (舊版)
    return hr;
}

void UninitCOM() {
    CoUninitialize();
}

// RAII 封裝 (推薦)
class ComInitializer {
public:
    ComInitializer() : hr_(CoInitializeEx(NULL, COINIT_APARTMENTTHREADED)) {}
    ~ComInitializer() { if (SUCCEEDED(hr_)) CoUninitialize(); }
    operator HRESULT() const { return hr_; }
private:
    HRESULT hr_;
};

// 使用
void DatabaseOperation() {
    ComInitializer com;
    if (FAILED(com)) return;
    // ... ADO 操作
} // 離開作用域自動 CoUninitialize

步驟 3:建立數據庫連接


_ConnectionPtr CreateConnection(LPCTSTR connStr) {
    _ConnectionPtr pConn(__uuidof(Connection)); // 智能指針創建實例
    pConn->ConnectionTimeout = 20; // 秒
    pConn->Open(_bstr_t(connStr), _bstr_t(L""), _bstr_t(L""), adConnectUnspecified);
    return pConn; // 智能指針複製 (AddRef),離開作用域自動 Release
}

// 連接字串範例
// Access: "Provider=Microsoft.Jet.OLEDB.4.0;Data Source=C:\\Data\\db.mdb;"
// SQL Server: "Provider=SQLOLEDB;Server=.;Database=Test;UID=sa;PWD=123;"
// SQL Server (ODBC): "Driver={SQL Server};Server=.;Database=Test;UID=sa;PWD=123;"

步驟 4:執行命令與獲取記錄集

方式 A:`_ConnectionPtr::Execute` (簡單查詢)


_RecordsetPtr ExecuteQuery(_ConnectionPtr pConn, LPCTSTR sql) {
    _variant_t rowsAffected;
    _RecordsetPtr pRs = pConn->Execute(_bstr_t(sql), &rowsAffected, adCmdText);
    return pRs; // 只支持前向只讀、唯讀游標
}

方式 B:`_CommandPtr` + 參數化 (防注入、存儲過程、輸出參數)


_RecordsetPtr ExecuteParamQuery(_ConnectionPtr pConn, LPCTSTR sql, const std::vector<_variant_t>& params) {
    _CommandPtr pCmd(__uuidof(Command));
    pCmd->ActiveConnection = pConn;
    pCmd->CommandText = _bstr_t(sql);
    pCmd->CommandType = adCmdText; // 或 adCmdStoredProc

    // 添加參數 (順序必須與 SQL 中 ? 一致)
    for (size_t i = 0; i < params.size(); ++i) {
        _ParameterPtr pParam = pCmd->CreateParameter(
            _bstr_t(L""), adVariant, adParamInput, 0, params[i]);
        pCmd->Parameters->Append(pParam);
    }

    _RecordsetPtr pRs = pCmd->Execute(NULL, NULL, adCmdText);
    return pRs;
}

// 使用
std::vector<_variant_t> params = { _variant_t(L"admin"), _variant_t(1) };
_RecordsetPtr pRs = ExecuteParamQuery(pConn, L"SELECT * FROM Users WHERE Name=? AND Status=?", params);

步驟 5:遍歷記錄集與類型安全讀取


void ProcessRecordset(_RecordsetPtr pRs) {
    if (!pRs || pRs->adoEOF) return;

    // 獲取列信息 (可選)
    long colCount = pRs->Fields->Count;
    for (long i = 0; i < colCount; ++i) {
        _bstr_t colName = pRs->Fields->GetItem((long)i)->Name;
        DataTypeEnum colType = pRs->Fields->GetItem((long)i)->Type;
        // 打印列名/類型...
    }

    // 遍歷行
    while (!pRs->adoEOF) {
        for (long i = 0; i < colCount; ++i) {
            _variant_t val = pRs->Fields->GetItem(i)->Value;
            if (val.vt == VT_NULL) {
                // NULL 值處理
            } else {
                // 類型安全提取
                switch (val.vt) {
                    case VT_I4:    int n = val.lVal; break;
                    case VT_BSTR:  CString s = (LPCTSTR)_bstr_t(val); break;
                    case VT_DATE:  COleDateTime dt = val.date; break;
                    case VT_CY:    CURRENCY cy = val.cyVal; break;
                    // ... 其他類型
                }
            }
        }
        pRs->MoveNext();
    }
}

步驟 6:異常與錯誤處理 (雙層)


void SafeAdoOperation() {
    ComInitializer com;
    if (FAILED(com)) return;

    try {
        _ConnectionPtr pConn(__uuidof(Connection));
        pConn->Open(...);

        try {
            _RecordsetPtr pRs = pConn->Execute(L"SELECT * FROM Table");
            // 處理...
        }
        catch (_com_error& e) {
            // 1. COM 層錯誤 (HRESULT, IErrorInfo)
            CString msg;
            msg.Format(_T("COM Error: 0x%08lx - %s\nSource: %s\nDescription: %s"),
                e.Error(), e.ErrorMessage(), (LPCTSTR)e.Source(), (LPCTSTR)e.Description());
            AfxMessageBox(msg);
        }

        // 2. ADO 提供者錯誤 (Connection.Errors 集合)
        if (pConn->Errors->Count > 0) {
            for (long i = 0; i < pConn->Errors->Count; ++i) {
                ErrorPtr pErr = pConn->Errors->GetItem(i);
                CString adoErr;
                adoErr.Format(_T("ADO Error: 0x%lx - %s"), pErr->Number, (LPCTSTR)pErr->Description);
                TRACE(adoErr);
            }
        }
    }
    catch (...) {
        AfxMessageBox(_T("未知嚴重錯誤"));
    }
}

步驟 7:事務處理


void TransactionSample(_ConnectionPtr pConn) {
    pConn->BeginTrans();
    try {
        pConn->Execute(L"INSERT INTO T1 VALUES (1)");
        pConn->Execute(L"UPDATE T2 SET C=1 WHERE ID=2");
        pConn->CommitTrans();
    }
    catch (_com_error&) {
        pConn->RollbackTrans();
        throw;
    }
}

常見問題與優化

問題解決
編譯錯誤:`EOF` 重定義`rename("EOF", "adoEOF")` 或 `#undef EOF`
連接字串 Provider 錯誤Access 用 `Microsoft.Jet.OLEDB.4.0` (32位) / `Microsoft.ACE.OLEDB.12.0` (64位/新版)
參數順序錯誤OLE DB 使用位置參數 `?`,Append 順序必須與 SQL 中 `?` 順序完全一致
記錄集游標類型限制`Execute` 返回 `adOpenForwardOnly`/`adLockReadOnly`;需可更新游標請用 `_CommandPtr` 設置 `CursorType`/`LockType` 後 `Open`
大數據量效能使用 `adUseClient` 客戶端游標 (`CursorLocation`),分頁獲取 (`PageSize`/`AbsolutePage`)
64 位編譯找不到 Provider安裝 64 位 Access Database Engine / SQL Server Native Client

現代替代建議

  • ODBC + C++ 封裝:標準化、跨平台、無 COM 依賴。
  • OLE DB 驅動程序 for SQL Server (MSOLEDBSQL):微軟推薦的新一代 OLE DB Provider。
  • C++ REST SDK (cpprestsdk) + JSON:配合 Web API 替代直連數據庫。
  • SQLite (C/C++ 嵌入式):本地存儲首選,零配置。

結語

VC++ 調用 ADO 核心在於:`#import` 智能指針管理 COM 生命週期、`_variant_t`/`_bstr_t` 封裝 VARIANT/BSTR、`_com_error` 捕獲 COM 異常、`Connection.Errors` 診斷提供者錯誤。**參數化命令 (`_CommandPtr`) 是防注入、支援存儲過程、類型安全的標準做法**。新專案建議遷移至 ODBC/現代數據訪問庫,降低 COM 複雜度與部署依賴。

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