Microsoft KB Archive/195251

-

{| The information in this article applies to:
 * width="100%"|
 * Microsoft Visual C++, 32-bit Editions, versions 5.0, 6.0

SUMMARY
This article describes in detail how to use COM and Automation to open a Word 97 document stored in an Access 97 database table.

The concepts in this article apply to opening documents from any other DAO data source.

MORE INFORMATION
Here are the steps you need to create a project that can open a Word 97 document stored in an Access 97 database:

  Follow steps 1 through 8 in the following Microsoft Knowledge Base article to begin creating a sample project. ARTICLE-ID: Q178749 TITLE : HOWTO: Create an Automation Project Using MFC and a Type Library   Click the Automation tab. Click Add Class and choose "From a type library." Navigate to select the object library for the application you want to automate (for this example, choose the Microsoft Word 8.0 Object Library; the default location is C:\Program Files\Microsoft Office\Office\MSWord8.olb), and click Open. Select all classes in the Confirm Classes list and click OK. NOTE: The list box in the Confirm Classes dialog box contains all of the IDispatch interfaces (which are, after all, classes) in the Microsoft Excel type library. In the lower half of the dialog box, notice that an Implementation file named Msword8.cpp contains generated class wrappers derived from ColeDispatchDriver, and the appropriate declaration header file is named Msword8.h.  Follow steps 10 through 12 in the Microsoft Knowledge Base article referred to above (Q178749).  Add the include statement for Msword8.h and Afxdao.h after the include statement for Stdafx.h at the top of the AutoProjectDlg.cpp program file: #include "stdafx.h"     #include "msword8.h"      #include  // MFC DAO   Add automation code to CAutoProjectDlg::OnRun so that it appears as shown below: //Some useful VARIANTS. COleVariant covTrue((short)TRUE); COleVariant covFalse((short)FALSE); COleVariant covOptional((long)DISP_E_PARAMNOTFOUND,VT_ERROR); void CAutoProjectDlg::OnRun { _Application objWord; Documents oDocs; _Document oDoc; LPDISPATCH pDispatch = NULL; HRESULT hr = S_OK; //Create an instance of Word97. objWord.CreateDispatch("Word.Application.8"); //Make it visible. objWord.SetVisible(TRUE); //Get a reference to the Documents collection and attach it to //the wrapper. oDocs.AttachDispatch(objWord.GetDocuments); //Create a new _Document and attach it to the Document wrapper. pDispatch = oDocs.Add( covOptional, covOptional ); oDoc.AttachDispatch( pDispatch ); //Open a Word Document stored in an Access Database. OpenDocument( pDispatch ); //Wait for user... MessageBox( "waiting..." ); //Quit Word97. objWord.Quit(covFalse, covOptional, covOptional ); }   Add the following declarations to the CAutoProjectDlg class in CAutoProjectDlg.h:      void OpenDocument( LPDISPATCH pDoc ); IStorage* GetTestStorage; IStorage* CreateIStorageFromVariant( VARIANT* pVariant ); BOOL GetBinaryFromVariant( VARIANT* pvData, BYTE** ppBuf, unsigned long* pcBufLen ); BOOL StripDBHeader( BYTE* pArray, BYTE** ppResult, unsigned long* pcBufLen );   Add the following code to CAutoProjectDlg.cpp: void CAutoProjectDlg::OpenDocument(LPDISPATCH pDoc) { //QI the document for IpersistStorage. IPersistStorage* pIPStorage = NULL; HRESULT hr = pDoc->QueryInterface( IID_IPersistStorage, (void**)&pIPStorage ); //If we succeeded, then get the test storage from the database. if (SUCCEEDED(hr)){ IStorage* pStorage = NULL; pStorage = GetTestStorage; //This loads the Word Document we retrieved from the database, //but it doesn't make it visible. if (pStorage != NULL) hr = pIPStorage->Load( pStorage ); } //QI the document for IoleObject. IOleObject* pObject = NULL; hr = pDoc->QueryInterface( IID_IOleObject, (void**)&pObject ); //If we succeeded, then use the IOleObject interface to make the //document visible. if (SUCCEEDED(hr)) hr = pObject->DoVerb( OLEIVERB_SHOW, NULL, NULL, 0, NULL, NULL ); } IStorage* CAutoProjectDlg::GetTestStorage { IStorage* pStorage = NULL; char* pszDatabaseName = "testDatabase.mdb"; char* pszTableName = "File Table"; char* pszRecordName = "Test Document"; CDaoDatabase db; COleVariant varFieldValue; //Open the test Database. This assumes mdb in current folder. db.Open("testDatabase.mdb"); //Create a new CDaoRecordset based on the new Database. CDaoRecordset rs(&db); //Open the Table we want. rs.Open( dbOpenTable, pszTableName ); //Set the search index to the primary key. rs.SetCurrentIndex( "PrimaryKey" ); //Search for the record we want. COleVariant varSearchFor( pszRecordName, VT_BSTRT ); if ( rs.Seek( "=", &varSearchFor ) ) rs.GetFieldValue("FileObject", varFieldValue ); else varFieldValue.Clear; //The BYTEs we need are stored in varFieldValue, so get them //out and make an IStorage out of them. pStorage = CreateIStorageFromVariant( &varFieldValue ); return pStorage; } IStorage* CAutoProjectDlg::CreateIStorageFromVariant( VARIANT *pVariant) { IStorage* pStorage = NULL; BYTE* pFullArray = NULL; BYTE* pByteArray = NULL; unsigned long actualSize = 0; //Get a BYTE array from SAFEARRAY in varFieldArray. if ( GetBinaryFromVariant( pVariant, &pFullArray, &actualSize ) ) { //Strip the header information off the BYTE array. if (StripDBHeader( pFullArray, &pByteArray, &actualSize )) { //Allocate some global memory for the OLE magic. HGLOBAL pDataHandle = NULL; LPVOID pDataPointer = NULL; pDataHandle = GlobalAlloc( (GMEM_MOVEABLE | GMEM_NODISCARD), (DWORD)actualSize ); pDataPointer = GlobalLock( pDataHandle ); //Copy the BYTEs into the global memory and delete the BYTE //array. CopyMemory( pDataPointer, pByteArray, actualSize ); GlobalUnlock( pDataHandle ); delete [] pByteArray; //Create an ILockBytes on the BYTE array. ILockBytes* pLockBytes = NULL; HRESULT hr = CreateILockBytesOnHGlobal( pDataHandle, false, &pLockBytes ); //Create an IStorage on the ILockBytes pointer. hr = StgOpenStorageOnILockBytes(pLockBytes, NULL, STGM_READWRITE | STGM_SHARE_EXCLUSIVE, NULL, 0, &pStorage ); } } //Return the created storage. return pStorage; } BOOL CAutoProjectDlg::GetBinaryFromVariant(VARIANT *pvData, BYTE **ppBuf, unsigned long *pcBufLen) { BOOL fRetVal = FALSE; //Binary data is stored in the variant as an array of //unsigned char. if(pvData->vt == (VT_ARRAY|VT_UI1)) // (OLE SAFEARRAY) { //Retrieve size of array. *pcBufLen = pvData->parray->rgsabound[0].cElements; //Allocate a buffer to store the data. *ppBuf = new BYTE[*pcBufLen]; if(*ppBuf != NULL) { void* pArrayData; //Obtain safe pointer to the array. SafeArrayAccessData(pvData->parray,&pArrayData); //Copy the bits into our buffer. memcpy(*ppBuf, pArrayData, *pcBufLen); //Unlock the variant data. SafeArrayUnaccessData(pvData->parray); fRetVal = TRUE; } } return fRetVal; } BOOL CAutoProjectDlg::StripDBHeader(BYTE *pArray, BYTE **ppResult, unsigned long *pcBufLen) { BOOL fRetVal = TRUE; void* pStreamPt = NULL; //There is an 86 byte header at the front of this array that we //don't need. short headerSize = 86; //Create a new BYTE array of size (*pSize - headerSize). *pcBufLen -= headerSize; *ppResult = new BYTE[*pcBufLen]; //Offset the pStreamPt pointer by the header size. pStreamPt = &pArray[ --headerSize ]; //Copy the stream data into the new array. memcpy( *ppResult, pStreamPt, *pcBufLen ); //delete the old BYTE array. delete [] pArray; return fRetVal; } As you can see in CAutoProjectDlg::GetTestStorage, this code expects an Access database called "TestDatabase.mdb", with a table called "File Table." This table should have at least one record, with the string value "Test Document" in the Primary Key field, and a OLE field called "FileObject" that contains a Word 97 document. Of course, you can change these to suit your tests, but be sure to make the corresponding changes to the sample code as well.  Compile and run the code. Click Run. Word 97 is launched and a new document is created. Then the database is accessed and the test document is pulled into the newly created Word 97 document. At this point, a message box pops up to give the user a chance to inspect the document before Word 97 is shut down by the sample code.</li></ol>