在 VC++ 數據庫應用開發中,處理 Access/SQL Server 等數據庫中的 OLE 對象字段(存儲 BMP、JPG 等圖像)是常見難點。數據集類(CRecordset 派生類)通過 DFX_Binary 交換得到的是原始二進制數組(CByteArray),包含 OLE 封裝頭信息,無法直接用於繪圖。本文提供完整解決方案:從 DAO 記錄集讀取二進制數據 → 解析 DIB 位圖頭 → 創建 GDI 位圖對象 → 界面繪製顯示。
CDaoRecordset 派生類,映射 OLE 字段到 CByteArrayBITMAPINFOHEADER、調色盤、像素數據CreateDIBitmap 創建 GDI 位圖句柄(HBITMAP)CBitmap 對象,使用 StretchBlt 繪製到目標 DC
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); // 二進制交換
}
// 全局/成員 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;
}
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();
}
Access 不同版本寫入的 OLE 封裝頭長度可能不同(78、116 字節等)。生產環境建議:
BM 簽名(BITMAPFILEHEADER 開頭)OleLoadPicture / IPicture 接口讓系統自動解析(更健壯但需 COM 初始化)BI_RGB(無壓縮)、BI_RLE8/4、BI_BITFIELDS、BI_JPEG、BI_PNG。本方案僅處理非壓縮/位域格式。DWORD 掩碼(紅綠藍),解析時需跳過。CreateDIBitmap 於舊版 Windows 不支持預乘 Alpha,需手動預處理。CByteArray::RemoveAt 會移動內存,大圖片時效率較低,建議使用指針偏移而非修改數組CBitmap::Attach 托管 HBITMAP,析構時自動 DeleteObject,避免 GDI 泄漏CreateCompatibleDC)而非直接用屏幕 DC,確保繪製質量若項目允許使用較新技術棧,推薦:
Image::FromStream 直接從 IStream 加載多格式圖像(JPG/PNG/TIFF 等),無需手動解析 OLE 結構CPictureHolder / IPicture 封裝 OLE 圖片渲染邏輯本文提供的方案通過手動解析 OLE 封裝結構、提取 DIB 數據並創建 GDI 位圖,實現了 VC++ 經典數據庫編程中圖像字段的讀取顯示。雖然代碼涉及底層位圖結構操作,但理解其原理有助於處理各類二進制圖像數據互操作場景。現代項目建議優先採用 GDI+/WIC 等高層封裝,降低維護成本並提升格式兼容性。