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

ADO 實現 C++ 對象持久化存取:基於序列化與 BLOB 字段的完整方案

     編輯:關於VC++

在圖形應用開發中,常需將複雜的 C++ 對象(含動態數組、GDI 物件等)持久化到數據庫。本文展示一套成熟方案:**利用 MFC 序列化機制將對象轉為二進制流,存入 Access/SQL Server 的 BLOB (OLE Object) 字段,讀取時反序列化還原對象**。完整覆蓋類定義、數據庫連接、寫入/讀取核心代碼、關鍵數據結構轉換 (CMemFile→SAFEARRAY→VARIANT→AppendChunk/GetChunk),可直接應用於圖形、文檔、配置對象的數據庫存儲。

一、 可序列化 C++ 類設計規範

對象類必須:

  1. 繼承 CObject (支持運行時類信息、動態創建、序列化)。
  2. 聲明 DECLARE_SERIAL(ClassName) 宏 (頭文件)。
  3. 實現 IMPLEMENT_SERIAL(ClassName, BaseClass, Schema) 宏 (CPP 文件)。
  4. 重寫虛函數 virtual void Serialize(CArchive& ar),區分 ar.IsStoring() (寫入) 與 ar.IsLoading() (讀取)。
  5. 正確序列化所有成員:基本類型用 <</>>,結構體用 Write/Read,容器 (如 CArray) 調用其 Serialize。

示例類:CLine (折線圖形對象)


// Line.h
class CLine : public CObject {
    DECLARE_SERIAL(CLine)
private:
    LOGPEN       m_logPen;           // 畫筆屬性 (結構體)
    COLORREF     m_crBackColor;      // 背景色
    CArray m_PointArray; // 點座標動態數組

public:
    CLine(); virtual ~CLine();
    virtual void Serialize(CArchive& ar); // 核心序列化
    // 業務方法:AddPoint, DrawPoint, DrawLine, Get/Set Color/Width...
    CLine& operator=(CLine& src); // 賦值運算符
};

// Line.cpp
IMPLEMENT_SERIAL(CLine, CObject, 1) // Schema 1

void CLine::Serialize(CArchive& ar) {
    if (ar.IsStoring()) {
        ar << DWORD(m_crBackColor);           // COLORREF 轉 DWORD 存儲
        ar.Write(&m_logPen, sizeof(LOGPEN));  // 結構體原始寫入
    } else {
        DWORD dw; ar >> dw; m_crBackColor = COLORREF(dw);
        ar.Read(&m_logPen, sizeof(LOGPEN));
    }
    m_PointArray.Serialize(ar); // CArray 內置序列化支持
}

// 賦值運算符 (淺拷貝非指針成員)
CLine& CLine::operator=(CLine& src) {
    if (this != &src) {
        m_logPen = src.m_logPen;
        m_crBackColor = src.m_crBackColor;
        // m_PointArray 需深拷貝或賴於 CArray operator=
    }
    return *this;
}

二、 ADO 數據庫連接配置


// StdAfx.h 引入 ADO 類型庫 (路徑隨 Office 版本調整)
#import "C:\\Program Files\\Common Files\\System\\ADO\\msado15.dll" \
    no_namespace rename("EOF", "adoEOF")

// 初始化 COM (App::InitInstance)
AfxOleInit(); // 或 CoInitialize(NULL)

// 連接函數
BOOL CMyDlg::OpenDB(CString strPath) {
    try {
        HRESULT hr = m_pCon.CreateInstance(__uuidof(Connection));
        _bstr_t connStr = "Provider=Microsoft.Jet.OLEDB.4.0;Data Source=" + _bstr_t(strPath) + ";";
        hr = m_pCon->Open(connStr, "", "", adModeUnknown);

        hr = m_pSet.CreateInstance(__uuidof(Recordset));
        m_pSet->CursorLocation = adUseClient; // 客戶端游標支援 GetChunk/AppendChunk
        hr = m_pSet->Open("SELECT * FROM object_table", _variant_t((IDispatch*)m_pCon, true),
                          adOpenStatic, adLockOptimistic, adCmdText);
        return SUCCEEDED(hr);
    } catch (_com_error& e) {
        AfxMessageBox(_T("DB連接失敗: ") + e.ErrorMessage());
        return FALSE;
    }
}

表結構建議:object_table (id AUTOINCREMENT, name TEXT, object OLEOBJECT)。

三、 對象寫入數據庫:序列化 → CMemFile → SAFEARRAY → AppendChunk


void CMyDlg::OnButtonSave() {
    if (!m_bState) return; // 連接狀態檢查
    UpdateData(); // 獲取 m_sName

    try {
        m_pSet->AddNew();
        m_pSet->PutCollect("name", _variant_t(m_sName));

        // 1. 序列化到內存文件
        CMemFile memFile;
        CArchive ar(&memFile, CArchive::store);
        m_Line.Serialize(ar); // 調用對象序列化
        ar.Close(); // 必須 Close 刷新緩衝區

        // 2. 獲取內存緩衝區指針與長度
        DWORD dwSize = memFile.GetLength();
        LPBYTE lpInfo = memFile.Detach(); // 奪取所有權,調用者負責 delete[]

        // 3. 構造一維 SAFEARRAY (VT_UI1)
        SAFEARRAYBOUND rgsabound[1];
        rgsabound[0].lLbound = 0;
        rgsabound[0].cElements = dwSize;
        SAFEARRAY* psa = SafeArrayCreate(VT_UI1, 1, rgsabound);

        // 4. 填充數據
        for (long i = 0; i < (long)dwSize; i++) {
            SafeArrayPutElement(psa, &i, lpInfo++);
        }
        delete[] (lpInfo - dwSize); // 釋放 Detach 返回的緩衝區

        // 5. 封裝 VARIANT 並分塊寫入 BLOB 字段
        VARIANT varBLOB;
        varBLOB.vt = VT_ARRAY | VT_UI1;
        varBLOB.parray = psa;

        // 關鍵:使用 AppendChunk 分塊寫入大對象
        m_pSet->GetFields()->GetItem("object")->AppendChunk(varBLOB);

        m_pSet->Update(); // 提交記錄
        SafeArrayDestroy(psa); // 釋放 SAFEARRAY

        m_List.AddString(m_sName); // UI 同步
    } catch (_com_error& e) {
        AfxMessageBox(_T("保存失敗: ") + e.Description());
    }
}

四、 對象讀取:GetChunk → SAFEARRAY → CMemFile → 反序列化


void CMyDlg::OnSelchangeListData() {
    int iPos = m_List.GetCurSel();
    if (iPos < 0) return;

    m_pSet->MoveFirst();
    m_pSet->Move(iPos); // 定位到選中記錄

    long lDataSize = m_pSet->GetFields()->GetItem("object")->ActualSize;
    if (lDataSize <= 0) return;

    _variant_t varBLOB;
    varBLOB = m_pSet->GetFields()->GetItem("object")->GetChunk(lDataSize); // 一次性讀取

    if (varBLOB.vt == (VT_ARRAY | VT_UI1)) {
        BYTE* pBuf = nullptr;
        // 1. 訪問 SAFEARRAY 數據指針
        SafeArrayAccessData(varBLOB.parray, (void**)&pBuf);

        // 2. 包裝為 CMemFile 並反序列化
        CMemFile memFile;
        memFile.Attach(pBuf, lDataSize); // 不複製,直接掛接
        memFile.SeekToBegin();
        CArchive ar(&memFile, CArchive::load);
        m_Line.Serialize(ar); // 還原對象
        ar.Close();
        memFile.Detach(); // 分離,不釋放 pBuf (由 SafeArrayUnaccessData 管理)

        // 3. 釋放 SAFEARRAY 訪問鎖
        SafeArrayUnaccessData(varBLOB.parray);

        // 4. 觸發重繪
        CRect rc = GetRect(IDC_STATIC_RECT);
        InvalidateRect(rc);
    }
    VariantClear(&varBLOB); // 清理 VARIANT
}

五、 關鍵技術點深度解析

環節核心 API/類關鍵細節
序列化容器CMemFile內存文件,無磁盤 I/O,Detach() 移交緩衝區所有權,Attach() 掛接外部緩衝區
二進制封裝SAFEARRAY + VARIANTADO BLOB 字段要求 VT_ARRAY | VT_UI1;SafeArrayCreate/PutElement/AccessData/UnaccessData/Destroy 全生命周期管理
大對象寫入AppendChunk分塊寫入避免單次巨大內存分配,配合 adUseClient 游標
大對象讀取GetChunk / ActualSizeActualSize 獲取真實大小分配緩衝;GetChunk(size) 一次性讀完
游標類型adOpenStatic + adLockOptimistic靜態游標支持書籤/絕對定位;樂觀鎖減少並發衝突
COM 錯誤處理_com_error所有 ADO 操作拋出 _com_error,統一 try-catch 捕獲,ErrorMessage()/Description() 獲取詳情

六、 擴展應用場景

  • 圖片/文檔存儲:CImage/CDocument 同樣支持 Serialize,無需修改寫入/讀取邏輯。
  • 多對象單表:表增加 type 字段區分對象類型,讀取時根據類型動態創建對象 (CRuntimeClass::CreateObject) 再反序列化。
  • SQL Server VARBINARY(MAX):原理相同,連接字符串改用 SQLOLEDB,字段類型改為 VARBINARY(MAX),ADO 操作不變。
  • 版本兼容:IMPLEMENT_SERIAL 的 wSchema 參數配合 CArchive::GetObjectSchema() 實現多版本共存。

七、 總結

「MFC 序列化 + ADO BLOB」是 C++ 對象持久化到關係數據庫的經典高效方案。**核心鏈路**:Serialize → CMemFile → SAFEARRAY(VT_UI1) → VARIANT → AppendChunk/GetChunk。掌握 CMemFile::Detach/Attach 零拷貝技巧、SAFEARRAY 生命周期管理、adUseClient 游標配置,即可構建穩健的對象存儲層,支撐圖形編輯器、報表模板、用戶配置等複雜場景。

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