Introduction
Long have I wanted to reuse dialogs without having to worry about resource ids, DLLs, headers and libraries. I hoped MFC and COM would take care of that but never found a good article explaining how to do it. There are several books and articles about COM, MFC, ATL, components but I could never find one that would just show me how to put a COM dialog in a COM DLL and let me manage it through automation. I've finally figured out a simple process and share the process here.
I could see that you could create dialog resources, new classes for them, and enable the Automation for them, but you couldn�t create instances of them. I�m not sure why, since they derive from CCmdTarget
. This article describes how to create a COM Automated dialog that you can bring up from any COM client (i.e VB or VC++) and control it.
Below is a UML diagram showing the component and test application architecture of this simple prototype and the relevant operations involved.
Creating the MFC COM DLL that holds the component
Create a new MFC DLL project:
Make sure it is an Regular DLL using shared MFC DLL and make sure �Automation� is checked. This creates the necessary entry points for the DLL to be registered.
Create the Dialog that will be automated
Now, add a dialog resource to the project. Add a button and double click it. Tell the Class Wizard to create a new class.
Create the dialog and make sure the �Automation� radio button is selected. Note that you cannot enable the �Createable by type ID�. This caused me lots of problems and we�ll see how to get around it later.
Add an event handler to the button press, for example:
void CMyComDlg::OnPressMe()
{
AfxMessageBox("Pressed");
}
Now, you can add automation controls to the dialog. Using the Class Wizard is easiest. Choose �Add Method�
Add some implementation to the automation handler to call the same button handler, for example:
void CMyComDlg::PressMe()
{
OnPressMe();
}
Creating the COM Dialog factory
At this point, this COM dialog can be seen outside the dialog, but you cannot create an instance of it. To fix that, we add an object that can be created dynamically. For example, use the Class Wizard to add a new class, derived from CCmdTarget
. Make sure the Createable by type ID is checked.
Click OK. I�ve noticed a bug in Dev Studio, that sometimes, the interface doesn�t show up. To get it to appear, just close the project and reopen it. That should fix it.
Otherwise, use the class wizard to add a new Automation interface for the new factory.
Note the return type is of LPDISPATCH
. That is the key to passing the dialog out of the DLL to COM Automation clients. In the implementation, you return the dispatch interface pointer through COM to the client.
#include "MyComDlg.h"
LPDISPATCH CComDialogFactory::GetDialog()
{
CMyComDlg *dlg = new CMyComDlg();
return dlg->GetIDispatch(TRUE);
}
Build the project.
Run the regsvr32.exe on the DLL to register the component.
Visual Basic Client
You can now create a VB project to use this dialog and control it easily.
Create a VB Exe project, Choose Project References and browse to the newly compiled type library (TLB file).
Add a button and a handler to the VB form. The code below creates the factory, then creates the dialog. Note the dialog doesn�t appear, you would have to add a �DoModal
� or show dialog automation method to get that to happen.
Private Sub Command1_Click()
Dim factory As ComDlgInDll.ComDialogFactory
Set factory = New ComDlgInDll.ComDialogFactory
Dim dlg As ComDlgInDll.MyComDlg
Set dlg = factory.GetDialog
dlg.PressMe
End Sub
Visual C++ Client
The VC++ client isn�t quite as simple. Create a new MFC project (Doc/view, dialog, whatever) add a menu or button and a handler. You have to make sure you initialize the COM libraries if the MFC project doesn�t do it for you in OnInitDialog
or InitInstance
:
HRESULT hrx;
hrx = CoInitialize(NULL);
ASSERT( SUCCEEDED( hrx ) );
And a similar call to close the libraries OnDestory
or ExitInstance
:
CoUninitialize();
To use the component, you do this:
#import "..\Debug\ComDlgInDll.tlb" no_namespace
void CTestAppDlg::OnButton1()
{
HRESULT hrx;
IComDialogFactoryPtr pFactory;
hrx = pFactory.CreateInstance( __uuidof(ComDialogFactory) );
ASSERT( SUCCEEDED( hrx ) );
if ( SUCCEEDED( hrx ) )
{
IMyComDlgPtr pDlg;
pDlg = pFactory->GetDialog();
pDlg->PressMe();
}
}
Conclusion
At this point you have a truly reusable dialog component, with defined interfaces that you can access through any COM client. There is no worrying about copying headers and cpp files around, making sure your resource ID�s don�t overlap and merging or managing of resource files. Just the DLL and the type library.
The code in this article has minimal error checking, and is meant for illustrative purposes only. The code has been tested on VC++ 6.0 SP5, Win 2K SP4