在我的应用程序中显示 Microsoft Access“OLE 对象”
Posted
技术标签:
【中文标题】在我的应用程序中显示 Microsoft Access“OLE 对象”【英文标题】:Display Microsoft Access "OLE Object" in my application 【发布时间】:2010-11-17 15:38:48 【问题描述】:我有一个包含 OLE 对象字段的 Access 数据库。我需要将此字段的内容提取为图像。最初放置在 OLE 字段中的类型无关紧要。我只需要一张代表该对象外观的图像。
这样做的主要目标是从 OLE 对象字段转移到存储在 blob 字段中的更标准的图像。
我找到了一些解析 blob 并尝试提取底层文件的示例代码。我真的在寻找按预期使用 OLE 对象的东西,而不是试图解决它们。
***上有两个类似的问题:Converting an OLE Image Object from MS Access for use in .NETExtract OLE Object (pdf) from Access DB
我打开这个问题主要是为了发布我当前的 Delphi 代码,看看是否有比我当前的代码更好的方法,如果没有,则帮助其他人。
我使用的是 Delphi 2007,但任何语言的代码都会有所帮助。
【问题讨论】:
【参考方案1】:以下是我正在使用的解决方案。这个起点来自Ms Access Ole Fields on the about.com site。
一些注意事项:
Access 似乎使用 OLE1 标准而不是 OLE2 存储对象。除了上面提到的帖子之外,我在任何地方都找不到任何确认。 Access 在 OLE1 字段前面附加它自己的标头。同样,我没有找到除帖子之外的任何文档。 我找不到直接使用 OLE1 对象的方法,我需要将其转换为 OLE2 对象。为了做到这一点,我需要 OLE1 流,它似乎没有在任何地方的 VCL 或 Win API 中实现。实现直接取自帖子,我不能说我理解它所做的一切。一旦您拥有一个 IOLEObject,您就可以使用 OleDraw 函数在您自己的画布上绘图。那时你有一个图像并且正在关闭并运行。如果您只想显示对象,您可以使用 TOLEContainer 组件并让它处理绘图。
这是使用该单位的代码。我有一个 ADO 表,其中包含一个名为 AdoTable1Photo 的 TBlobField 类型的字段对象。
procedure TForm2.ADOTable1AfterScroll(DataSet: TDataSet);
var
Bmp: TBitmap;
Jpg: TJpegImage;
OleObject: IOleObject;
DataObject: IDataObject;
CreateInfo: TCreateInfo;
begin
if AdoTable1Photo.BlobSize = 0 then
exit;
OleObject := OleFieldToObject(AdoTable1Photo);
// If you want to save out an image file draw let the ole object draw to
// a bitmap canvas and then save the results. Could be used for converting
// a field to a true image blob instead of a OLE Object type.
Bmp := TBitmap.Create();
Jpg := TJpegImage.Create();
try
DrawOleOnBmp(OleObject, Bmp);
Jpg.Assign(Bmp);
Jpg.SaveToFile('C:\temp\test.jpg');
finally
Bmp.Free;
Jpg.Free;
end;
// If just trying to display the results without converting you can attach
// the OleObject to a OleContainer component that will handle the drawing.
// I could not find an easy way to do this directly. I needed to use the
// IDataObject interace with the CreateInfo record.
if Succeeded(OleObject.QueryInterface(IDataObject, DataObject)) then
begin
// Load the OLE Container control by using the IDataObject
CreateInfo.CreateType := ctFromData;
CreateInfo.ShowAsIcon := false;
CreateInfo.DataObject := DataObject;
OleContainer.CreateObjectFromInfo(CreateInfo);
end;
OleObject := nil;
end;
这里是将字段转换为 IOleObject 的单元。困难的部分是提取流拆分出标头并将其转换为 OLE2 对象。完成后,我实际上只使用了两个函数:OLELoad 和 OLEDraw。
unit MSAccessOleObject;
interface
uses ActiveX, Windows, Classes, ComObj, DB, Graphics;
// This file is a modified version of the source code posted here:
// http://forums.about.com/ab-delphi/messages?lgnF=y&msg=1865.1
//-----------------------------------------------------------------------------
// Converted from Ole.h
//
// Used inside from OleConvertOLESTREAMToIStorage OLE Function
// As far I know the Access Converts the OLE2 objects in OLE1 Objects
//
// So for read this Kind of field we must covert the OLE1 format
// to OLE2 format so we need the OleConvertOLESTREAMToIStorage
// and to write an OLE object to Field we need the
// OleConvertIStorageToOLESTREAM OLE function.
// The code here is only for reading "Ole Object" fields, but it coudld adapted
// to write them as well.
//
// OLE.h define a OLE stream that uses a vtable and callback functions. I
// could not find a class in the VCL that implemented the OLE v1 Stream.
type
POleStreamVtbl = ^TOleStreamVtbl;
TOleStreamVtbl = record
Get: Pointer;
Put: Pointer;
end;
POle1Stream = ^TOle1Stream;
TOle1Stream = record
pvt: POleStreamVtbl;
lpData: Pointer; // Link to Data in .MDB file
dwSize: Integer; // OLE Stream length (relative to position)
end;
POleStream = ^TPOleStream;
TPOleStream = record
lpstbl: POleStreamVtbl;
end;
//-----------------------------------------------------------------------------
// Microsoft Access Field Header
//
// Access adds header information in front of the actual OLE stream.
// We need to read it to get the size in order to find the start of
// the actual OLE stream.
type
TKind=record
case Integer of
0: (oot: DWord); // OLE Object type code (OT_LINK, OT_EMBEDDED, OT_STATIC)
1: (lobjTyp: LongInt); // in our case: OT_EMBEDDED
end;
PAccessOleObjectHeader=^TAccessOleObjectHeader;
TAccessOleObjectHeader = record
typ: WORD; // Type signature (0x1C15)
cbHdr: WORD; // sizeof(struct OLEOBJECTHEADER) + cchName +cchClass
lobjType: TKind; // OLE Object Type Code (OT_STATIC, OT_LINKED,OT_EMBEDDED)
cchName: WORD; // Count of characters in object Name (CchSz(szName) + 1))
cchClass: WORD; // Count of characters in class Name (CchSz(szClss) + 1))
ibName: WORD; // Offset of object name in structure (sizeof(OLEOBJECTHEADER)
ibClass: WORD; // Offset of class name in structure (ibName +cchName)
ptSize: TSmallPoint; // Original size of Object (MM_HIMETRIC)
end;
function CreateOle1Stream(pStm: IStream; dwSize: Integer): POle1Stream;
procedure DeleteOle1Stream(var Ole1Stream: POle1Stream);
// Callback Functions for OLE1 Stream
function Get(OleStream: POLESTREAM; Pb:Pointer; cb:Integer): Integer; stdcall;
function Put(OleStream: POLESTREAM; const Pb:Pointer; cb:Integer): Integer; stdcall;
function OleFieldToObject(AdoField: TBlobField): IOleObject;
procedure DrawOleOnBmp(Ole: IOleObject; Bmp: TBitmap);
implementation
uses Sysutils;
CreateOle1Stream
---------------------------------------------------------------------------
function CreateOle1Stream(pStm:IStream; dwSize:Integer): POLE1Stream;
var
cb: Int64;
begin
Result := new(POle1Stream);
Result.pvt := new(POleStreamVtbl);
Result.pvt.Get := @Get;
Result.pvt.Put := @Put;
Result.dwSize := dwSize;
Result.lpData := Pointer(pStm);
// Seek to the start of the stream
IStream(Result.lpData).Seek(0,STREAM_SEEK_SET,cb);
end;
DeleteOle1Stream
---------------------------------------------------------------------------
procedure DeleteOle1Stream(var Ole1Stream: POle1Stream); // Dispose then OLE1 Stream
begin
if Ole1Stream = Nil then
exit;
Dispose(Ole1Stream^.pvt);
Dispose(Ole1Stream);
end;
Put
---------------------------------------------------------------------------
Callback function for Ole1Stream
function Put(OleStream: POLESTREAM; const Pb:Pointer; cb:Integer): Integer; stdcall;
Var
pStream: POle1Stream;
ulBytesWritten: longInt;
begin
pStream:=POle1Stream(OleStream);
if (pStream = Nil) or (pStream^.lpData=Nil) or (pb=Nil) then
begin
Result:=0;
exit;
end;
ulBytesWritten:=0;
if IStream(pStream^.lpData).Write(Pb,cb,@ulBytesWritten) <> S_OK then
begin
Result:=0;
exit;
end;
pStream^.dwSize:=pStream^.dwSize+ulBytesWritten;
Result:=cb;
end;
Get
---------------------------------------------------------------------------
Callback function for Ole1Stream
function Get(OleStream: POLESTREAM; Pb: Pointer; cb: Integer): Integer; stdcall;
Var
pStream: POle1Stream;
ulBytesRead: LongInt;
begin
pStream := POle1Stream(OleStream);
if (pStream=Nil) or (pStream^.lpData=Nil) or (pStream^.dwSize < cb)
then
begin
Result := 0;
exit;
end;
ulBytesRead := 0;
if IStream(pStream^.lpData).Read(pb, cb, @ulBytesRead) <> S_OK then
begin
Result := 0;
exit;
end;
pStream^.dwSize := pStream^.dwSize-ulBytesRead;
Result := cb;
end;
OleFieldToObject
---------------------------------------------------------------------------
Pass in the ADO field of the "OLE Object" type and get the IOleObject
interface back. You can then attached that object to a OleContanier and
let it draw itself or use the DrawOleOnBmp function to get your own bitmap
that can be used by itself.
function OleFieldToObject(AdoField: TBlobField): IOleObject;
var
AccessHeader: TAccessOleObjectHeader;
FullAdoStream: TMemoryStream;
ObjectStream: TMemoryStream;
StreamAdapter: TStreamAdapter;
StreamInterface: IStream;
Ole1Stream: POle1Stream;
OleBytes: ILockBytes;
OleStorage: IStorage;
OleObject: IOleObject;
begin
FullAdoStream := nil;
ObjectStream := nil;
StreamAdapter := nil;
StreamInterface := nil;
Ole1Stream := nil;
try
// We need a IStorage Interface that will be used to load the OLEObject
OleCheck( CreateILockBytesOnHGlobal(0, true, OleBytes) );
OleCheck( StgCreateDocfileOnILockBytes(OleBytes,
STGM_Create or STGM_READWRITE or STGM_SHARE_EXCLUSIVE or STGM_DIRECT, 0, OleStorage) );
// We need to get the data out of the field. Microsoft Access stores a OLE1 field
// and not the current OLE2 format. It also adds it's own header on the object.
// We need to - extract the stream, remove the header, wrap the stream in an
// IStreamInterface, wrap that inside and OLE1Stream object.
FullAdoStream := TMemoryStream.Create();
AdoField.SaveToStream(FullAdoStream);
FullAdoStream.Seek(0, soBeginning);
FullAdoStream.ReadBuffer(AccessHeader, sizeof(TAccessOleObjectHeader));
// We could check if AccessHeader.typ = $1C15 but if the format is not
// right something will go wrong later.
// Seek past the header and then copy the rest of the stream to a new stream.
FullAdoStream.Seek(AccessHeader.cbHdr, soBeginning);
ObjectStream := TMemoryStream.Create();
ObjectStream.CopyFrom(FullAdoStream, FullAdoStream.Size - FullAdoStream.Position);
StreamAdapter := TStreamAdapter.Create(ObjectStream, soReference);
StreamInterface := StreamAdapter as IStream;
Ole1Stream := CreateOle1Stream(StreamInterface, ObjectStream.Size);
// Now convert the OLE1 stream to OLE2 (IStorage) and load the IOleObject
// This function seems to be slow, but I can't find anything to change
// or any other function to use.
OleCheck( OleConvertOLESTREAMToIStorage(Ole1Stream, OleStorage, Nil) );
OleCheck( OleLoad(OleStorage, IOleObject, nil, OleObject) );
finally
DeleteOle1Stream(Ole1Stream);
StreamInterface := nil;
StreamAdapter := nil;
ObjectStream.Free;
FullAdoStream.Free;
end;
result := OleObject;
end;
DrawOleOnBmp
---------------------------------------------------------------------------
Take a OleObject and draw it to a bitmap canvas. The bitmap will be sized
to match the normal size of the OLE Object.
procedure DrawOleOnBmp(Ole: IOleObject; Bmp: TBitmap);
var
ViewObject2: IViewObject2;
ViewSize: TPoint;
AdjustedSize: TPoint;
DC: HDC;
R: TRect;
begin
if Succeeded(Ole.QueryInterface(IViewObject2, ViewObject2)) then
begin
ViewObject2.GetExtent(DVASPECT_CONTENT, -1, nil, ViewSize);
DC := GetDC(0);
AdjustedSize.X := MulDiv(ViewSize.X, GetDeviceCaps(DC, LOGPIXELSX), 2540);
AdjustedSize.Y := MulDiv(ViewSize.Y, GetDeviceCaps(DC, LOGPIXELSY), 2540);
ReleaseDC(0, DC);
Bmp.Height := AdjustedSize.Y;
Bmp.Width := AdjustedSize.X;
SetRect(R, 0, 0, Bmp.Width, Bmp.Height);
OleDraw(Ole, DVASPECT_CONTENT, Bmp.Canvas.Handle, R);
end
else
begin
raise Exception.Create('Could not get the IViewObject2 interfact on the OleObject');
end;
end;
end.
【讨论】:
以上是关于在我的应用程序中显示 Microsoft Access“OLE 对象”的主要内容,如果未能解决你的问题,请参考以下文章
Microsoft Edge WebView2 控件可以在显示之前预加载页面吗?
在我的 Microsoft Lync 视频聊天应用程序中出现 COMException
就像在我的 Mac OSX Cocoa 应用程序中在 Microsoft Word 中一样创建字体菜单
在 WKWebView 中加载 Microsoft Office 文档