Purpose
This article shows how to implement a simple Client
application. It will be developed in C++ using MFC architecture and ATL.
Requirements
All the code in these samples have been written in C++,
using Visual C++ 6.0 (sp4) under Windows 2000.
They also use the Active-X
Template Library (ATL v3.0).
Other articles in this series
This article is part of a series called
"COM Macro-Architecture Topology". Here are the links to other articles:
Simple Client Application
What we want in the end
The final Client application will be a simple
MFC Dialog based application. It will use the
Afx???()
functions from the
MFC (Microsoft Foundation Classes) framework. In addition, it will look
like that (or something similar):
Creating the Project
In order to create the client application, you have
to follow the steps below:
- Start the Visual C++ IDE and Select File, New and fill in
like below:
- You want to create a Dialog based application:
- Click on Next.
- Uncheck the ActiveX Controls support option.
- You can change the Title.
- Click on Next.
- Keep with the default settings.
- Click on Next.
After that, the MFC wizard will create these files :
- Workspace:
macrotopoclient.dsw
- Project:
macrotopoclient.dsp
- Application code in
macrotopoclient.h/cpp
- Dialog box code in
macrotopoclientDlg.h/cpp
COM Initialisation and Uninitialisation
You have to Initialise and
Uninitialise COM in your client application. Here because we are building a MFC
application we will use the
Afx???()
functions:
AfxOleInit()
to initialise COM.
AfxOleTerm()
to uninitialise COM.
Find the
InitInstance()
method of our Client MFC application (e.g.
CMacrotopoclientApp
) and add these lines:
BOOL CMacrotopoclientApp::InitInstance()
{
if (!AfxOleInit())
{
AfxMessageBox("Error when Initialising COM.");
return(FALSE);
}
...
CMacrotopoclientDlg dlg;
m_pMainWnd = &dlg;
int nResponse = dlg.DoModal();
...
AfxOleTerm();
return(FALSE);
}
Of course you can use as well the normal COM API functions:
CoInitializeEx()
and
CoUninitialize().
However, you need to use
the pre-processor definition
_WIN32_DCOM
.
The User Interface
In order to make life easy, we will create push
buttons and bind them to methods of our Dialog box class. These methods will
make a call to the COM Server.
Use the resource editor to add a button like
that:
- Click on
ResourceView
in the workspace view.
- Open the Dialog folder and double-click on the dialog resource for
your application (e.g.
IDD_MACROTOPOCLIENT_DIALOG
).
At the beginning, your Dialog box resource only has 2
push buttons (
OK
and
Cancel
) and a static text control
(
TODO: Place dialog controls here.
):
Remove the static text control by selecting it and press
the
Delete keyboard.
Select the push button control tool in the
Controls toolbar
:
Create a new push button on the dialog box:
- right-click anywhere in the dialog box,
- maintain your right mouse button down and move your mouse in order
to initialise the push button size,
- then release your right mouse button.
Change the caption
and the ID of your button like that:
- left-click over your push button,
- select
Properties
from the popup menu,
- then:
- Change the
ID
: IDBUT_MACROTOPOEXE_DISP_SHOWHELLO
,
- Change the
Caption
: ShowHello()
...
If you build and run your Client application at this stage,
the result could be like that:
To bind the push button to a method double-click in it. A
"
Add member function
" dialog box will appear. If you want you can
change the name of the member function, else just click
OK.
This
dialog box shows the
member function name
(
OnMacrotopoexeDispShowhello()
) that will be used when the push button
control (ID:
IDBUT_MACROTOPOEXE_DISP_SHOWHELLO
) will receive a control
message
BN_CLICKED
.
Calling the COM Server
Now we have a push button in our dialog box and
its Click event is bound to a method in our Dialog box class (e.g.
CMacrotopoclientDlg
). Next step is to call our COM Server. To write
this code you will use the "
#import
" statement:
#import "../bin/macrotoposerver_exe.exe"
void CMacrotopoclientDlg::OnMacrotopoexeDispShowhello()
{
try
{
using namespace MACROTOPOSERVER_EXELib;
IMacroTopoDispPtr pDisp(__uuidof(CoMacroTopo));
pDisp->ShowHello();
}
catch(_com_error &)
{
::MessageBox(GetSafeHwnd(),
_T("DCOM exception."),
_T("OnMacrotopoexeDispShowhello()"),
MB_OK | MB_ICONSTOP);
}
}
The code above uses the
#import
statement to get Type information
from the COM Server (e.g. from
macrotoposerver_exe.exe,
macrotoposerver_dll_psdll.dll or
macrotoposerver_dllmerged.dll).
It tries to obtain an Interface pointer of
IMacroTopoDisp
from a COM Object
CoMacroTopo
, which should implement it. If not, a
COM exception (using the
_com_error
class) will be raised.
This code is very short, but in fact the
#import
directive has
generated wrap code for you. Look in your "
.\Debug
" directory (if
you are working in
Debug build), you will find 2 files:
- macrotoposerver_exe.tlh it is a header file containing a C++
version of the Type information in the Type Library stored (in general) inside
your COM Server.
- macrotoposerver_exe.tli it is the file containing inline Interface
member functions that serve as wrappers to the remote interface methods
implemented by the COM object.
Now, if you want to have a push button
for each method of each interface of your COM object you have to:
- repeat the creation of a push button,
- bind its click event to a new member function of your Dialog box
- use the snippet code above as a template to call another
function.
At the end, just build your application,
select
a case in the main article, follow the instruction and run your application.
The #import statement
The
#import statement is a
Visual
C++ compiler directive, and like many other Microsoft tools it works very
well with COM. This directive could be see as a COM appWizard, in that sense it
helps C++ developers to get the C++ representations of what has been describe in
the Type Library (inside the COM Server).
The Type Library and its
information (in fact the COM Server
metadata information) can reside in
files with extensions such as: TLB, EXE, DLL and OCX.
The
#import
statement will generate 2 files in your current build working directory (e.g.
".\Debug" if you are working in Debug build):
- TLH file extension is a header file that contains a C++ version of
the Type information from your Type Library COM Server.
All the Type
information resides in a C++ namespace for avoiding collision between logical
names (or readable names).
Smart pointers on Interface are declared
in this file.
At the end of this file, a #include "filename.tli"
statement is defined to include the second file generated by the #import
statement.
- TLI file extension is the file that contains implementation of
inline Interface member functions. These member functions serve as wrappers to
remote interface methods implemented by the COM object.
To go further
If you want to go deeper in ATL look at
[Bi7]
and
[Bi10].
If
you are more interested in the different techniques for using COM Objects in
client code look at
[Bi3]
chapter 7.
The
#import
statement has a number of
attributes
such as
no_namespace
/
rename_namespace
for managing the namespace of the imported Type information, look the
Visual
C++ documentation.