Embedding OLE Objects Into an Access Database

This article illustrates how to programmatically embed OLE objects into Microsoft Access database.

  • 1. Write the header
  • 2. Write the name GetUserType( USERCLASSTYPE_SHORT )
  • 3. Write the class ProgIDFromCLSID
  • 4. Write the storage OleConvertIStorageToOLESTREAM

One remaining item is that before Access actually writes out the storage, it adds a stream to it that contains information that is lost in the process of OleConvertIStorageToOLESTREAM/OleConvertOLESTREAMToIStorage. However, if this stream isn't there, Access just uses the defaults so it isn't fatal.

This means that to read an object back in the following steps have to be taken:

  • 1. Read the header
  • 2. Read the name (ignore)
  • 3. Read the class CLSIDFromProgID
  • 4. Read the storage OleConvertOLESTREAMToIStorage

The sample attached uses the Insert OLE Object dialog.


DWORD dwObjectSize = 0;
DWORD dwCheckSum = 0;
int iHeaderSize = 0;
CLongBinaryDoc* pDoc = NULL;
CLongBinaryItem* pClone = NULL;
LPSTREAM lpObjStream = NULL;
LPOLE1STREAM lpOle1Stream = NULL;
LPOLECLIENTSITE lpClientSite = NULL;
HGLOBAL hgMemObject = NULL;
HGLOBAL hgAccessHeader = NULL;
HGLOBAL hgNewLongBinary = NULL;
LPBYTE lpbMemObject = NULL;
LPBYTE lpbAccessHeader = NULL;
LPBYTE lpbNewLongBinary= NULL;
int nIDLast = 1;
ULARGE_INTEGER ulStreamSize;


// allocate enough memory to write access header to
if( NULL == ( hgAccessHeader = GlobalAlloc(GMEM_MOVEABLE, sizeof( OLEOBJHDR ) + 100 )))
{//Processing fault}

// write the access-header
if( 0 == ( iHeaderSize = WriteAccessHeader(hgAccessHeader, m_pSelection->m_lpObject )))
{//Processing fault}

// adjust size of memory-block according to WriteAccessHeader()
if ( NULL == ( hgAccessHeader = GlobalReAlloc(hgAccessHeader, iHeaderSize, GMEM_MOVEABLE )))
{//Processing fault}
// Alloc the memory to store the long binary
if ( NULL == ( hgMemObject = GlobalAlloc(GMEM_MOVEABLE | GMEM_DDESHARE, 0 )))
{//Processing fault}
// create an IStream on top of the ILockBytes 
// (indicate automatic deletion of HGLOBAL on Release)
// IMPORTANT: This IStream* (and therewith the memory
// will be released during DeleteOleStream()
if ( FAILED( ::CreateStreamOnHGlobal( hgMemObject, TRUE, &lpObjStream )))
{//Processing fault}
// convert the IStream to an IStorage ( ACCESS stores OLE Objects the OLE1 way)
// in order to do so, we first have to wrap the OLE2 IStream into an OLE1 OLESTREAM
// see MSDN Article: "More on Using Storage Objects in OLE"
// NOTE: the Get() and Put() functions wrapped in the OLESTREAMVTBL will
// access the newly opened stream using an IStream* variable
if ( NULL == ( lpOle1Stream = CreateOle1Stream( lpObjStream, 0 )))
{//Processing fault}
// if the OLE object was not loaded from the DB, but rather
// created using CreateNewItem(), or if the item was modified
// after loading it from the DB, the m_lpStorage of the item
// will not contain the updated item information, since it wasn't
// yet saved to that IStorage.
// in order to assure that m_lpStorage points to the updated
// information, we force the item to save it's data.
// NOTE: GetClientSiteFromHere() will internally call GetClientSite()
// and GetInterface(). The MFC's implementation of GetInterface()
// however, doesn't AddRef() "lpClientSite", so we don't have to
// release it.
lpClientSite = m_pSelection->GetClientSiteFromHere();
if ( FAILED ( lpClientSite->SaveObject()))
{//Processing fault}
// convert the client-item's IStorage* to an OLE1 OLESTREAM
// NOTE: as soon as the stream gets converted, the data will be written
// to the IStream* and thereby to the HGLOBAL (see Put() function)
if ( FAILED ( ::OleConvertIStorageToOLESTREAM(
m_pSelection->m_lpStorage, ( LPOLESTREAM ) lpOle1Stream )))
{//Processing fault}
// adjust stream's size to reflect exactly the amount of bytes written to it
ulStreamSize.LowPart = lpOle1Stream->dwSize;
ulStreamSize.HighPart = 0;
if ( FAILED ( lpOle1Stream->lpStream->SetSize( ulStreamSize )))
{//Processing fault}
// allocate a new memory handle which is large enough to hold the
// Access header, the OLE1 OLESTREAM containing the object and the checksum
if ( NULL == ( hgNewLongBinary = GlobalAlloc( 
GHND, iHeaderSize + lpOle1Stream->dwSize + sizeof( DWORD ))))
{//Processing fault}
// and copy the complete information into it
if ( NULL == ( lpbAccessHeader = ( LPBYTE ) GlobalLock(hgAccessHeader )))
{//Processing fault}
if ( NULL == ( lpbMemObject = ( LPBYTE ) GlobalLock( hgMemObject)))
{//Processing fault}
if ( NULL == ( lpbNewLongBinary = ( LPBYTE ) GlobalLock( hgNewLongBinary )))
{//Processing fault}

memcpy( lpbNewLongBinary, lpbAccessHeader, iHeaderSize );
lpbNewLongBinary += iHeaderSize;
memcpy( lpbNewLongBinary, lpbMemObject, lpOle1Stream->dwSize );
lpbNewLongBinary += lpOle1Stream->dwSize;
memcpy( lpbNewLongBinary, &dwCheckSum, sizeof( DWORD ));
lpbNewLongBinary += sizeof( DWORD );
GlobalUnlock( hgMemObject );
GlobalUnlock( hgAccessHeader );
GlobalUnlock( hgNewLongBinary );

// make sure recordset is open and in edit mode
if( !m_pSet->IsOpen() )
m_pSet->Open();
m_pSet->Edit();

// throw away old memory and point the longbinary to the new memory
GlobalFree( m_pSet->m_OLE_Object.m_hData );
m_pSet->m_OLE_Object.m_hData = hgNewLongBinary;

// set the CLongBinary length
m_pSet->m_OLE_Object.m_dwDataLength = 
iHeaderSize + lpOle1Stream->dwSize + sizeof( DWORD );

// Update the datasource
m_pSet->SetFieldDirty( &m_pSet->m_OLE_Object, TRUE );
m_pSet->SetFieldNull( &m_pSet->m_OLE_Object, FALSE );
m_pSet->Update();
If given only file path you can use following lines of code

/////////////////////////////////////////////////////////////////////////////////////

COleDocument Doc;

HGLOBAL hgAccessHeader = NULL;

//To get the class header and implementation code download the source
CLongBinaryItem oci(&Doc);
CString strPath;

HGLOBAL hgAccessHeader = NULL; 
 ::GetTempPath(MAX_PATH,strPath.GetBuffer(MAX_PATH));

strPath.ReleaseBuffer();

strPath += _T("some.bmp");
//Create from file
if(!oci.CreateFromFile(strPath))
{//Processing fault}
// allocate enough memory to write access header to
hgAccessHeader = GlobalAlloc(GMEM_MOVEABLE, sizeof( OLEOBJHDR ) + 100);
int iHeaderSize = WriteAccessHeader(hgAccessHeader,oci.m_lpObject);

Downloads

Download demo project - 81 Kb
Download source - 10 Kb