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

VC++ 數據庫編程:OLE 圖像字段讀取與顯示完整方案

     編輯:VC 教學

問題背景

在 VC++ 數據庫應用開發中,處理 Access/SQL Server 等數據庫中的 OLE 對象字段(存儲 BMP、JPG 等圖像)是常見難點。數據集類(CRecordset 派生類)通過 DFX_Binary 交換得到的是原始二進制數組(CByteArray),包含 OLE 封裝頭信息,無法直接用於繪圖。本文提供完整解決方案:從 DAO 記錄集讀取二進制數據 → 解析 DIB 位圖頭 → 創建 GDI 位圖對象 → 界面繪製顯示。

核心技術路線

  1. 定義 CDaoRecordset 派生類,映射 OLE 字段到 CByteArray
  2. 讀取記錄集,獲取原始二進制數據
  3. 解析 OLE 封裝結構,定位標準 DIB(設備無關位圖)數據起始位置
  4. 提取 BITMAPINFOHEADER、調色盤、像素數據
  5. 調用 CreateDIBitmap 創建 GDI 位圖句柄(HBITMAP)
  6. 關聯 CBitmap 對象,使用 StretchBlt 繪製到目標 DC

關鍵代碼實現

1. 記錄集類定義


class CImageData : public CDaoRecordset
{
public:
    CImageData(CDaoDatabase* pDB = NULL);
    DECLARE_DYNAMIC(CImageData)

    // 字段映射
    CByteArray m_Images; // 對應 OLE 字段 "Images"

    // 重寫虛函數
    virtual CString GetDefaultDBName();   // 返回 .mdb 路徑
    virtual CString GetDefaultSQL();      // 返回表名 "[Table]"
    virtual void DoFieldExchange(CDaoFieldExchange* pFX);
};

IMPLEMENT_DYNAMIC(CImageData, CDaoRecordset)

CImageData::CImageData(CDaoDatabase* pDB) : CDaoRecordset(pDB)
{
    m_nFields = 1;
    m_nDefaultType = dbOpenDynaset;
}

CString CImageData::GetDefaultDBName()
{
    return _T("E:\\Images.mdb"); // 數據庫路徑
}

CString CImageData::GetDefaultSQL()
{
    return _T("[Table]"); // 表名
}

void CImageData::DoFieldExchange(CDaoFieldExchange* pFX)
{
    pFX->SetFieldType(CDaoFieldExchange::outputColumn);
    DFX_Binary(pFX, _T("[Images]"), m_Images); // 二進制交換
}

2. 核心解析函數:二進制數組 → CBitmap


// 全局/成員 CBitmap 對象,持有位圖資源
CBitmap g_Bitmap; 

BOOL CImageDlg::ParseOleImageData(const CByteArray& dbArray)
{
    if (dbArray.GetSize() == 0) return FALSE;

    // 複製數據,避免修改原記錄集緩衝區
    CByteArray arr = dbArray;

    // 關鍵:OLE 封裝頭固定偏移 78 字節 + BITMAPFILEHEADER(14字節)
    // 實際偏移需根據 OLE 版本驗證,常見 78 或 116 字節
    const int HEADER_OFFSET = 78 + sizeof(BITMAPFILEHEADER);
    if (arr.GetSize() <= HEADER_OFFSET) return FALSE;

    // 移除封裝頭,定位到 BITMAPINFOHEADER
    arr.RemoveAt(0, HEADER_OFFSET);

    // 直接從數據緩衝區解析結構(零拷貝)
    LPBITMAPINFOHEADER pBmiHeader = (LPBITMAPINFOHEADER)arr.GetData();
    LPBITMAPINFO pBmi = (LPBITMAPINFO)arr.GetData();

    // 計算調色盤大小
    int nColors = pBmiHeader->biClrUsed ? pBmiHeader->biClrUsed : (1 << pBmiHeader->biBitCount);

    // 定位像素數據起始指針
    LPVOID pBits;
    if (pBmiHeader->biBitCount > 8)
    {
        // 高色深:調色盤後可能跟 3 個 DWORD 的位域掩碼
        pBits = (LPVOID)((LPDWORD)(pBmi->bmiColors + nColors) + 
                         ((pBmiHeader->biCompression == BI_BITFIELDS) ? 3 : 0));
    }
    else
    {
        // 8位及以下:調色盤緊接像素數據
        pBits = (LPVOID)(pBmi->bmiColors + nColors);
    }

    // 創建設備兼容 DC 用於 CreateDIBitmap
    CClientDC dc(NULL);
    HBITMAP hBmp = CreateDIBitmap(
        dc.m_hDC,
        pBmiHeader,
        CBM_INIT,
        pBits,
        pBmi,
        DIB_RGB_COLORS
    );

    if (!hBmp) return FALSE;

    // 關聯 CBitmap 對象(自動管理生命周期)
    if (g_Bitmap.m_hObject)
        g_Bitmap.DeleteObject();
    g_Bitmap.Attach(hBmp);

    return TRUE;
}

3. 界面繪製顯示


void CImageDlg::OnShowImage()
{
    CImageData rs;
    if (!rs.Open()) return; // 打開記錄集

    if (ParseOleImageData(rs.m_Images))
    {
        // 在對話框指定區域繪製(如 20,20 寬高 200x200)
        CPaintDC dc(this);
        if (g_Bitmap.m_hObject)
        {
            CDC memDC;
            memDC.CreateCompatibleDC(&dc);
            CBitmap* pOldBmp = memDC.SelectObject(&g_Bitmap);

            BITMAP bm;
            g_Bitmap.GetBitmap(&bm);

            // 縮放繪製保持比例或填充矩形
            dc.StretchBlt(20, 20, 200, 200, &memDC, 0, 0, bm.bmWidth, bm.bmHeight, SRCCOPY);

            memDC.SelectObject(pOldBmp);
        }
    }
    rs.Close();
}

關鍵技術點深度解析

OLE 封裝結構變異性

Access 不同版本寫入的 OLE 封裝頭長度可能不同(78、116 字節等)。生產環境建議:

  • 通過特徵碼掃描定位 BM 簽名(BITMAPFILEHEADER 開頭)
  • 或使用 OleLoadPicture / IPicture 接口讓系統自動解析(更健壯但需 COM 初始化)

位圖格式兼容性

  • 壓縮格式:BI_RGB(無壓縮)、BI_RLE8/4、BI_BITFIELDS、BI_JPEG、BI_PNG。本方案僅處理非壓縮/位域格式。
  • 高色深位域掩碼:16/32 位位圖可能包含 3 個 DWORD 掩碼(紅綠藍),解析時需跳過。
  • Alpha 通道:32 位位圖可能含 Alpha,CreateDIBitmap 於舊版 Windows 不支持預乘 Alpha,需手動預處理。

內存與資源管理

  • CByteArray::RemoveAt 會移動內存,大圖片時效率較低,建議使用指針偏移而非修改數組
  • CBitmap::Attach 托管 HBITMAP,析構時自動 DeleteObject,避免 GDI 泄漏
  • 繪製時創建兼容 DC(CreateCompatibleDC)而非直接用屏幕 DC,確保繪製質量

現代化替代方案

若項目允許使用較新技術棧,推薦:

  • GDI+:Image::FromStream 直接從 IStream 加載多格式圖像(JPG/PNG/TIFF 等),無需手動解析 OLE 結構
  • WIC (Windows Imaging Component):系統級解碼器框架,支持擴展格式,性能優異
  • ATL/OLE 圖片控件:CPictureHolder / IPicture 封裝 OLE 圖片渲染邏輯

總結

本文提供的方案通過手動解析 OLE 封裝結構、提取 DIB 數據並創建 GDI 位圖,實現了 VC++ 經典數據庫編程中圖像字段的讀取顯示。雖然代碼涉及底層位圖結構操作,但理解其原理有助於處理各類二進制圖像數據互操作場景。現代項目建議優先採用 GDI+/WIC 等高層封裝,降低維護成本並提升格式兼容性。

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