C++使用ADO實(shí)現(xiàn)存取圖片的方法
一般在網(wǎng)上查到的資料中向Server2000存儲(chǔ)圖片代碼比較多,從數(shù)據(jù)庫(kù)中讀取圖片并顯示也不少,但是把圖片從數(shù)據(jù)庫(kù)中二進(jìn)制數(shù)據(jù)轉(zhuǎn)換為原圖片保存在本地,就很少有C++代碼了。本文就此問(wèn)題一步一步地講一講解決的方法:
一、使用數(shù)據(jù)庫(kù)前的準(zhǔn)備
我們使用ADO,是用_ConnectionPtr,_RecordsetPtr來(lái)操縱數(shù)據(jù)庫(kù)的。還有一個(gè)_CommandPtr,本程序沒(méi)有使用它。
為了使用ADO,需要導(dǎo)入ADO動(dòng)態(tài)鏈接庫(kù)。在工程的stdafx.h文件中,添加如下代碼:
//導(dǎo)入ADO #import "C:\Program Files\Common Files\System\ado\msado15.dll"\ rename_namespace("ADOCG")rename("EOF","EndOfFile") using namespace ADOCG;
這些代碼聲明,在這個(gè)工程中使用ADO但不使用ADO的名字空間,并且為了避免常數(shù)沖突,將常數(shù)EOF改名為adoEOF。
再有就是要建一個(gè)簡(jiǎn)單的數(shù)據(jù)庫(kù),名字叫TestImage,里面有一個(gè)表Images,這個(gè)表有三個(gè)字段,分別是ID,Name,ImageData。
二、連接數(shù)據(jù)庫(kù)
連接數(shù)據(jù)庫(kù)的代碼可以放入一個(gè)函數(shù)中,在想調(diào)用的地方調(diào)用。一般不推薦在CAPP類(lèi)的Initalize()里連接數(shù)據(jù)庫(kù),在退出程序時(shí)關(guān)閉數(shù)據(jù)庫(kù)連接。應(yīng)該是在使用時(shí)連接,使用完馬上關(guān)閉。項(xiàng)目中m_pConn是_ConnectionPtr類(lèi)型的變量。
BOOL OpenConnection() { if(m_pConn == NULL) { m_pConn.CreateInstance("ADODB.Connection"); //創(chuàng)建_ConnectionPtr的一個(gè)實(shí)例 } try { if(adStateClosed == m_pConn->State) //如果已關(guān)閉 { m_pConn->Open("driver={SQL Server};Server=HP-CADD722B76A0;DATABASE=TestImage;UID=sa;PWD=sa","","",adModeUnknown); //因數(shù)據(jù)庫(kù)而異 return true; } } catch(_com_error e) { AfxMessageBox(_T("連接數(shù)據(jù)庫(kù)失敗!")); return false; } }
三、打開(kāi)數(shù)據(jù)集,操縱數(shù)據(jù)庫(kù)
在使用_RecordSetPtr對(duì)象m_pRecord時(shí),必須先創(chuàng)建這種對(duì)象的一個(gè)實(shí)例:
m_pRecord.CreateInstance( __uuidof(RecordSet) ); CString strSQL; //獲取表中最大的id,下一次插入時(shí)就用id+1 strSQL.Format(_T("Select count(*) as num, Max(ID) as maxid from Images")); try { m_pRecord->Open(strSQL.AllocSysString(), m_pConn.GetInterfacePtr(), adOpenDynamic, adLockUnspecified, adCmdText); } catch (_com_error e) { AfxMessageBox(_T("讀取最大的id異常")); eturn; } //從RecordSet中獲取數(shù)據(jù)數(shù)目和當(dāng)前數(shù)據(jù)庫(kù)中最大的ID。 int num = m_pRecord->GetCollect("num"); int maxid; if (num != 0) { maxid = m_pRecord->GetCollect("maxid"); } else { maxid = 0; } strSQL.Format(_T("Select * from Images where ID = %d"), maxid); //下面向數(shù)據(jù)庫(kù)中插入圖片等。 //首先從數(shù)據(jù)庫(kù)中讀id最大的那條數(shù)據(jù),主要目的是為了將RecordSet初始化 m_pRecord.CreateInstance(__uuidof(Recordset));
上面這句一定要注意,因?yàn)樯弦淮伟岩恍?shù)據(jù)放入m_pRecord中,這一次再放的時(shí)候,要重新創(chuàng)建一次,否則數(shù)據(jù)格式要么不匹配,要么保留有上一次的數(shù)據(jù),定位困難。
m_pRecord->Open(strSQL.AllocSysString(), m_pConn.GetInterfacePtr(), adOpenDynamic, adLockOptimistic, adCmdText); //這是AddNew方法要求的 CString imagepath = _T("F:/200713454/20090326.bmp"); CString imagename = imagepath.Right(12); try { m_pRecord->AddNew(); //為記錄集添加新的一行,更新時(shí)就會(huì)把這條新紀(jì)錄放到數(shù)據(jù)庫(kù)中 } catch (_com_error e) { AfxMessageBox(_T("不能插入一條新的記錄")); return; } try { //使用putcollect插入非圖像數(shù)據(jù),使用SetImage2DB插入圖像數(shù)據(jù) m_pRecord->PutCollect("ID", _variant_t(maxid+1)); m_pRecord->PutCollect("Name", _variant_t(imagename)); SetImage2DB(imagepath); } catch (_com_error e) { AfxMessageBox(_T("插入圖片有異常")); return; } m_pRecord->Update(); //使用完畢,關(guān)閉m_pRecord,并設(shè)置為NULL,最后關(guān)閉數(shù)據(jù)庫(kù)連接 m_pRecord->Close(); m_pRecord = NULL; CloseConnection();
四、讀取圖片并存儲(chǔ)到本地計(jì)算機(jī)
要將數(shù)據(jù)庫(kù)中的二進(jìn)制數(shù)據(jù)變?yōu)閳D片,最簡(jiǎn)單的方法就是用GDI+。GDI+有一個(gè)類(lèi)是Image,可以用stream來(lái)創(chuàng)建對(duì)象,還可以用Save方法保存到本地,所以這個(gè)類(lèi)很符合需要。
要使用GDI+,需要做些設(shè)置。首先在VS2005的項(xiàng)目屬性中,加上gdiplus.lib。
然后在stdafx.h中添加代碼
#include <GdiPlus.h> using namespace Gdiplus;
在CApp類(lèi)添加兩個(gè)變量:
GdiplusStartupInput m_gdiplusstartUpInput; ULONG_PTR m_GdiplusToken;
在CApp的InitInstance函數(shù)中添加
GdiplusStartup(&m_GdiplusToken, &m_gdiplusstartUpInput, NULL);
在ExitInstance函數(shù)中添加
GdiplusShutdown(m_GdiplusToken);
以下是讀取圖片數(shù)據(jù)并保存到本地的代碼實(shí)現(xiàn):
OpenConnection(); m_pRecord.CreateInstance(__uuidof(Recordset)); CString strSQL; strSQL.Format(_T("Select * from Images where ID = 1")); try { m_pRecord->Open(strSQL.AllocSysString(), m_pConn.GetInterfacePtr(), adOpenDynamic, adLockOptimistic, adCmdText); } catch (_com_error e) { AfxMessageBox(_T("讀取圖片信息異常")); return; } LPVOID Data; char* pbuf = NULL; long lDatasize = m_pRecord->GetFields()->GetItem("ImageData")->ActualSize; //數(shù)據(jù)庫(kù)中圖像數(shù)據(jù)長(zhǎng)度 CString imagename = m_pRecord->GetCollect("Name").bstrVal; if (lDatasize > 0) { _variant_t varBLOB; varBLOB = m_pRecord->GetFields()->GetItem("ImageData")->GetChunk(lDatasize); Data = new char[lDatasize+1]; if (varBLOB.vt == (VT_ARRAY|VT_UI1)) { SafeArrayAccessData(varBLOB.parray, (void **)&pbuf); memcpy(Data, pbuf, lDatasize); SafeArrayUnaccessData(varBLOB.parray); } } IStream* pStm; LONGLONG cb = lDatasize; HGLOBAL hGlobal = GlobalAlloc(GMEM_MOVEABLE, cb); LPVOID pvData; if (hGlobal != NULL) { pvData = GlobalLock(hGlobal); memcpy(pvData, Data, cb); GlobalUnlock(hGlobal); CreateStreamOnHGlobal(hGlobal, TRUE, &pStm); } else { AfxMessageBox(_T("Error")); return; } CLSID encoderClsid; GetEncoderClsid(L"image/bmp",&encoderClsid); //確定編碼格式是bmp格式 Image image(pStm, TRUE); CString imagepath; imagepath.Format(_T("F:/200713454/%s"), imagename); image.Save(imagepath, &encoderClsid, NULL); //把image中的數(shù)據(jù)按照bmp編碼格式存到本地 m_pRecord->Close(); m_pRecord = NULL; CloseConnection();
上面存儲(chǔ)和讀取數(shù)據(jù)的代碼中用到了兩個(gè)函數(shù),GetEncoderClsid和SetImage2DB。它們的實(shí)現(xiàn)如下:
這個(gè)函數(shù)和上面的存/取函數(shù)都是一個(gè)類(lèi)的成員函數(shù),而m_pConn和m_pRecord是這個(gè)類(lèi)的成員變量,所以
void CDlgTest::SetImage2DB(CString path) { VARIANT varChunk; SAFEARRAY* psa; SAFEARRAYBOUND rgsabound[1]; CFile f(path.operator LPCTSTR(),CFile::modeRead); BYTE bval[ChunkSize+1]; long uIsRead=0; while (1) { uIsRead=f.Read(bval,ChunkSize); if (uIsRead==0) break; rgsabound[0].cElements=uIsRead; rgsabound[0].lLbound=0; psa=SafeArrayCreate(VT_UI1,1,rgsabound); for (long index=0;index<uIsRead;index++) { if (FAILED(SafeArrayPutElement(psa,&index,&bval[index]))) AfxMessageBox(_T("錯(cuò)誤。")); } varChunk.vt =VT_ARRAY|VT_UI1; varChunk.parray=psa; try { m_pRecord->Fields->GetItem("ImageData")->AppendChunk(varChunk); } catch (_com_error e) { AfxMessageBox(_T("錯(cuò)誤。")); } ::VariantClear(&varChunk); ::SafeArrayDestroyData(psa); if (uIsRead<ChunkSize)break; } f.Close(); } INT CDlgTest::GetEncoderClsid(const WCHAR* format, CLSID* pClsid) { UINT num = 0; // number of image encoders UINT size = 0; // size of the image encoder array in bytes ImageCodecInfo* pImageCodecInfo = NULL; GetImageEncodersSize(&num, &size); if(size == 0) return -1; // Failure pImageCodecInfo = (ImageCodecInfo*)(malloc(size)); if(pImageCodecInfo == NULL) return -1; // Failure GetImageEncoders(num, size, pImageCodecInfo); for(UINT j = 0; j < num; ++j) { if( wcscmp(pImageCodecInfo[j].MimeType, format) == 0 ) { *pClsid = pImageCodecInfo[j].Clsid; free(pImageCodecInfo); return j; // Success } } free(pImageCodecInfo); return -1; // Failure }
至此就實(shí)現(xiàn)了存儲(chǔ)圖片和從數(shù)據(jù)庫(kù)中把圖片下載到本地的功能。
相關(guān)文章
C語(yǔ)言實(shí)現(xiàn)學(xué)生學(xué)籍管理系統(tǒng)程序設(shè)計(jì)
這篇文章主要為大家詳細(xì)介紹了C語(yǔ)言實(shí)現(xiàn)學(xué)生學(xué)籍管理系統(tǒng)程序設(shè)計(jì),文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-07-07C++實(shí)現(xiàn)LeetCode(18.四數(shù)之和)
這篇文章主要介紹了C++實(shí)現(xiàn)LeetCode(18.四數(shù)之和),本篇文章通過(guò)簡(jiǎn)要的案例,講解了該項(xiàng)技術(shù)的了解與使用,以下就是詳細(xì)內(nèi)容,需要的朋友可以參考下2021-07-07C++中typedef 及其與struct的結(jié)合使用
這篇文章主要介紹了C++中typedef 及其與struct的結(jié)合使用,需要的朋友可以參考下2014-02-02opencv實(shí)現(xiàn)定時(shí)錄像功能
這篇文章主要為大家詳細(xì)介紹了opencv實(shí)現(xiàn)定時(shí)錄像功能,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-06-06深入解析Radix Sort基數(shù)排序算法思想及C語(yǔ)言實(shí)現(xiàn)示例
基數(shù)排序和桶排序、計(jì)數(shù)排序共同是三種最常用的線(xiàn)性排序算法,這里我們就來(lái)深入解析Radix Sort基數(shù)排序算法思想及C語(yǔ)言實(shí)現(xiàn)示例,需要的朋友可以參考下2016-07-07使用C語(yǔ)言訪(fǎng)問(wèn)51單片機(jī)中存儲(chǔ)器的核心代碼
這篇文章主要介紹了使用C語(yǔ)言訪(fǎng)問(wèn)51單片機(jī)中存儲(chǔ)器的相關(guān)知識(shí),本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2022-01-01詳解C語(yǔ)言編程中的函數(shù)指針以及函數(shù)回調(diào)
這篇文章主要介紹了C語(yǔ)言編程中的函數(shù)指針以及函數(shù)回調(diào),函數(shù)回調(diào)實(shí)際上就是讓函數(shù)指針作函數(shù)參數(shù)、調(diào)用時(shí)傳入函數(shù)地址,需要的朋友可以參考下2016-04-04