Microsoft Excel does not raise
a Quit event that can observed by other processes. When an out-of-process
application that controls an instance of Excel releases all object references
to the instance and tells it to quit, that controller knows Excel has
terminated. But, when the controller creates and makes an instance of Excel
visible it does not know when the end-user has closed Excel. As long as the
controlling application has unreleased object references to the instance, Excel
remains loaded and registered in the Running Object Table.
This
article uses the concepts of Microsoft Knowledge Base article:
178749
(http://support.microsoft.com/kb/178749/EN-US/
)
How to create Automation project using MFC and a type library
and modifies that article's step 14 to include sample code to
illustrate one method of determining that the end-user has actually closed the
instance of Excel. The approach is to call WaitForSingleObject() to determine
that the specific Excel process has terminated.
Some methods and properties have changed for Microsoft Excel
2000. For additional information about using the sample code described in this
article with the Microsoft Excel 2000 type library, please see the following
article in the Microsoft Knowledge Base:
224925
(http://support.microsoft.com/kb/224925/EN-US/
)
Type libraries for Office may change with new release
With Microsoft Developer Studio, start a new "MFC AppWizard
(exe)" project named AutoProject.
In step 1 of the MFC AppWizard, choose Dialog Based for the
application type and click Finish. The New Project Information dialog box
appears and indicates that the Classes to be created include:
Application: CAutoProjectApp in AutoProject.h and AutoProject.cpp Dialog:
CAutoProjectDlg in AutoProject.h and AutoProjectDlg.cpp
Click OK to
create the project.
In the project workspace, click the "Resource View" tab.
Double-click AutoProject Resources to expand the resource tree. Double-click
Dialog in the Resource tree and double-click to select the dialog box resource
IDD_AUTOPROJECT_DIALOG.
Remove the Label control (IDC_STATIC).
Change the name of the OK button to IDSEEXLQUIT and the
caption to "See Excel Quit." Close the AutoProject.rc dialog box design form.
Click ClassWizard on the View menu (or press CTRL+W).
Select the Message Maps tab. Select IDSEEXLQUIT in the
Object Ids list box and select BN_CLICKED in the Messages list box. Click Add
Function and accept the function name OnSeexlquit. Click OK to close the
ClassWizard.
Note This step adds a declaration for the function
member OnSeexlquit(); to the header file named AutoProjectDLG.h. This step also
adds an empty skeleton message handler function named
CAutoProjectDlg::OnSeexlquit() to the file named AutoProjectDLG.cpp.
Click ClassWizard on the View menu (or press CTRL+W).
Select the Automation tab. Click Add Class and choose "From
a type library." Navigate to select the object library for Microsoft Excel 8.0
Object Library; (the default location is C:\Program Files\Microsoft
Office\Office\Excel8.olb) and click Open. Select all classes in the Confirm
Classes list and click OK. For Excel 2000, select the object library for
Microsoft Excel 9.0 Object Library; The default location is C:\Program
Files\Microsoft Office\Office\Excel9.olb. For Excel 2002 the full path is
C:\Program Files\Microsoft Office\Office10\excel.exe. .
Note The
list box in the Confirm Classes dialog box contains all of the IDispatch
interfaces in the Microsoft Excel type library. In the lower half of the dialog
box you will see that an Implementation file named Excel8.cpp contains
generated class wrappers derived from ColeDispatchDriver(), and the appropriate
declaration header file is named Excel8.h. For Excel 2000, the implementation
file is Excel9.cpp and the declaration header file is Excel9.h. For Excel 2002
the implementation file is Excel.cpp and the declaration header file is
Excel.h.
Click OK to close the MFC ClassWizard dialog box.
Add the following code to the
CAutoProjectApp::InitInstance() function, which loads and enables the COM
services library:
BOOL CAutoProjectApp::InitInstance()
{
if(!AfxOleInit()) // Your addition starts here.
{
AfxMessageBox("Could not initialize COM dll");
return FALSE;
} // End of your addition.
AfxEnableControlContainer();
.
.
.
}
Add the following line to the #include statements at the
top of the AutoProject.cpp program file:
#include <afxdisp.h>
Add the include statement for Excel8.h after the include
statement for Stdafx.h at the top of the AutoProjectDlg.cpp program file:
#include "stdafx.h"
#include "excel8.h"
// for Excel 2000, use #include "excel9.h"
// for Excel 2002<?xm-insertion_mark_start author="v-thomr" time="20070327T063222-0600"?> or later versions of Excel<?xm-insertion_mark_end?>, use #include "excel.h"
Add automation code to the CAutoProjectDlg::OnSeexlquit()
so that it appears as shown below:
void CAutoProjectDlg::OnSeexlquit() // Message handler function.
{
char buf[1024]; // General purpose message buffer.
_Application oExcel; // oExcel is an _Application object.
Workbooks oBooks;
LPDISPATCH lpDisp;
// Common OLE-variants... Easy variants to use for calling arguments.
COleVariant
covTrue((short)TRUE),
covFalse((short)FALSE),
covOptional((long)DISP_E_PARAMNOTFOUND, VT_ERROR);
// Start Excel and get Application object.
if(!oExcel.CreateDispatch("Excel.Application"))
{
AfxMessageBox("Couldn't CreateDispatch on Excel");
return;
}
// Set visible.
oExcel.SetVisible(TRUE);
oExcel.SetUserControl(TRUE); // This is a property of the
// _Application object. Set it so you
// can Release the oExcel and
// oBooks objects without killing
// Excel.
// Get Workbooks collection...
lpDisp = oExcel.GetWorkbooks(); // Get an IDispatch pointer
ASSERT(lpDisp); // or fail.
oBooks.AttachDispatch( lpDisp ); // Attach IDispatch pointer to
// oBooks object.
// Open a workbook.<?xm-deletion_mark author="v-thomr" time="20070327T063248-0600" data=".."?><?xm-insertion_mark_start author="v-thomr" time="20070327T063324-0600"?> If this code is run in Microsoft Office Excel 2007,
// change the file name to MybookTest.xlsx.<?xm-insertion_mark_end?>
lpDisp = oBooks.Open("C:\\MybookTest.xls", // Change for your .xls.
covOptional, covOptional, covOptional, covOptional,
covOptional, covOptional, covOptional, covOptional,
covOptional, covOptional, covOptional, covOptional // Excel 2000
);
/*
// <?xm-insertion_mark_start author="v-thomr" time="20070327T063406-0600"?>If you want to <?xm-insertion_mark_end?><?xm-deletion_mark author="v-thomr" time="20070327T063415-0600" data="O"?><?xm-insertion_mark_start author="v-thomr" time="20070327T063415-0600"?>o<?xm-insertion_mark_end?>pen an Excel 2002 <?xm-insertion_mark_start author="v-thomr" time="20070327T063420-0600"?>or an Excel 2003 <?xm-insertion_mark_end?>workbook<?xm-insertion_mark_start author="v-thomr" time="20070327T063429-0600"?>,
// use this code<?xm-insertion_mark_end?>.
lpDisp = books.Open("C:\\Test",
covOptional, covOptional, covOptional, covOptional,
covOptional, covOptional, covOptional, covOptional,
covOptional, covOptional, covOptional, covOptional,
covOptional, covOptional, covOptional ); // Excel 2000 requires
*/ // only 13 arguments<?xm-insertion_mark_start author="v-thomr" time="20070327T063451-0600"?>
// If you want to open an Excel 2007 workbook, use this code.
lpDisp = oBooks.Open("C:\\Test.xlsx",
covOptional, covOptional, covOptional, covOptional,
covOptional, covOptional, covOptional, covOptional,
covOptional, covOptional, covOptional, covOptional,
covOptional, covOptional);<?xm-insertion_mark_end?>
ASSERT(lpDisp); // It better have worked?
HWND hWnd;
hWnd = ::FindWindow("XLMain", // Pointer to class name.
NULL // Pointer to window name option.
);
if(NULL==hWnd)
{
long lErr = GetLastError();
sprintf(buf, "FindWindow error code = %d", lErr);
AfxMessageBox(buf);
}
DWORD pid; // Variable to hold the process ID.
DWORD dThread; // Variable to hold (unused) thread ID.
dThread = GetWindowThreadProcessId(hWnd, // Handle to window.
&pid // Address of variable
// for process identifier.
);
HANDLE hProcess; // Handle to existing process
hProcess = OpenProcess(SYNCHRONIZE | PROCESS_ALL_ACCESS, // access
// flag
TRUE, // handle inheritance flag
pid // process identifier
);
oBooks.ReleaseDispatch(); // Release the object-IDispatch binding.
oExcel.ReleaseDispatch();
oBooks = NULL; // Destroy the object references.
oExcel = NULL;
DWORD dwReason; // Variable to receive signal.
dwReason = WaitForSingleObject(hProcess, // Handle to object to
// wait for its end.
INFINITE // Time-out interval in
// milliseconds.
);
sprintf(buf, "Reason for Wait to terminate is %d", dwReason);
// Zero is good.
AfxMessageBox(buf);
}
Build and run the project. When the dialog box appears,
minimize Visual Studio, leaving the dialog box from your project. Click the
"See Excel Quit" button. Wait for Excel to appear and be visible. Once it is
visible, at your leisure, click the control button at the right end of the
title bar (the "X") to close Excel.
This article presents a specific approach to determining
that the end-user has terminated an instance of Excel. If you would like more
information about building a dialog-box project for other Automation servers,
please see the following article in the Microsoft Knowledge Base.
178749
(http://support.microsoft.com/kb/178749/EN-US/
)
How to create an Automation Project using MFC and a typelib
For more information on catching application events, please see the following
article in the Microsoft Knowledge Base:
183599
(http://support.microsoft.com/kb/183599/EN-US/
)
How to catch Microsoft Word 97 events using Visual C++