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;
HGLOBAL hgMemObject = NULL;
HGLOBAL hgAccessHeader = NULL;
HGLOBAL hgNewLongBinary = NULL;
LPBYTE lpbMemObject = NULL;
LPBYTE lpbAccessHeader = NULL;
LPBYTE lpbNewLongBinary= NULL;
int nIDLast = 1;

// 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() )

// 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 );
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; 


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


Download demo project - 81 Kb
Download source - 10 Kb

This article was originally published on July 29th, 1999

Most Popular Programming Stories

More for Developers

RSS Feeds

Thanks for your registration, follow us on our social networks to keep up-to-date