用VC实现对超长数据库字段的操作

2010-08-28 10:49:52来源:西部e网作者:

    数据库在实际的开发过程中常常需要存储较大的二进制数据对象,如图像、音频文件、视频文件或其他二进制数据,这些数据称之为二进制大对象BLOB(Binary Large Object),其存取方式与普通数据有所区别。在数据库的表中,BLOB实际上是以二进制数据的形式存放的。由于BLOB的特殊性,一般的程序都无法处理它。比如,如果在一张表中存在BLOB,当用access或Database DeskTop(Delphi在带的数据库管理工具)等打开它时,BLOB列将只显示BLOB字样。至于该列中实际存的是什么数据单靠access(Database DeskTop)是无法得到的。如果在我们编制的程序中,用控件打开一个有BLOB字段的表,效果也一样。我们的程序无法直接显示、编辑以及插入BLOB字段。可见,如何处理这种不能直接显示的二进制形式数据,用常规的方法是不能满足要求的。

  结合笔者开发的模型库管理系统,本文以Visual C++ 6.0和access 2003环境下的数据库为例,介绍利用ADO实现对超长数据库字段的访问,包括写入和读出。

  设计数据库

  使用Access2003作为数据库系统,数据库名为blob,唯一的一个数据表为blob,如图1所示。

  \
  图1

  其中包括四个字段,分别是id(文本),name(文本),data(OLE对象)和suffix(后缀名),其字段类型必须是OLE对象。id作为主键,name是该blob文件的文件名,data字段用来保存二进制大对象,suffix是二进制文件的后缀名,可以是rm,avi,bmp,mp3等。

  系统的实现:

  1、SafeArray:

  在对BLOB进行操作时,要用到SAFEARRAY结构。SAFEARRAY是一种结构化的数据类型,包含了一个由其它数据类型的数据元素组成的数组。之所以称之为安全的数组是因为它包含了每一维的边界信息,并限制在边界内进行数组元素的访问。其Win32定义SAFEARRAY如下:

typedef struct tagSAFEARRAY
{
 unsigned short cDims;
 unsigned short fFeatures;
 unsigned long cbElements;
 unsigned long cLocks;
 void * pvData;
 SAFEARRAYBOUND rgsabound[ 1 ];
} SAFEARRAY;

  这个结构的成员(cDims,cLocks等)是通过API函数来设置和管理的。真正的数据存放在pvData成员中,而SAFEARRAYBOUND结构定义该数组结构的细节。以下就是该结构成员的简要描述:

成员 描述
cDims 数组的维数
fFeatures 用来描述数组如何分配和如何被释放的标志
cbElements 数组元素的大小
cLocks 一个计数器,用来跟踪该数组被锁定的次数
pvData 指向数据缓冲的指针
rgsabound 描述数组每维的数组结构,其大小是可变的

  rgsabound是一个有趣的成员,它的结构不太直观。它是数据范围的数组。该数组的大小依safe array维数的不同而有所区别。rgsabound成员是一个SAFEARRAYBOUND结构的数组--每个元素代表SAFEARRAY的一个维。

typedef struct tagSAFEARRAYBOUND
{
 unsigned long cElements;
 unsigned long lLbound;
} SAFEARRAYBOUND;


  维数被定义在cDims成员中。例如,一个'C'类数组的维数可以是[3][4][5]-一个三维的数组。如果我们使用一个SAFEARRAY来表示这个结构,我们定义一个有三个元素的rgsabound数组--一个代表一维。cDims = 3; SAFEARRAYBOUND rgsabound[ 3 ]; rgsabound[0]元素定义第一维。在这个例子中ILBOUND元素为0,是数组的下界。cElements成员的值等于三。数组的第二维([4])可以被rgsabound结构的第二个元素定义。下界也可以是0,元素的个数是4,第三维也是这样。


    2、将二进制文件写入到数据库:

  由于这个二进制文件可能很大,所以无法将所有内容一次性读入到内存。我们需要多次读入,每次可以使用函数CFile::Read()从文件中读出一个数据包,然后调用Field对象的AppandChrnk()函数将该包读入数据库。AppandChrnk()函数包含在Field对象中,原型如下:HRESULT AppendChunk (const _variant_t & Data );从函数原型中可以看到关键的问题是我们需把二进制数据赋值给VARIANT类型的变量。实现的关键代码如下:

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); ///创建SAFEARRAY对象
 for(long index=0;index<UISREAD;INDEX++)
  SafeArrayPutElement(psa,&index,&bVal[index]);
  varChunk.vt = VT_ARRAY|VT_UI1;
  varChunk.parray = psa;
  //加入BLOB类型的数据
  m_pRecordset->Fields->GetItem("data")->AppendChunk(varChunk);
  ::VariantClear(&varChunk);
  ::SafeArrayDestroyData( psa);
  if(uIsRead<<CHUNKSIZE) break;
}

  我们所有的读入数据工作都在一个while循环中实现,每次读入一个数据包,直到读完这个数据,即数据量为二进制数据的长度ChunkSize。其中*pBuf为指向缓冲区指针,即要读入的数据包。ChunkSize为VARIANT变量,用于保存二进制数据,psa是指向安全数组SAFEARRAY的指针。

  3、从数据库读出二进制对象到文件:

  同样,由于这个二进制对象可能很大,无法将所有内容一次性读入内存中对应于保存数据时我们所使用的AppendChunk函数,读取数据应该使用GetChunk函数,GetChunk的原型如下:

  _variant_t GetChunk (long Length );唯一的参数Length代表需要读取的字节数。

  实现的关键代码如下:

long lBlobSize = m_pRecordset->Fields-> Item["data"] ->ActualSize;
while(lBlobSize>0)
{
 lIsRead= lBlobSize >=ChunkSize? ChunkSize: lBlobSize;
 //从字段data中获取一个数据包
 varChunk = m_pRecordset->Fields->Item["data"]->GetChunk(lIsRead);
 for(index=0;index<LISREAD;INDEX++)
  ::SafeArrayGetElement(varChunk.parray,&index,buf+index);
  //将数据包写入文件
  f.Write(buf,lIsRead);
  lBlobSize -=lIsRead;
}

  其中f是一个CFile对象,代表了要存储的文件。Long型变量lBlobSize记录了二进制对象的大小。利用一个while循环,每次从数据库中读取lIsRead字节,直至将其全部读出。

  结束语

  由于信息技术的飞速发展,特别是多媒体技术的广泛运用,可以预见,以后数据库中大对象的应用将日益普遍,有关大对象的存取势必将是数据库技术的一个发展方向。本文讨论了二进制大对象BLOB在数据库中的存储与读取的VC实现,能够满足基本的系统要求,具备了一定的可移植性,为今后更广泛的应用和更深入的研究提供的借鉴基础。

关键词:VC

赞助商链接: