C++使用ADO实现存取图片的方法
一般在网上查到的资料中向Server2000存储图片代码比较多,从数据库中读取图片并显示也不少,但是把图片从数据库中二进制数据转换为原图片保存在本地,就很少有C++代码了。本文就此问题一步一步地讲一讲解决的方法:
一、使用数据库前的准备
我们使用ADO,是用_ConnectionPtr,_RecordsetPtr来操纵数据库的。还有一个_CommandPtr,本程序没有使用它。
为了使用ADO,需要导入ADO动态链接库。在工程的stdafx.h文件中,添加如下代码:
//导入ADO #import"C:\ProgramFiles\CommonFiles\System\ado\msado15.dll"\ rename_namespace("ADOCG")rename("EOF","EndOfFile") usingnamespaceADOCG;
这些代码声明,在这个工程中使用ADO但不使用ADO的名字空间,并且为了避免常数冲突,将常数EOF改名为adoEOF。
再有就是要建一个简单的数据库,名字叫TestImage,里面有一个表Images,这个表有三个字段,分别是ID,Name,ImageData。
二、连接数据库
连接数据库的代码可以放入一个函数中,在想调用的地方调用。一般不推荐在CAPP类的Initalize()里连接数据库,在退出程序时关闭数据库连接。应该是在使用时连接,使用完马上关闭。项目中m_pConn是_ConnectionPtr类型的变量。
BOOLOpenConnection() { if(m_pConn==NULL) { m_pConn.CreateInstance("ADODB.Connection");//创建_ConnectionPtr的一个实例 } try { if(adStateClosed==m_pConn->State)//如果已关闭 { m_pConn->Open("driver={SQLServer};Server=HP-CADD722B76A0;DATABASE=TestImage;UID=sa;PWD=sa","","",adModeUnknown);//因数据库而异 returntrue; } } catch(_com_errore) { AfxMessageBox(_T("连接数据库失败!")); returnfalse; } }
三、打开数据集,操纵数据库
在使用_RecordSetPtr对象m_pRecord时,必须先创建这种对象的一个实例:
m_pRecord.CreateInstance(__uuidof(RecordSet)); CStringstrSQL; //获取表中最大的id,下一次插入时就用id+1 strSQL.Format(_T("Selectcount(*)asnum,Max(ID)asmaxidfromImages")); try { m_pRecord->Open(strSQL.AllocSysString(),m_pConn.GetInterfacePtr(), adOpenDynamic,adLockUnspecified,adCmdText); } catch(_com_errore) { AfxMessageBox(_T("读取最大的id异常")); eturn; } //从RecordSet中获取数据数目和当前数据库中最大的ID。 intnum=m_pRecord->GetCollect("num"); intmaxid; if(num!=0) { maxid=m_pRecord->GetCollect("maxid"); } else { maxid=0; } strSQL.Format(_T("Select*fromImageswhereID=%d"),maxid); //下面向数据库中插入图片等。 //首先从数据库中读id最大的那条数据,主要目的是为了将RecordSet初始化 m_pRecord.CreateInstance(__uuidof(Recordset));
上面这句一定要注意,因为上一次把一些数据放入m_pRecord中,这一次再放的时候,要重新创建一次,否则数据格式要么不匹配,要么保留有上一次的数据,定位困难。
m_pRecord->Open(strSQL.AllocSysString(),m_pConn.GetInterfacePtr(), adOpenDynamic,adLockOptimistic,adCmdText);//这是AddNew方法要求的 CStringimagepath=_T("F:/200713454/20090326.bmp"); CStringimagename=imagepath.Right(12); try { m_pRecord->AddNew();//为记录集添加新的一行,更新时就会把这条新纪录放到数据库中 } catch(_com_errore) { AfxMessageBox(_T("不能插入一条新的记录")); return; } try { //使用putcollect插入非图像数据,使用SetImage2DB插入图像数据 m_pRecord->PutCollect("ID",_variant_t(maxid+1)); m_pRecord->PutCollect("Name",_variant_t(imagename)); SetImage2DB(imagepath); } catch(_com_errore) { AfxMessageBox(_T("插入图片有异常")); return; } m_pRecord->Update(); //使用完毕,关闭m_pRecord,并设置为NULL,最后关闭数据库连接 m_pRecord->Close(); m_pRecord=NULL; CloseConnection();
四、读取图片并存储到本地计算机
要将数据库中的二进制数据变为图片,最简单的方法就是用GDI+。GDI+有一个类是Image,可以用stream来创建对象,还可以用Save方法保存到本地,所以这个类很符合需要。
要使用GDI+,需要做些设置。首先在VS2005的项目属性中,加上gdiplus.lib。
然后在stdafx.h中添加代码
#include<GdiPlus.h> usingnamespaceGdiplus;
在CApp类添加两个变量:
GdiplusStartupInputm_gdiplusstartUpInput; ULONG_PTRm_GdiplusToken;
在CApp的InitInstance函数中添加
GdiplusStartup(&m_GdiplusToken,&m_gdiplusstartUpInput,NULL);
在ExitInstance函数中添加
GdiplusShutdown(m_GdiplusToken);
以下是读取图片数据并保存到本地的代码实现:
OpenConnection(); m_pRecord.CreateInstance(__uuidof(Recordset)); CStringstrSQL; strSQL.Format(_T("Select*fromImageswhereID=1")); try { m_pRecord->Open(strSQL.AllocSysString(),m_pConn.GetInterfacePtr(), adOpenDynamic,adLockOptimistic,adCmdText); } catch(_com_errore) { AfxMessageBox(_T("读取图片信息异常")); return; } LPVOIDData; char*pbuf=NULL; longlDatasize=m_pRecord->GetFields()->GetItem("ImageData")->ActualSize;//数据库中图像数据长度 CStringimagename=m_pRecord->GetCollect("Name").bstrVal; if(lDatasize>0) { _variant_tvarBLOB; varBLOB=m_pRecord->GetFields()->GetItem("ImageData")->GetChunk(lDatasize); Data=newchar[lDatasize+1]; if(varBLOB.vt==(VT_ARRAY|VT_UI1)) { SafeArrayAccessData(varBLOB.parray,(void**)&pbuf); memcpy(Data,pbuf,lDatasize); SafeArrayUnaccessData(varBLOB.parray); } } IStream*pStm; LONGLONGcb=lDatasize; HGLOBALhGlobal=GlobalAlloc(GMEM_MOVEABLE,cb); LPVOIDpvData; if(hGlobal!=NULL) { pvData=GlobalLock(hGlobal); memcpy(pvData,Data,cb); GlobalUnlock(hGlobal); CreateStreamOnHGlobal(hGlobal,TRUE,&pStm); } else { AfxMessageBox(_T("Error")); return; } CLSIDencoderClsid; GetEncoderClsid(L"image/bmp",&encoderClsid);//确定编码格式是bmp格式 Imageimage(pStm,TRUE); CStringimagepath; imagepath.Format(_T("F:/200713454/%s"),imagename); image.Save(imagepath,&encoderClsid,NULL);//把image中的数据按照bmp编码格式存到本地 m_pRecord->Close(); m_pRecord=NULL; CloseConnection();
上面存储和读取数据的代码中用到了两个函数,GetEncoderClsid和SetImage2DB。它们的实现如下:
这个函数和上面的存/取函数都是一个类的成员函数,而m_pConn和m_pRecord是这个类的成员变量,所以
voidCDlgTest::SetImage2DB(CStringpath) { VARIANTvarChunk; SAFEARRAY*psa; SAFEARRAYBOUNDrgsabound[1]; CFilef(path.operatorLPCTSTR(),CFile::modeRead); BYTEbval[ChunkSize+1]; longuIsRead=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(longindex=0;index<uIsRead;index++) { if(FAILED(SafeArrayPutElement(psa,&index,&bval[index]))) AfxMessageBox(_T("错误。")); } varChunk.vt=VT_ARRAY|VT_UI1; varChunk.parray=psa; try { m_pRecord->Fields->GetItem("ImageData")->AppendChunk(varChunk); } catch(_com_errore) { AfxMessageBox(_T("错误。")); } ::VariantClear(&varChunk); ::SafeArrayDestroyData(psa); if(uIsRead<ChunkSize)break; } f.Close(); } INTCDlgTest::GetEncoderClsid(constWCHAR*format,CLSID*pClsid) { UINTnum=0;//numberofimageencoders UINTsize=0;//sizeoftheimageencoderarrayinbytes 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(UINTj=0;j<num;++j) { if(wcscmp(pImageCodecInfo[j].MimeType,format)==0) { *pClsid=pImageCodecInfo[j].Clsid; free(pImageCodecInfo); returnj;//Success } } free(pImageCodecInfo); return-1;//Failure }
至此就实现了存储图片和从数据库中把图片下载到本地的功能。