在本文中,将探讨一个扩展库,它允许OLE DB消费者应用程序与数据库交换图像和文本BLOB数据。这个扩展库名为blobddx.h
,它扩展了atlddx.h
的功能,使得应用程序能够通过流的方式读取和写入MS SQL Server的文本字段和MS Access的备忘录字段。此外,它还支持读取和写入非OLE图像BLOB,并且为Northwind数据库中的OLE对象位图图像提供只读支持。
有两个示例项目可供下载。第一个是一个标准的WTL对话框应用程序,它使用编辑控件和静态图片控件来显示数据。它通过OLE DB for SQL Server提供者从MS SQL Server的"Pubs"数据库获取数据。要使用此示例,必须能够访问SQL Server。
第二个示例通过OLE DBfor Jet 4.0提供者从MS Access的"Northwind"数据库获取数据。由于这些图像是OLE对象,因此它们是只读的。必须拥有Microsoft Office 97或2000示例目录中的Northwind.mdb文件。
blobddx.h
包含了扩展atlddx.h
的源代码,通过提供文本和图像BLOB的DDX(动态数据交换)来实现。它包含以下宏/方法:
DDX_IMAGE
- 支持从SQL Server图像列中交换位图、gif和jpg图像,以便在静态图片控件中显示。它还为MS Access OLE对象位图图像提供只读支持。DDX_EDIT
- 在编辑控件和SQL Server文本列或MS Access备忘录列之间交换最多64K的文本BLOB。DDX_RICHEDIT
- 支持与RichEdit控件交换最多2GB的文本BLOB。当Unicode标志设置为TRUE
时,DDX_EDIT
和DDX_RICHEDIT
都提供有限的Unicode支持。要使用Unicode与DDX_RICHEDIT
,需要RichEdit 2.0控件。
可以通过手动编辑.rc文件来为VC6对话框应用程序启用RichEdit 2.0。将资源编辑器添加的任何RichEdit控件的"RICHEDIT"更改为"RichEdit20a"。此外,还必须在stdafx.h
中将RICHEDIT_VER
定义更改为0x0200。
以下是SQL Server示例项目的Oledb2View.h
中的DDX_MAP
,它显示了DDX_IMAGE
宏所需的语法。
BEGIN_DDX_MAP(COledb2View)
DDX_TEXT(IDC_PUBID, m_pub.m_pubid)
DDX_TEXT(IDC_PRINFO, m_pub.m_prinfo)
DDX_IMAGE(IDC_LOGO, m_pub.m_logo, m_pub.m_logoLength, m_pub.m_logoStatus)
END_DDX_MAP()
在这里,pr_info
列被定义为SQL Server文本列。它实际上是一个BLOB,但ATL OLE DB向导将其定义为TCHAR [1024]
。由于实际数据比这更长,示例项目中的变量被更改为TCHAR [65536]
。理论上,使用SQL Server 7或更高版本,可以定义两个BLOB流,一个用于文本BLOB,一个用于图像BLOB。
要在RichEdit 2.0控件中支持Unicode,并且使用pr_info
列,需要进行以下更改(当然,列数据类型需要更改为ISequentialStream*
,并且需要定义长度和状态变量):
// 旧的DDX条目
DDX_TEXT(IDC_PRINFO, m_pub.m_prinfo)
// 替换为
DDX_RICHEDIT(IDC_PRINFO, m_pub.m_prinfo, m_pub.m_prinfoLength,
m_pub.m_prinfoStatus, TRUE)
访问器是消费者定义的数据结构(在示例项目的pubinfo.h
中定义),它描述了数据存储中的行或参数数据如何在消费者的数据缓冲区中布局。提供者使用此访问器来确定如何将数据传输到消费者和从消费者传输数据。
OLE DB消费者向导创建了一个COLUMN_MAP
(ACCESSOR_MAP
的单个访问器版本),它包含了选定表的所有列。在某些情况下,COLUMN_MAP
就足够了,例如对于只读或非BLOB数据,特别是与Jet 4.0一起使用。
然而,消费者可能需要多个访问器。例如,以下ACCESSOR_MAP
来自SQL Server示例项目的pubinfo.h
,包含了访问两个文本列数据的访问器0,以及访问图像BLOB数据的访问器1。此访问器映射用于从SQL Server读取和写入Pubs.pub_info
表。
BEGIN_ACCESSOR_MAP(CPubInfoAccessor, 2)
BEGIN_ACCESSOR(0, true)
COLUMN_ENTRY(1, m_pubid)
COLUMN_ENTRY(3, m_prinfo)
END_ACCESSOR()
BEGIN_ACCESSOR(1, false)
BLOB_ENTRY_LENGTH_STATUS(2, IID_ISequentialStream, STGM_READ, m_logo,
m_logoLength, m_logoStatus)
END_ACCESSOR()
END_ACCESSOR_MAP()
注意对Logo列使用BLOB_ENTRY_LENGTH_STATUS
宏。这个宏使用ISequentialStream读取第2列,并提供数据、数据长度和列状态。在atldbcli.h
中定义了几个列宏,但这个宏是从MSDN示例AOTBLOB复制到blobddx.h
中的。
Blob DDX使用一个IPicture
对象来保存图像。IPicture
可以处理包括位图、gif和jpeg在内的几种图像格式。Blob DDX使用各种流和缓冲区在数据库和IPicture
之间移动图像数据,并使用静态图片控件在对话框上显示图像。
以下代码片段显示了支持DDX_IMAGE
的方法的读取代码。唯一的棘手部分是OLE DB提供者是一个ISequentialStream
,而IPicture
想要一个IStream
。因此,使用了中间缓冲区。
BOOL DDX_Image(UINT nID, ISequentialStream* &pStream, BOOL bSave,
ULONG& ulLength, ULONG& ulStatus)
{
T* pT = static_cast(this);
if (!bSave)
{
IPicture* pIPicture;
IStream* pIStream = NULL;
void* buffer = ::CoTaskMemAlloc(ulLength);
// 读取提供者提供的ISequentialStream
if (pStream->Read(buffer, ulLength, NULL) == S_OK)
{
// 在缓冲区上创建一个IStream。TRUE意味着流将自动释放
::CreateStreamOnHGlobal(buffer, TRUE, &pIStream);
// 从IStream加载IPicture
if (::OleLoadPicture(pIStream, 0, FALSE, IID_IPicture,
(LPVOID*)&pIPicture) == S_OK)
{
// 在静态控件中显示图像
OLE_HANDLE handle;
pIPicture->get_Handle(&handle);
CStatic(pT->GetDlgItem(nID)).SetBitmap((HBITMAP)handle);
}
// 释放数据库流
pStream->Release();
}
::CoTaskMemFree(buffer);
}
}
Blob DDX还提供了辅助方法来加载和保存磁盘文件图像。读取由LoadImageFromFile()
方法处理,写入由SaveImageToFile()
处理。两种方法都启动标准文件对话框以提示用户输入文件名。加载文件中的图像后,它被放置在IPicture
对象中,然后可以随后保存到数据库中(请记住,不支持保存OLE对象)。
blobddx.h
版本可能允许写入。SaveImageToFile()
仅以位图格式保存。(Unisys LZW专利问题,不是技术问题)OleSavePictureFile()
,在SaveImageToFile()
中使用,可能在处理大图像(> 5MB)时崩溃。OleLoadPicturePath()
,在LoadImageFromFile()
中使用,可能分配资源但不释放。DDX_EDIT
的Unicode支持是从Unicode数据源(如MS Access备忘录字段)转换为ANSI以在ANSI编辑控件中显示的角度来看的。它可能不适用于Unicode编辑控件。一些信息来源于Microsoft知识库文章Q190958 "AOTBLOB使用OLE DB消费者模板读取/写入BLOB"。两个宏被复制,并且ISSHelper类从MSDN示例AOTBLOB移植过来。