You are on page 1of 256

At one point in time, before COM, before ATL, programmers used ordinary .DLLs instead.

You could do a
lot with a .DLL. If you had several programs that used the same functions or other resources, you could
save space by putting those resources in a .DLL. Putting code used by multiple programs in a single .DLL
often saved maintenance time because the code was all in one place. Fixes and other modifications would
only have to be done one time. If you had a program which needed to run different routines at different
times, you could put those routines into .DLLs and have the application load the appropriate .DLL when it
was needed. There were lots of good reasons to use .DLLs.
(continued)

Featured Resources from the


IBM Business Values Solution Center

WHITEPAPER :
CRM Done Right
Improve the likelihood of CRM success from less
than 20 percent to 60 percent.

WHITEPAPER :
CFO's Rising to the Challenge of
Performance Management
Summarizes the results of the IBM Business
Consulting Services CFO Survey.

SURVEY :
The 2005 Chief Procurement Officer Survey
Highlights several strategic imperatives that are
fundamentally altering the role of procurement.

Error! Unknown switch argument.

There are still a lot of good reasons to use .DLLs. They haven't gone away. Sure, whatever you can do
with a .DLL, you can probably do with a COM object. Granted, there are a number of shortcomings to
.DLLs, some of them serious, which is why we ended up with COM in the first place. Still, .DLLs remain a
very useful tool. Compared to COM and ATL, they are much easier to make. Learning COM or ATL requires
a serious investment of time and effort. Making a .DLL is relatively easy. Modifying one is easy too. If you
know some C++ and MFC, you could be making .DLLs today.

This article will review the types of .DLLs you can make with MFC, including when to use each type and
how to make them. In the next article there will be a discussion of the limitations of .DLLs (which led to
the rise of COM and ATL), and how these can be partially avoided. In the third article, there will be more
coding details and examples.

Different types of .DLLs


There are two kinds of .DLLs you can make using MFC: an MFC extension .DLL or a regular .DLL. Regular
.DLLs in turn come in two varieties: dynamically linked or statically linked. Visual C++ also allows you to
make a generic Win32 .DLL, but in this article I'm only going to discuss the MFC-based .DLL types.

MFC extension .DLLs

Every .DLL has some kind of interface. The interface is the set of the variables, pointers, functions or
classes provided by the .DLL which you can access from the client program. They are the things that allow
the client program to use the .DLL. An MFC extension .DLL can have a C++ style interface. That is, it can
provide ("export") C++ functions and entire C++ classes to be used by the client application. The
functions it exports can use C++ or MFC data types as parameters or as return values. When it exports a
class, the client will be able to create objects of that class or derive new classes from it. Inside the .DLL,
you can also use MFC and C++.
The MFC code library used by Visual C++ is stored in a .DLL. An MFC extension .DLL dynamically links to
the MFC code library .DLL. The client application must also dynamically link to the MFC code library .DLL.
As the years have gone by the MFC library has grown. As a result, there are a few different versions of the
MFC code library .DLL out there. Both the client program and the extension .DLL must be built using the
same version of MFC. Therefore, for an MFC extension .DLL to work, both the extension .DLL and the
client program must dynamically link to the same MFC code library .DLL, and this .DLL must be available
on the computer where the application is running.

Note: If you have an application which is statically linked to MFC, and you wish to modify it so that it can
access functions from an extension .DLL, you can change the application to dynamically link to MFC. In
Visual C++, select "Project | Settings" from the menu. On the "General" settings tab you can change your
application to dynamically link to MFC.

MFC extension .DLLs are very small. You can build an extension .DLL which exports a few functions or
small classes and has a size of 10-15 KB. Obviously, the size of your .DLL depends on how much code you
store in it, but in general MFC extension .DLLs are relatively small and quick to load.

Regular .DLLs

The MFC extension .DLL only works with MFC client applications. If you need a .DLL that can be loaded
and run by a wider range of Win32 programs, you should use a regular .DLL. The downside is that your
.DLL and your client application cannot send each other pointers or references to MFC-derived classes and
objects. If you export a function, it cannot use MFC data types in its parameters or return values. If you
export a C++ class, it cannot be derived from MFC. You can still use MFC inside your .DLL, but not in
your interface.

Your regular .DLL still needs to have access to the code in the MFC code library .DLL. You can dynamically
link to this code or statically link. If you dynamically link, that means the MFC code your .DLL needs in
order to function is not built into your .DLL. Your .DLL will get the code it needs from the MFC code library
.DLL found on the client application's computer. If the right version of the MFC code library .DLL is not
there, your .DLL won't run. Like the MFC extension .DLL, you get a small .DLL (because the .DLL doesn't
include the MFC code), but you can only run if the client computer has the MFC code library .DLL.

If you statically link to the MFC code library, your .DLL will incorporate within itself all the MFC code it
needs. Thus, it will be a larger .DLL, but it won't be dependent on the client computer having the proper
MFC code library .DLL. If you can't rely on the host computer having the right version of MFC available,
this is the way to go. If your application users are all within your own company, and you have control over
what versions of the MFC .DLLs are lurking on their computers, or if your installation program also loads
the right MFC .DLL, this might not be an issue.

Building a .DLL
You can make an MFC-based .DLL with the App Wizard. Select "File | New" from the menu. On the
"Projects" tab, select "MFC AppWizard (.DLL)." Pick a name for your new project and click "OK." On the
next screen, you will have the choice to create an MFC extension .DLL, a regular .DLL "using shared MFC
.DLL" (i.e., a regular .DLL dynamically linked to MFC), or a regular .DLL statically linked to MFC. Pick the
one you want and click "Finish."

App Wizard builds a .DLL which doesn't do anything. The new .DLL will compile, but since it doesn't export
any classes or functions yet, it is still essentially useless. You now have two jobs: (1) add functionality to
make your .DLL useful; and (2) modify your client application to use your .DLL.

Export a class

Once you're done with the App Wizard, you can add classes to your .DLL by adding the .cpp and .h files
from another project, or you can create them from scratch within your current project. To export a class,
you add "__declspec(dllexport)" to the class declaration so it looks like this:

class __declspec(dllexport) CMyClass


{
//class declaration goes here
};

If you are making an MFC extension .DLL, you can instead use the AFX_EXT_CLASS macro:

class AFX_EXT_CLASS CMyClass


{
//class declaration goes here
};

There are other ways to export a class, but this is the easiest. If your exported class requires a resource
which is located in the .DLL, for example a class derived from CDialog, the process is more involved. I'll
cover this subject in tutorial #3. Below I'll discuss what to do to the client application so that it can use
your exported class.

Export variables, constants and objects

Instead of exporting a whole class, you can have your .DLL export a variable, constant or object. To export
a variable or constant, you simply declare it like this:

__declspec(dllexport) int MyInt;


__declspec(dllexport) extern const COLORREF MyColor =
RGB(50,50,50);

When you want to export a constant, you must use the "extern" specifier. Otherwise you will get a link
error.

You can declare and export a class object in the exact same manner:

__declspec(dllexport) CRect MyRect(30, 30, 300, 300);

Note that you can only export a class object if the client application recognizes the class and has its
header file. If you make a new class inside your .DLL, the client application won't recognize it without the
header file.

When you export a variable or object, each client application which loads the .DLL will get its own copy.
Thus, if two different applications are using the same .DLL, changes made by one application will not
affect the other application.

It's important to remember that you can only export objects and variables which are of global scope
within your .DLL. Local objects and variables cease to exist when they go out of scope. Thus, if your .DLL
included the following, it wouldn't work.

MyFunction( )
{
__declspec(dllexport) CSomeClass SomeObject;
__declspec(dllexport) int SomeInt;
}

As soon as the object and variable go out of scope, they will cease to exist.

Export a function

Exporting functions is similar to exporting objects or variables. You simply tack "_declspec(dllexport)" onto
the beginning of your function prototype:
__declspec(dllexport) int SomeFunction(int);

If you are making an MFC regular .DLL which will be used by a client application written in C, your function
declaration should look like this:

extern "C" __declspec(dllexport) int SomeFunction(int);

and your function definition should look like this:

extern "C" __declspec(dllexport) int SomeFunction(int x)


{
//do something
}

If you are building a regular .DLL which is dynamically linked to the MFC code library .DLL, you must
insert the AFX_MANAGE_STATE macro as the first line of any exported function. Thus, your function
definition would look like this:

extern "C" __declspec(dllexport) int AddFive(int x)


{
AFX_MANAGE_STATE(AfxGetStaticModuleState( ));
return x + 5;
}

It doesn't hurt to do this in every regular .DLL. If you you switch your .DLL to static linking, the macro will
simply have no effect.

That's all there is to exporting functions. Remember, only an MFC extension .DLL can export functions with
MFC data types in the parameters or return value.

Export a pointer

Exporting an uninitialized pointer is simple. You do it the same way you export a variable or object:

__declspec(dllexport) int* SomeInt;

You can also export an initialized object this way:

__declspec(dllexport) CSomeClass* SomePointer =


new CSomeClass;

Of course, if you declare and initialize your pointer you need to find a place to delete it.

In an extension .DLL, you will find a function called DllMain( ). This function gets called when the client
program attaches your .DLL and again when it detaches. So here's one possible way to handle your
pointers in an extension .DLL:

#include "SomeClass.h"

_declspec(dllexport) CSomeClass* SomePointer = new CSomeClass;

DllMain(HINSTANCE hInstance, DWORD dwReason, LPVOID lpReserved)


{
if (dwReason == DLL_PROCESS_ATTACH)
{
}
else if (dwReason == DLL_PROCESS_DETACH)
{
delete SomePointer;
}
}

A regular .DLL looks more like an ordinary MFC executable. It has an object derived from CWinApp to
handle opening and closing your .DLL. You can use the class wizard to add an InitInstance( ) function and
an ExitInstance( ) function.

int CMyDllApp::ExitInstance()
{
delete SomePointer;
return CWinApp::ExitInstance();
}

Using the .DLL in a client application


A .DLL can't run on its own. It requires a client application to load it and use its interface. Making a client
application that can do so is not difficult.

When you compile your .DLL, the compiler creates two important files: the .DLL file and the .lib file. Your
client application needs both of these. You must copy them into the project folder of your client
application. Note that the .DLL and .lib files that are created when you build in Debug are different that
those built when you build in Release. When you are building your client application in Debug, you need
the Debug versions of the .DLL and .lib files, and when you are building in Release you need the Release
.DLL and .lib. The easiest way to handle this is to put the Debug .DLL and .lib files in your client
application's Debug folder and the Release .DLL and .lib in the Release folder.

The next step is to go into your client project settings and tell the linker to look for your .lib file. You must
tell the linker the name of your .lib file and where it can be found. To do this, open the project settings, go
to the "Link" tab and enter your file name and path in the "Object/library modules" box. It should look
something like this:

In addition to the .DLL and .lib files, your client application needs a header file for the imported classes,
functions, objects and variables. When we were exporting, we added "__declspec(dllexport)" to our
declarations. Now when we are importing, we will add "__declspec(dllimport)." So if we wanted to import
the variable, object and function used in our previous examples, our header file would contain the
following:

__declspec(dllimport) int SomeFunction(int);


__declspec(dllexport) CSomeClass SomeObject;
__declspec(dllexport) int SomeInt;

Remember, if you used the extern "C" specifier in the .DLL, you must also use it in the client application:

extern "C" __declspec(dllimport) int SomeFunction(int);

To make things more readable, we might write it like this instead:

#define DLLIMPORT __declspec(dllimport)


DLLIMPORT int SomeFunction(int);
DLLIMPORT CSomeClass SomeObject;
DLLIMPORT int SomeInt;

Now that you have declared your object, variable and function in a header file inside your client
application, they are available for use.

To import an entire class, you must copy the entire .h header file into the client application. The .DLL and
the client application will thus have identical header files for the exported class, except one will say "class
__declspec(dllexport) CMyClass" and one will say "class __declspec(dllimport) CMyClass". If you are
making an MFC extension .DLL, you could instead say "class AFX_EXT_CLASS CMyClass" in both places.

Once you are done building your client application and you're ready to turn it over to the actual users, you
should give them your Release executable and the Release .DLL. You do not need to give the users the .lib
file. The .DLL can go in the same directory as the executable, or it can go in the Windows System
directory. As discussed above, you may also have to provide your users with the correct MFC code library
.DLL. This .DLL was loaded onto your computer when you installed Visual C++. Your users, however, may
not have it. It does not come standard with Windows.

A Word of Caution
This article should provide you with enough information to start building your own .DLLs. A word of
caution is needed, however. As mentioned at the beginning of this article, there are several serious
shortcomings to .DLLs. These shortcomings are the reason that we now have COM and ATL. There are two
main problems. First, a .DLL built with one brand of compiler may not be compatible with a client
application built with a different compiler. Second, when you modify the .DLL, you may have to recompile
the client application, even though you aren't changing any code in the client application. You may still
have to copy in a new .DLL and .lib file and recompile.

There are ways to avoid this problem under some circumstances. I'll discuss the problem in more detail in
the next article.
Introduction

I have read a lot of good articles on CodeProject. Now it's time for me to provide one.
Please leave any comment or suggestion you may have. All are welcome and greatly
appreciated. I do want to get your feedback on this implementation of multiple language
support. Following requirements have been made for this implementation of multiple
language support for MFC applications with MFC extension DLL:

• Supporting one more language should not require changing the code any
more unless there is some kind of hard-code in the previous code. You should
remove all hard coding. E.g. all message strings should come from resources,
not hardcoded in CPP / H files.
• Supporting one more language only needs to be dealt with resource files -
*.rc and res/*.* files only. This means you can provide the resource file to
anyone, and they can build resource-only DLLs for a new language without
access to your code. Their new language DLL files should work with your
application.
• Each existing MFC EXE / DLL file has one resource-only language DLL file for
each language. Resource-only language DLL file is named after its application;
appended iwth three-letter language abbreviation codes. It uses DLL as suffix.
• Existing EXE / DLLs have the choice to store resources or not. If no other
language DLL exists, it searches the system default fake language “LOC”. If
that fails, it uses the resource stored in EXE / DLL file, if any.
• Resource-only language DLL files stay with MFC application in the same folder.
• Use Control Panel settings in user’s OS to determine which language DLL to
load to memory.
• Easily change to existing VC6 code to make it support the above features for
multiple languages. Change a few codes in InitInstance()/ExitInstance()
and DllMain() and that should be enough.

This document is for Visual Studio 6, may be version 5, but not for version 7, 7.1, 8.0.
MFC version 7 has some built-in support for multiple languages. This article includes the
following:

• How to build resource-only language DLLs.


• How to detect OS language settings, how to load resource-only language DLLs
when application starts.
• Three-letter language table.

Why another Article on Multiple Language Applications

I have read some articles here at CodeProject and Microsoft:

• Internationalization and Multiple Language Support - I don't need to switch


languages on the fly. But I need to handle resources in many DLL files and
one EXE file. That's the problem with MFC extension DLL application.
• Multilingual support for applications - With about 100 M, I can never call
LoadString() etc. but the comment area of this article is pretty good.
• Multilingual Application - Change Application Language - My code has to be
language neutral. Other developers in other countries should be able to add
another language without accessing C++ code here. Provide only RC files and
files under RES folders, they can add any language to this application.
• Localized Resources in MFC Applications: Satellite DLLs - This is for VC7. This
is very good for you to know satellite DLLs.
• Creating a Resource-Only DLL - How to create a resource-only DLL.
• MS Global Development and Computing Portal - Developers going for multiple
languages can start here. Know your OS support for Multilingual User
Interface (MUI).

From VC7 and up, you shouldn't worry that much on multiple language support.

I wrapped some language related functions to a class. I made some changes for VC6, and
put a demo project using it. You need to exit and launch your application again for a new
language selected in the Control Panel.

Make a Sample MFC Application and its Extension DLL


• Using MFC AppWizard, make a MFC application. I chose "single document",
"German [German] (APPWZDEU.DLL)", "Header files only" for database
support, etc. and named it as Main. Press F7 to build it and run it.
• In FileView, right click workspace and add a MFC AppWizard (DLL) project.
Name it as SubDLL. Choose "MFC Extension DLL (Using shared MFC DLL)".
• To avoid resource ID conflict, using main's resource.h, delete SubDLL's
resource.h and replace it in ResourceView. Backup Main's resource.h before it
be overwritten in this process, since there is no resource in SubDLL now. You
are safe to restore it back after it is saved. Now Main.Exe and SubDLL.DLL
share one resource.h. To make life easier, you can change the output
SubDLL.dll to the same folder as Main.exe, and make Main dependent on
SubDll.dll in Project Dependencies.
• Now add a dialog to SubDLL and call it from Main. Export this new dialog class
by adding AFX_EXT_CLASS in the class definition header file. In Main, add a
new menu item to run this test dialog. Sure, you need to include main.h or
resource.h in TestDlg.cpp, and include TestDlg.h in MainFrame.cpp.
• Build them, run Main.exe, and test this TestDlg. Yes, we got our sample MFC
application and MFC extension DLL working now.
• Optional, add Unicode configuration to this project. Add new configuration
from Build / Configurations menu, get new configuration by copying it from
the existing release and debug versions, change _MBCS to _UNICODE at C/C++
tab in project settings dialog, change Entry-point symbol at output category
of Link tab to wWinMainCRTStartup. You need do this for Main and SubDLL,
Release and Debug.
• Notes on MFC AppWizard Language Support for East Asian Languages:

Visual C++ allows you to choose different languages when you create an MFC
AppWizard program. The East Asian language support DLLs (Japanese, Korean,
and simplified Chinese) for the MFC AppWizard, which require double-byte
enabled operating systems, are not installed by default. Thus you will not see
them in the language drop-down list on the first page of the MFC AppWizard.
However, you can find them in the Microsoft Visual
Studio\Common\msdev98\bin\ide directory on the Visual C++ CD as follows:

Language AppWizard DLL


Japanese APPWZJPN.DLL
Korean APPWZKOR.DLL
Chinese (simplified) APPWZCHS.DLL

To take advantage of East Asian language support:

1. Copy the appropriate MFC AppWizard DLL to your Microsoft Visual


Studio\Common\msdev98\bin\ide directory. This DLL can be found in
the corresponding directory on the Visual C++ CD.
2. Install the appropriate code page on your system.

If your application is dynamically linked to MFC, you must have the


corresponding localized version of the MFC resource DLL, MFC##LOC.DLL, in
your Windows system directory. To do this, copy the corresponding DLL in the
MFC\include\L.XXX\MFC##XXX.DLL on the Visual C++ CD into the Windows
system directory, and rename it to MFC##LOC.DLL. For more information on
using the localized resources that Visual C++ provides, see Tech Note 56 and
Tech Note 57.

– or–

If your application is statically linked to MFC, you must have the appropriate
localized MFC resource files in your MFC\[src|include]\L.XXX\*.rc directory.
You can find these files in the corresponding directories of Visual Studio CD1.
For more information on using the localized resources that Visual C++ provides,
see Tech Note 56 and Tech Note 57.

Static linking to MFC is supported only in Visual C++ Professional and Enterprise
Editions. For more information, see Visual C++ Editions.

Build Resource-only Language DLL

Now build English resource only DLL files for Main.exe and subdll.dll. A resource-only
DLL is a DLL that contains nothing but resources, such as icons, bitmaps, strings, and
dialog boxes. It is also a good way to provide an application with resources localized for
multiple languages. See “Creating a Resource-Only DLL” (1.) in MSDN. Here are the
steps to build a resource-only language DLL. It's named after its EXE or DLL, appending
the three-letter language code. E.g. Main.exe has its English resource DLL as
MainENU.DLL. Following are the steps to build English (ENU) resource-only language
DLL from the previous German (DEU) code.

• Copy Main.rc to MainENU.rc.


• Copy resENU\*.* to resENU\*.*.
• Use a text editor to edit MainENU.rc and replace the following:
o All word "AFX_TARG_DEU" to "AFX_TARG_ENU".
o All word "LANG_GERMAN, SUBLANG_GERMAN" to "LANG_ENGLISH,
SUBLANG_ENGLISH_US".
o The same as above "LANGUAGE 7, 1" to "LANGUAGE 9, 1".
(LANG_ENGLISH is 9, LANG_GERMAN is 7; both sublanguages are 1.)
o Take out "l.deu\\" or "l.deu\" for English. For language other than
English, use “l.xxx” subfolder to locate the MFC resource for that
language. xxx is the three-letter language code. See tables below.
o Change "res\" to "resENU\".
o VERSIONINFO block from "040704B0" to "040904B0" - "7" for German
and "9" for English.
o The same "407" to "409".
o "German (Germany)" to "English (U.S.)".
o "code_page(1252)" to "code_page(1252)". No need to change code
page for this case. (Otherwise, you may need to update codepage
based on “Code-Page Identifiers” (2.)) Keep in mind only some code
pages are supported by Windows. See “Code Pages Supported by
Windows” (7.).
• Translate all resources in MainENU.rc to English.
• Translate all resources in resENU\*.* to English.
• In FileView, right click workspace, add a “Win32 Dynamic-Link Library”
project, name it as MainENU, and put it to the same location as Main. Select
"An empty DLL project" in the wizard.
• Add the resource file MainENU.rc to this project's source files.
• Change the setting for the project MainENU, select Link tab in Project Settings
dialog. Highlight this project, add "/NOENTRY" for both release and debug
configurations.
• Optionally update intermediate and output files folder as the same as Main
project. This can be done under General tab and Link tab.
• Under Resources tab change Language to English (United States).
• Build this project, you should get MainENU.DLL.

Detect OS UI Language settings

For MUI (Multiple User Interface) OS, a user can update his OS user language in Control
Panel, Region and language options, Language. Following code is partially copied from
VC7.1 appcore.cpp. It detects UI language, and stores a language list for loading later.

LANGID CMultiLanguage::DetectUILanguage()
{
LANGID langid = 0;
int nPrimaryLang = 0;
int nSubLang = 0;
LCID lcid = 0;
PFNGETUSERDEFAULTUILANGUAGE pfnGetUserDefaultUILanguage;
PFNGETSYSTEMDEFAULTUILANGUAGE pfnGetSystemDefaultUILanguage;
HINSTANCE hKernel32;

hKernel32 = ::GetModuleHandle(_T("kernel32.dll"));
ASSERT(hKernel32 != NULL);
pfnGetUserDefaultUILanguage =
(PFNGETUSERDEFAULTUILANGUAGE)::GetProcAddress(hKernel32,
"GetUserDefaultUILanguage");
if(pfnGetUserDefaultUILanguage != NULL)
{
// First, try the user's UI language
langid = pfnGetUserDefaultUILanguage();
AddLangId( langid );
TRACE(_T("CMultiLanguage::DetectUILanguage()"
_T" 1st/2nd = %04X\n"), langid );

// Then, try the system's default UI language


pfnGetSystemDefaultUILanguage =
(PFNGETSYSTEMDEFAULTUILANGUAGE)::GetProcAddress(hKernel32,
"GetSystemDefaultUILanguage");
ASSERT( pfnGetSystemDefaultUILanguage != NULL );

langid = pfnGetSystemDefaultUILanguage();
AddLangId( langid );
TRACE(_T("CMultiLanguage::DetectUILanguage()"
_T" 3rd/4th = %04X\n"), langid );
}
else
{
// We're not on an MUI-capable system.
if (::GetVersion()&0x80000000)
{
// We're on Windows 9x, so look
// in the registry for the UI language
HKEY hKey = NULL;
LONG nResult = ::RegOpenKeyEx(HKEY_CURRENT_USER,
_T( "Control Panel\\Desktop\\ResourceLocale" ),
0, KEY_READ, &hKey);
if (nResult == ERROR_SUCCESS)
{
DWORD dwType;
TCHAR szValue[16];
ULONG nBytes = sizeof( szValue );
nResult = ::RegQueryValueEx(hKey, NULL, NULL, &dwType,
LPBYTE( szValue ), &nBytes );
if ((nResult == ERROR_SUCCESS) && (dwType == REG_SZ))
{
DWORD dwLangID;
int nFields = _stscanf( szValue, _T( "%x" ),
&dwLangID );
if( nFields == 1 )
{
langid = LANGID( dwLangID );
AddLangId( langid );
TRACE(_T("CMultiLanguage::DetectUILanguage()"
_T" 9X1st/2nd = %04X\n"), langid );
}
}
::RegCloseKey(hKey);
}
}
else
{
// We're on NT 4. The UI language
// is the same as the language of the
// version resource in ntdll.dll
HMODULE hNTDLL = ::GetModuleHandle( _T( "ntdll.dll" ) );
if (hNTDLL != NULL)
{
langid = 0;
::EnumResourceLanguages( hNTDLL, RT_VERSION,
MAKEINTRESOURCE( 1 ),
_AfxEnumResLangProc,
reinterpret_cast< LONG_PTR >( &langid ) );
if (langid != 0)
{
AddLangId( langid );
TRACE(_T("CMultiLanguage::DetectUILanguage()"
_T" NT1st/2nd = %04X\n"), langid );
}
}
}
}
if ( m_nLocales < MAX_NUM_LCID )
{
m_alcidSearch[m_nLocales] = LOCALE_SYSTEM_DEFAULT;
m_nLocales++;
} else {
m_alcidSearch[MAX_NUM_LCID-1] = LOCALE_SYSTEM_DEFAULT;
m_nLocales = MAX_NUM_LCID;
}

return LANGIDFROMLCID(m_alcidSearch[0]);
}

While the above code works for MUI OS, a user may change the default locale, not the
UI language. The following code detects the user default language, not the UI language.

LANGID CMultiLanguage::DetectLangID()
{
LANGID langid = 0;
int nPrimaryLang = 0;
int nSubLang = 0;
LCID lcid = 0;
int nLocales = 0;

langid = GetUserDefaultLangID(); // WinNT3.1/95 and later


AddLangId( langid );
TRACE(_T("CMultiLanguage::GetUserDefaultLangID() 1st/2nd = %0X\n"),
langid );

LANGID langSysid = GetSystemDefaultLangID();// WinNT3.1/95 and


later
AddLangId( langSysid );
TRACE(_T("CMultiLanguage::GetSystemDefaultLangID() 3rd/4th =
%0X\n"),
langid );

return langid;
}

We can get user selected languages in the OS now. I tested these only on Win200 Pro and
WinXP Pro. If you can test this on 9X or NT, let me know if this failed or succeeded.

Load Language Resource DLL

Like that in MFC version 7, we attempt to load the resource DLL for each of the
following languages in order, stopping when it finds one:

• The current user's default language, as returned from the


GetUserDefaultLangID() Win32 API.
• The current user's default language, without any specific sublanguage (that
is, ENC [Canadian English] becomes ENU [U.S. English]).
• The system's default language, as returned from the
GetSystemDefaultLangID() Win32 API.
• The system's default language, without any specific sublanguage.
• (Windows 2000 or later only) The current user's default UI language, as
returned from the GetUserDefaultUILanguage() Win32 API.
• (Windows 2000 or later only) The current user's default UI language, without
any specific sublanguage.
• The system's default UI language. On Windows 2000 or higher, this is
returned from the GetSystemDefaultUILanguage() API. On other platforms,
this is the language of the OS itself.
• The system's default UI language, without any specific sublanguage.
• A "fake" language with the 3-letter code LOC.

To detect the user’s default language and system default language, we should make a call
to DetectLangID(). For user and system’s UI language, calling DetectUILanguage()
will be OK. After these two calls, a list of languages requested is stored in the
CMultiLanguage::m_alcidSearch[] array. To load it, use the following:

HINSTANCE CMultiLanguage::LoadLangResourceDLL(LPCTSTR szModuleName,


LANGID langUpdateId)
{
TCHAR szResDLLName[_MAX_PATH+14];
HINSTANCE hLangDLL = NULL;
LCID alcid[MAX_NUM_LCID+1];
TCHAR szLangCode[4];
//LPTSTR pszExtension;
int nNoExtension;
LCID lcid;
int nLocales = 0;

//pszExtension = ::PathFindExtension(szModuleName);
//nNoExtension = pszExtension - szModuleName; temp. for ".exe"
nNoExtension = lstrlen(szModuleName) - 3 ;
// Quick and kind of dirty way to take ".exe"/".dll" away.

if ( langUpdateId != MAKELANGID(LANG_NEUTRAL,SUBLANG_NEUTRAL) )
{
alcid[nLocales] = MAKELCID(langUpdateId, SORT_DEFAULT);
nLocales++;
}
for ( int iLocale = 0; iLocale < m_nLocales; iLocale++ )
{
if ( m_alcidSearch[iLocale] != 0 )
{
alcid[nLocales] = m_alcidSearch[iLocale];
nLocales++;
}
}
for ( iLocale = 0; iLocale < nLocales; iLocale++ )
{
lcid = alcid[iLocale];
if (lcid == LOCALE_SYSTEM_DEFAULT)
lstrcpy(szLangCode, _T("LOC"));
else {
int nResult = ::GetLocaleInfo(lcid,
LOCALE_SABBREVLANGNAME, szLangCode, 4);
ASSERT( nResult == 4 );
if ( nResult == 0 )
return NULL;
}

if ( nNoExtension + 3 + 4 + 1 < _MAX_PATH+14 )


{ // append "ENU.DLL" to moduleName
lstrcpyn(szResDLLName, szModuleName, nNoExtension);
lstrcat(szResDLLName, szLangCode);
lstrcat(szResDLLName, _T(".DLL"));
} else {
ASSERT(FALSE);
// No enough space to hold language resource dll name path.

return NULL;
}
hLangDLL = ::LoadLibrary(szResDLLName);
if(hLangDLL != NULL)
return hLangDLL;// Successful return
}

return hLangDLL;
}

We can call LoadLangResourceDLL(…) to load the first available language DLL in the
list. You can change the language ID detected from above. For example, you only include
the resource DLL for Chinese PRC, and you want to use it for Chinese Taiwan and
Chinese Singapore too. You can do this hard-coding here. If you don't want to hard code
here, you can make a duplicated copy of the resource-only DLL file with another three-
letter language name. (Copy XXXXCHS.DLL to XXXXCHT.DLL for this case.)

If your EXE or DLL files don't include resource, you need to have one system default
language "LOC" in your resource-only language DLL file list. This is the last chance the
system will try to find the language resource.

Application Code Changes

For Main.exe to load a language related resource-only DLL, add the following code to
main.cpp in the very beginning of CMain::InitInstance():

// Begin of Multiple Language support


if ( CMultiLanguage::m_nLocales <= 0 ) // Not detected yet
{
CMultiLanguage::DetectLangID(); // Detect language as user
locale
CMultiLanguage::DetectUILanguage(); // Detect language in
MUI OS
}
TCHAR szModuleFileName[MAX_PATH]; // Get Module File Name
and path
int ret = ::GetModuleFileName(theApp.m_hInstance, szModuleFileName,
MAX_PATH);
if ( ret == 0 || ret == MAX_PATH )
ASSERT(FALSE);
// Load resource-only language DLL. It will use the languages
// detected above, take first available language,
// or you can specify another language as second parameter to
// LoadLangResourceDLL. And try that first.
ghLangInst = CMultiLanguage::LoadLangResourceDLL(
szModuleFileName );
if (ghLangInst)
AfxSetResourceHandle( ghLangInst );
// End of Multiple Language support

You may want to free the library in ExitInstance().

DLL Code Changes

For the MFC extension DLL SubDLL.DLL to load language related resource-only DLL,
add the following code to SubDLL.cpp in DllMain(...) just before calling
CDynLinkLibrary(...):

// Begin of Multiple Language support


if ( CMultiLanguage::m_nLocales <= 0 )
// Not detected yet
{
CMultiLanguage::DetectLangID(); // Detect language as user
locale
CMultiLanguage::DetectUILanguage(); // Detect language in
MUI OS
}
TCHAR szModuleFileName[MAX_PATH]; // Get Module File Name and
path
int ret = ::GetModuleFileName(hInstance, szModuleFileName,
MAX_PATH);
if ( ret == 0 || ret == MAX_PATH )
ASSERT(FALSE);
// Load resource-only language DLL. It will use the languages
// detected above, take first available language,
// or you can specify another language as second parameter to
// LoadLangResourceDLL. And try that first.
shLangInst = CMultiLanguage::LoadLangResourceDLL(
szModuleFileName );
if (shLangInst)
SubDLLDLL.hResource = shLangInst;
// End of Multiple Language support

You may want to free the library when system detaches the process.

Using the Software

• Include MultiLanguage.cpp and MultiLanguage.h in your project. With


dependence, put this to the most independent DLL project.
• Use *.rc, res\*.* to build your resource-only DLL file for each EXE / DLL and
each language. See "Build resource-only language DLL" above.
• Load language resource-only DLL in your MFC application as "Application Code
Changes" above.
• Load language resource-only DLL in your MFC extension DLL as described in
"DLL Code Changes" above for each extension DLL project.
• If you have only one EXE file, you can forget all extension related issues here.
• It’s better to detect language once and use it everywhere, so I put this as
static. FYI: DetectUILanguage() function in DLLMain(...) will be called
earlier than that in InitInstance().
• Bugs are expected. This is only demo level code. You can report any bugs
here or send an email to me with [codeproject] in the Subject line.

Three-letter Language Identifier Table

Column one is LANGID; the lower 10 bits are for the language, the higher 6 bits are for the
sub-language. Column two is the three-letter language code. Column three is the three-
letter language code without sub-language. It looks for this language if the language in
column two does not exist. Column one and column four are got from “Language
Identifier”(3.); column two and column three are got from the GetLocaleInfo() function
on my XP Pro PC. See CMultiLanguage::PrintThreeLetterLanguageCodeList()
function in MultiLanguage.cpp for details.

Identifier Column 2 Column 3 Description and notes


0x0436 AFK AFK Afrikaans
0x041c SQI SQI Albanian
0x0401 ARA ARA Arabic (Saudi Arabia)
0x0801 ARI ARA Arabic (Iraq)
0x0c01 ARE ARA Arabic (Egypt)
0x1001 ARL ARA Arabic (Libya)
0x1401 ARG ARA Arabic (Algeria)
0x1801 ARM ARA Arabic (Morocco)
0x1c01 ART ARA Arabic (Tunisia)
0x2001 ARO ARA Arabic (Oman)
0x2401 ARY ARA Arabic (Yemen)
0x2801 ARS ARA Arabic (Syria)
0x2c01 ARJ ARA Arabic (Jordan)
0x3001 ARB ARA Arabic (Lebanon)
0x3401 ARK ARA Arabic (Kuwait)
0x3801 ARU ARA Arabic (U.A.E.)
0x3c01 ARH ARA Arabic (Bahrain)
0x4001 ARQ ARA Arabic (Qatar)
0x042b HYE HYE Windows 2000/XP: Armenian. This is Unicode only.
0x042c AZE AZE Azeri (Latin)
0x082c AZE AZE Azeri (Cyrillic)
0x042d EUQ EUQ Basque
0x0423 BEL BEL Belarusian
0x0445 BNG BNG Bengali (India)
0x141a BSB HRV Bosnian (Bosnia and Herzego vina)
0x0402 BGR BGR Bulgarian
0x0455 === === Burmese
0x0403 CAT CAT Catalan
0x0404 CHT CHT Chinese (Taiwan)
0x0804 CHS CHT Chinese (PRC)
0x0c04 ZHH CHT Chinese (Hong Kong SAR, PRC )
0x1004 ZHI CHT Chinese (Singapore)
Windows 98/ME, Windows 2000 /XP: Chinese
0x1404 ZHM CHT
(Macao SAR)
0x041a HRV HRV Croatian
0x101a HRB HRV Croatian (Bosnia and Herzeg ovina)
0x0405 CSY CSY Czech
0x0406 DAN DAN Danish
0x0465 DIV DIV Windows XP: Divehi. This is Unicode only.
0x0413 NLD NLD Dutch (Netherlands)
0x0813 NLB NLD Dutch (Belgium)
0x0409 ENU ENU English (United States)
0x0809 ENG ENU English (United Kingdom)
0x0c09 ENA ENU English (Australian)
0x1009 ENC ENU English (Canadian)
0x1409 ENZ ENU English (New Zealand)
0x1809 ENI ENU English (Ireland)
0x1c09 ENS ENU English (South Africa)
0x2009 ENJ ENU English (Jamaica)
0x2409 ENB ENU English (Caribbean)
0x2809 ENL ENU English (Belize)
0x2c09 ENT ENU English (Trinidad)
Windows 98/ME, Windows 2000 /XP: English
0x3009 ENW ENU
(Zimbabwe)
Windows 98/ME, Windows 2000 /XP: English
0x3409 ENP ENU
(Philippines)
0x0425 ETI ETI Estonian
0x0438 FOS FOS Faeroese
0x0429 FAR FAR Farsi
0x040b FIN FIN Finnish
0x040c FRA FRA French (Standard)
0x080c FRB FRA French (Belgian)
0x0c0c FRC FRA French (Canadian)
0x100c FRS FRA French (Switzerland)
0x140c FRL FRA French (Luxembourg)
Windows 98/ME, Windows 2000 /XP: French
0x180c FRM FRA
(Monaco)
0x0456 GLC GLC Windows XP: Galician
0x0437 KAT KAT Windows 2000/XP: Georgian. This is Unicode only.
0x0407 DEU DEU German (Standard)
0x0807 DES DEU German (Switzerland)
0x0c07 DEA DEU German (Austria)
0x1007 DEL DEU German (Luxembourg)
0x1407 DEC DEU German (Liechtenstein)
0x0408 ELL ELL Greek
0x0447 GUJ GUJ Windows XP: Gujarati. This is Unicode only.
0x040d HEB HEB Hebrew
0x0439 HIN HIN Windows 2000/XP: Hindi. This is Unicode only.
0x040e HUN HUN Hungarian
0x040f ISL ISL Icelandic
0x0421 IND IND Indonesian
0x0434 XHO XHO isiXhosa/Xhosa (South Africa)
0x0435 ZUL ZUL isiZulu/Zulu (South Africa)
0x0410 ITA ITA Italian (Standard)
0x0810 ITS ITA Italian (Switzerland)
0x0411 JPN JPN Japanese
0x044b KAN KAN Windows XP: Kannada. This is Unicode only.
0x0457 KNK KNK Windows 2000/XP: Konkani. This is Unicode only.
0x0412 KOR KOR Korean
0x0812 === KOR Windows 95, Windows NT 4.0 only: Korean (Johab)
0x0440 KYR KYR Windows XP: Kyrgyz.
0x0426 LVI LVI Latvian
0x0427 LTH LTH Lithuanian
0x0827 === LTH Windows 98 only: Lithuanian (Classic)
0x042f MKI MKI Macedonian (FYROM)
0x043e MSL MSL Malay (Malaysian)
0x083e MSB MSL Malay (Brunei Darussalam)
0x044c MYM MYM Malayalam (India)
0x0481 MRI MRI Maori (New Zealand)
0x043a MLT MLT Maltese (Malta)
0x044e MAR MAR Windows 2000/XP: Marathi. This is Unicode only.
0x0450 MON MON Windows XP: Mongolian
0x0414 NOR NOR Norwegian (Bokmal)
0x0814 NON NOR Norwegian (Nynorsk)
0x0415 PLK PLK Polish
0x0416 PTB PTB Portuguese (Brazil)
0x0816 PTG PTB Portuguese (Portugal)
0x0446 PAN PAN Windows XP: Punjabi. This is Unicode only.
0x046b QUB QUB Quechua (Bolivia)
0x086b QUE QUB Quechua (Ecuador)
0x0c6b QUP QUB Quechua (Peru)
0x0418 ROM ROM Romanian
0x0419 RUS RUS Russian
0x044f SAN SAN Windows 2000/XP: Sanskrit. This is Unicode only.
0x043b SME SME Sami, Northern (Norway)
0x083b SMF SME Sami, Northern (Sweden)
0x0c3b SMG SME Sami, Northern (Finland)
0x103b SMJ SME Sami, Lule (Norway)
0x143b SMK SME Sami, Lule (Sweden)
0x183b SMA SME Sami, Southern (Norway)
0x1c3b SMB SME Sami, Southern (Sweden)
0x203b SMS SME Sami, Skolt (Finland)
0x243b SMN SME Sami, Inari (Finland)
0x0c1a SRB HRV Serbian (Cyrillic)
0x1c1a SRN HRV Serbian (Cyrillic, Bosnia, and Herzegovina)
0x081a SRL HRV Serbian (Latin)
0x181a SRS HRV Serbian (Latin, Bosnia, and Herzegovina)
0x046c NSO NSO Sesotho sa Leboa/Northern Sotho (South Africa)
0x0432 TSN TSN Setswana/Tswana (South Africa)
0x041b SKY SKY Slovak
0x0424 SLV SLV Slovenian
0x040a ESP ESP Spanish (Spain, Traditional Sort)
0x080a ESM ESP Spanish (Mexican)
0x0c0a ESN ESP Spanish (Spain, Modern Sort )
0x100a ESG ESP Spanish (Guatemala)
0x140a ESC ESP Spanish (Costa Rica)
0x180a ESA ESP Spanish (Panama)
0x1c0a ESD ESP Spanish (Dominican Republic)
0x200a ESV ESP Spanish (Venezuela)
0x240a ESO ESP Spanish (Colombia)
0x280a ESR ESP Spanish (Peru)
0x2c0a ESS ESP Spanish (Argentina)
0x300a ESF ESP Spanish (Ecuador)
0x340a ESL ESP Spanish (Chile)
0x380a ESY ESP Spanish (Uruguay)
0x3c0a ESZ ESP Spanish (Paraguay)
0x400a ESB ESP Spanish (Bolivia)
0x440a ESE ESP Spanish (El Salvador)
0x480a ESH ESP Spanish (Honduras)
0x4c0a ESI ESP Spanish (Nicaragua)
0x500a ESU ESP Spanish (Puerto Rico)
0x0430 === === Sutu
0x0441 SWK SWK Swahili (Kenya)
0x041d SVE SVE Swedish
0x081d SVF SVE Swedish (Finland)
0x045a SYR SYR Windows XP: Syriac. This is Unicode only.
0x0449 TAM TAM Windows 2000/XP: Tamil. This is Unicode only.
0x0444 TTT TTT Tatar (Tatarstan)
0x044a TEL TEL Windows XP: Telugu. This is Unicode only.
0x041e THA THA Thai
0x041f TRK TRK Turkish
0x0422 UKR UKR Ukrainian
Windows 98/ME, Windows 2000 /XP: Urdu
0x0420 URD URD
(Pakistan)
0x0820 === URD Urdu (India)
0x0443 UZB UZB Uzbek (Latin)
0x0843 UZB UZB Uzbek (Cyrillic)
Windows 98/ME, Windows NT 4 .0 and later:
0x042a VIT VIT
Vietnamese
0x0452 CYM CYM Welsh (United Kingdom)

Introduction

This article shows you how to create a Win32 and MFC DLL to dynamically link a
Library to your application. Microsoft Foundation Class (MFC) library can be used to
create simplified DLLs. The MFC supports two types of DLLs, regular and extension:

• Regular DLL using shared MFC DLL


• Regular DLL with MFC statically linked (Client doesn't have to be MFC based)
• MFC Extension DLL (Using shared MFC DLL)

Win32 DLL (non-MFC Library based) in general, only supports regular DLLs. You
should use Win32 DLLs when your DLL is not using the MFC Library, Win32 is
substantially more efficient.

Extension DLLs are for developing re-useable binary code and it is for advance usage
(such as ATL and COM). DLLs are very useful, especially if you want to create programs
that are modular. You can go to the Microsoft MSDN web page if you want to know more
on DLLs.

This tutorial is in five parts. It gives a step by step procedure for developing a Win32
DLL and a regular MFC DLL object. There are three client applications, two Win32
console applications (one for Load Time linkage and the other illustrates Run Time
linkage), the third DLL client application uses the shared MFC Library.

Regular DLLs execute in the same memory space as the DLL client application, you don't
have to worry about marshaling data and pointers across process boundaries.

Part One, The Win32 DLL Object

First, we are going to make the Win32 DLL core files for the project, W32DLL.xxx.

1. Start Visual C++ Studio.


2. Close any open workspace and all files.
3. Select New from the file menu.
4. Select Project: Win32 Dynamic-Link Library.
5. Give the the project a fitting name, say, W32DLL, make sure the location is
acceptable and click OK.
6. Select the "A simple DLL project" radio button.
7. Click Finish and OK.
Next, we are going to make the DLL declaration/header file: DLLCode.h. Select New
from the file menu, then select "C/C++ Header File" and name the file DLLCode. Click
OK.

Copy and paste the following code excerpt:

/*******************************************************
File name: DLLCode.h

This file contains all the DLL interfacing object


declarations, in this example:
a class object, two global function object, and
a global integer variable.
Notice: we use the same header file for compiling the
.DLL and the .exe (application).
This header file defines a macro which export the target
DLL objects if we are building
a DLL, otherwise it import the DLL objects into an
application which uses the DLL. If
we define DLLDIR_EX (a preprocessor identifier),
then the preprocessor define macro
DLLDIR (a mnemonic for DLL import/export Direction)
becomes an export instruction,
otherwise its an import instruction by default.
************************************************************/
#ifdef DLLDIR_EX
#define DLLDIR __declspec(dllexport) // export DLL information
#else
#define DLLDIR __declspec(dllimport) // import DLL information
#endif

// The extern "C" declaration allows mixed languages compactability,


// it prevents the C++ compiler from using decorated (modified)
// names for the functions
extern "C" {
void DLLDIR DLLfun1(char*);
int DLLDIR DLLfun2(int);
};
extern int DLLDIR DLLArg;

class DLLDIR DLLclass


{
public:
DLLclass(); // Class Constructor
~DLLclass(); // Class destructor
int Add(int, int); // Class function Add
int Sub(int, int); // Class function Subtract
int Arg; // Warning: you should not
// import class variables
// since the DLL object can be dynamically unloaded.
};

Save and close this header file. Now we are going to create the DLL implementation file,
DLLCode.cpp.
Select New from the file menu, then select "C++ Source File" and name the file
DLLCode. Then click OK.

Copy and paste the following code excerpt:

/*********************************************************
File name: DLLCode.cpp

The header file, DLLCode.h, prototypes


all of the DLL interface objects
**********************************************************/
#include "Stdafx.h"
#include "DLLCode.h"
#include <iostream>

using namespace std;

void DLLfun1(char* a)
{
cout << a << endl;
};

int DLLfun2(int a) { return a<<1; };

int DLLArg = 100;

DLLclass::DLLclass() {};
DLLclass::~DLLclass() {};

int DLLclass::Add(int a, int b)


{
return a + b;
};

int DLLclass::Sub(int a, int b)


{
return a - b;
};

We want to access the DLL, so we need to export it. When the DLL is built, it also builds
something called an export library. The export library is similar to a regular library. It
contains all of the information necessary to dynamically link to the DLL at runtime.
When we export a function or a class, the function name and location is stored in the
export library. The application uses the DLL links in the export library to access the DLL.

To create the DLL export library, select "setting..." from the Project menu. Select the
C/C++ tab. Append, or insert, ",DLLDIR_EX" (without the quotation marks) to the
Preprocessor Definition text box. Then click OK. This will prevent compiler assumptions
and warnings.

Note, Visual C++ defines an export macro <projectname>_EXPORTS, in our case,


W32DLL_EXPORTS. But we used DLLDIR_EX, a generic macro name.
Click the "!" button to compile, build, and run the W32DLL project.

Close the "Executable For Debug Session" dialog box, we ran the DLL prematurely.

Congratulation, you finished building the Win32 DLL and its export Library.

Part Two, DLL Client Application One

This is the simplest and most versatile DLL Client, without MFC. This flexibility is due
to the DLL export library information.

Now, we are going to make a Win32 console application, project DLLClient1. In this
application, the DLL is loaded at application startup, "Load Time" linkage.

1. Close any open workspace and files, then select New from the File menu.
2. Select Win32 Console Application.
3. In the Project name box, give the project a fitting name, say, DLLClient1,
then click Next.
4. Select "An empty project", then click Finish and OK.

Next, we are going to make the C/C++ source file: DLLClient1. Select New from the File
menu, then select "C/C++ Source File". In the File Name box, give it a fitting name, say,
DLLClient1, then press the Enter key.

Copy and paste the following code excerpt:

/***********************************************************
File name: DLLClient1.cpp
***********************************************************/
#include <iostream>
#include <conio.h>
#include <windows.h>
#include "DLLCode.h"
#pragma comment(lib,"W32DLL.lib")

using namespace std;

int main()
{
int a, b, c;
DLLclass classFromDLL;
classFromDLL.Arg = 6;
a = classFromDLL.Add(3, 2);
b = classFromDLL.Sub(3, 2);
c = classFromDLL.Arg;

cout << "DLL class Add function return: " << a << endl;
cout << "DLL class Sub function return: " << b << endl;
cout << "DLL class Arg Variable return: " << c << endl;
getch();
a = DLLArg;
b = DLLfun2(30);

DLLfun1("this is the string pass to function DLLfun1");

cout << "\n\nDLL Variable DLLArg return: " << a << endl;
cout << "DLL function DLLfun2 return: " << b << endl;
getch();

return 0;
}

Save and close this C++ source file, then minimize the VC++ Studio window. We must
get some common files from the W32DLL project. So, Copy, don't move, the following
files to the DLLClient1 project directory:

• W32DLL\Debug\W32DLL.DLL
• W32DLL\Debug\W32DLL.lib
• W32DLL\DLLCode.h

If you don't see the .DLL file in the Debug or Release folders, then select the View, or
Tool (depending on the operating system) menu option, then select Folder Options. Next
click on the View tab. Select option: Show all files. Then re-examine the Debug or
Release folder.

Now maximize the VC++ Studio window, then click the "!" button to compile, build, and
run the application.

If you have a DLL you use in many projects, then you can take advantage of the VC++
file architecture. Copy the DLL file into the system directory and the LIB file into the
Visual C++ Lib directory. See Options on the Tools menu, and select the Directories tab,
there are search directories for EXEs, SOURCE CODEs, and LIBs.

In this part, the DLL is loaded at application startup time. The Operating System searches
the DLL in the following locations:

• The Windows System directory


• The Windows directory
• The Application local path
• The directories listed in the environment path variable

For simplicity, we just copy the common files to the target directory, DLLClient1.

Part Three, DLL Client Application Two

This DLL Client is for DLLs which do not have an export library, hints, we can't import
the DLL header file. Note, we are restricted to function calls.
Now, we are going to make a Win32 console application, project DLLClient2.

In this application, the DLL is loaded during execution of the application, "Run Time"
DLL linkage.

1. Close any open workspace and files, then select New from the File menu.
2. Select Win32 Console Application.
3. In the Project Name box, give the project a fitting name, say, DLLClient2,
then click Next.
4. Select "An empty project", then click Finish and OK.

Next, we are going to make the C/C++ source file DLLClient2.

Select New from the File menu, then select "C/C++ Source File". In the File Name box,
give it a fitting name, say, DLLClient2, then press the Enter key.

Copy and paste the following code excerpt:

/************************************************************
File name: DLLClient2.cpp
************************************************************/
#include <conio.h> // Header file containing getch() prototype
#include <iostream>
#include <windows.h>

using namespace std;

typedef void (*MYFUN1)(char*); // pointer to: void function(char*)


typedef int (*MYFUN2)(int); // pointer to: int function(int)
int main()
{

MYFUN1 pfun1;
MYFUN2 pfun2;
HMODULE hMod; // handle to loaded library module
BOOL bRes; // BOOL to check if DLL was successfully
unloaded

// returns a handle to the DLL, otherwise NULL


hMod = LoadLibrary("W32DLL.DLL");

// returns the address of the DLL functions, otherwise NULL


pfun1 = (MYFUN1) GetProcAddress(hMod, "DLLfun1");
pfun2 = (MYFUN2) GetProcAddress(hMod, "DLLfun2");

// (DLL function address) (function parameters)


(pfun1)("this is the string pass to function DLLfun1");

// (DLL function address) (function parameters)


int a = (pfun2) (30);

cout << "DLL function DLLfun2 return: " << a << endl;
cout << "Press a key to exit" << endl;
getch();

///////////////////////////////////////////////////////
// This code will run if you compile the W32DLL project
// with the W32DLL.def file to explicitly export DLLArg.
int *i;
i = (int*) GetProcAddress(hMod, "DLLArg");

if (i)
{
cout << "Variable DLLArg is: " << *i << endl;
};

cout << "Press a key to exit" << endl;


getch();

// returns nonzero if sucussful


bRes = FreeLibrary(hMod);
return 0;
}
// =========== Code snippet 4 ====================

Save and close this C++ source file, then minimize the VC++ studio window. Copy, don't
move, the following file to the DLLClient2 project directory:

W32DLL\Debug\W32DLL.DLL

Now maximize the VC++ Studio window, then click the "!" button to compile, build, and
run the application.

In this part, the DLL is loaded at application run time. The LoadLibrary function is used
to load a DLL at run time. Being that we dynamically loaded the library, we should free
this resource when we are finished with it.

Addendum

This subsection was added as a result to user Hing's comment.

The code in "Code snippet 4" was modified to add code for accessing the global variable
DLLArg. Note, in the application you just ran, DLLCient2, function call
GetProcAddress(hMod, "DLLArg") returned a NULL pointer. To add the global
variable name to the DLL object, you must add a new text file to the W32DLL project. Go
to New on the File menu. Select "Text File" on the File tag. Name the file:
"W32DLL.def", the "def" file extension is important. Click OK.

Add the following text:

;File: W32DLL.def (explicitly export the global object names.)


LIBRARY W32DLL.dll
EXPORTS
DLLfun1
DLLfun2
DLLArg

Save and recompile the W32DLL project. Copy the new W32DLL.DLL file to the project
DLLClient2 directory. Run the DLLClient2 application.

Function GetProcAddress should find variable "DLLArg".

Note: You CANNOT export a class object to the DLL. The library object contains that
information, along with the header file.

Part Four, MFC DLL Object

This time, we are going to make the MFC DLL core files for the project, RMFCDLL.xxx.

1. Start Visual C++ Studio.


2. Close any open workspace and all files.
3. Select New from the File menu.
4. Select Project: MFC AppWizard(DLL).
5. Give the the project a fitting name, say, RDLLMFC, make sure the location is
acceptable and click OK.
6. Select the "Regular DLL using shared MFC DLL" radio button.
7. Click Finish and OK.

Next, we are going to make the DLL declaration header file: DLLCode.h. Select New
from the File menu, then select "C/C++ Header File" and name the file DLLCode. Click
OK.

Copy and paste the following code excerpt:

/******************************************************
File name: DLLCode.h

This file contains MFC objects, hints,


you should use a MFC Client.
Notice: we use the same header file for compiling
the .DLL and the .exe (application).
This header file defines a macro which export the
target DLL objects if we are building
a DLL, otherwise it import the DLL objects into an
application which uses the DLL. If
we define DLLDIR_EX (a preprocessor identifier),
then the preprocessor define macro
DLLDIR (a mnemonic for: DLL import/export Direction)
becomes an export instruction,
otherwise its an import instruction by default.
******************************************************/
#ifdef DLLDIR_EX
#define DLLDIR __declspec(dllexport)
#else
#define DLLDIR __declspec(dllimport)
#endif

extern "C" {
// We delete the function DLLfun1 (as defined
// in the W32DLL DLL) it writes to
// a console, not a window, it isn't appropriate to
// call it from a MFC Client.
int DLLDIR DLLfun2(int);
void DLLDIR DrawEllipse ( CRect, CDC* ); // a MFC function call
};

extern int DLLDIR DLLArg;

class DLLDIR DLLclass


{
public:
DLLclass(); // Class Constructor
~DLLclass(); // Class destructor
int Add(int, int); // Class function Add
int Sub(int, int); // Class function Subtract
int Arg;
};

Save and close this header file. Now we are going to create the DLLCode.cpp file. Select
New from the File menu, then select "C++ Source File". Name the file: DLLCode. Click
OK. Copy and paste the following code excerpt:

/************************************************************
File name: DLLCode.cpp

The header file, DLLCode.h, prototypes all


of the DLL interface objects
*************************************************************/
#include "StdAfx.h"
#include "DLLCode.h"

int DLLfun2(int a) { return a<<1; };

int DLLArg = 100;

DLLclass::DLLclass() {};
DLLclass::~DLLclass() {};

int DLLclass::Add(int a, int b)


{
return a + b;
};

int DLLclass::Sub(int a, int b)


{
return a - b;
};

void DrawEllipse ( CRect rect, CDC *pDC )


{
CBrush brush;
brush.CreateSolidBrush(RGB(0,0,255));
pDC->SelectObject(&brush);
pDC->Ellipse(&rect);
};

Now, select "setting..." from the Project menu. Select the C/C++ tab. Append, or insert,
",DLLDIR_EX" (without the quotation marks) to the Preprocessor Definition text box.
Then click OK.

Click the "!" button to compile, build, and run the RDLLMFC project. Close the
"Executable For Debug Session" dialog box, we ran the DLL prematurely.
Congratulations, you finished building the Win32 DLL and its export Library.

Part Five, DLL MFC Client Application

This client application is a MFC application. It provides the framework, a window, for
the MFC CBrush object used in the DLL function DrawEllipse.

Now, we are going to make the MFC application project MFCAp.

1. Close any open workspace and files, then select New from the File menu.
2. Select Project: MFC AppWizard(exe).
3. In the Project name box, give the project a fitting name, say, MFCAp, then
click OK.
4. Select the "Single document" radio button, then click Finish and OK.

Select "ClassWizard..." from the View menu. The Message Maps tab should be active. In
the "Class name:" dropdown box, select "CMFCApView". In the "Message functions:"
list box, double click the "OnDraw" function label.

You should see the View class OnDraw function code. Replace that stub code with the
following code excerpt, use copy and paste:

///////////////////////////////////////////////
// CMFCApView drawing

void CMFCApView::OnDraw(CDC* pDC)


{
// DLL MFC Library function call
CRect rect;
rect.top=10;
rect.left=10;
rect.right=200;
rect.bottom=200;
DrawEllipse(rect,pDC);

// DLL class object call


int a, b, c;
CString str;
DLLclass classFromDLL;
classFromDLL.Arg = 6;
a = classFromDLL.Add(3, 2);
b = classFromDLL.Sub(3, 2);
c = classFromDLL.Arg;

// Display data in window


int y = 250, dy;
TEXTMETRIC tm;
pDC->GetTextMetrics(&tm);
dy = tm.tmHeight + tm.tmExternalLeading;
str.Format("DLL class Add function return: %d", a);
pDC->TextOut(20, y, str);
y += dy;
str.Format("DLL class Sub function return: %d", b);
pDC->TextOut(20, y, str);
y += dy;
str.Format("DLL class Arg Variable return: %d", c);
pDC->TextOut(20, y, str);
y += dy;
a = DLLArg;
b = DLLfun2(30);
str.Format("DLL class Arg Variable return: %d", a);
pDC->TextOut(20, y, str);
y += dy;
str.Format("DLL function \"DLLfun2\" return: %d", b);
pDC->TextOut(20, y, str);
}

At the top of the file MCFApView.cpp, and somewhere after the line containing:
#include "stdafx.h", insert the following line:

#include "DLLCode.h"

Save and close the MFCApView.cpp file. Select "Setting..." from the Project menu. Select
the "Link" tab. In the "Object/Library modules:" text box, enter: "RDLLMFC.lib",
without the quotation marks. Then click OK.

Now, minimize the VC++ Studio window. Copy the following files to the MFCAp project
directory:

• RDLLMFC\Debug\RDLLMFC.DLL
• RDLLMFC\Debug\RDLLMFC.lib
• RDLLMFC\DLLCode.h

Now maximize the VC++ Studio window, then click the "!" button.

A note in closing, you shouldn't change the DLL interface, especially class objects which
are exported therein, because the v-table and class size are fitted at compile time. If you
change the DLL and other programs are using it, you should rename the new version; or
alternatively, you should re-compile all programs based on that DLL with the new
interface.
DCOM
Introduction

Welcome to this tutorial. In this series, I will strip the mystique, the headache, and
confusion from DCOM by giving you a comprehensive tutorial with a straight forward
example. OK, no promises - but I will give it a good try. In this series of articles, I will
show you how to use the following technologies:

• the ATL COM AppWizard;


• implementation of a Windows NT Service;
• MFC;
• ATL;
• smart pointers;
• Connection Points;
• MFC Support for Connection Points;
• MFC ClassWizard support for implementing Connection Points (yes, it's true!).

These will be used to develop a sample client/server system to say "Hello, world from <
machine >" to the user! I haven't heard of anybody needing to do client/server
development in order to say, "Hello, world!", but that's what I'm doing.

If you want to follow along with this tutorial and add code and use the Visual C++
Wizards as we go along, that's great. In fact, I very very highly recommend that, because
otherwise this tutorial is a big waste of electronic ink (?). However, I follow along exactly
with the tutorial myself, as I write it, and develop the code and use the Visual C++
wizards just as I say you should. The screenshots, in fact, are from my development of
the files for each step! To download this already-developed code to compare with your
own, simply click the 'Download the Step n Files - n KB" links at the top of each step.
There's also an archive of the files for all the steps at the Questions and Answers page for
this tutorial. I still recommend that you follow along with us as we go; this way, you can
learn while you code.

Environment

The tutorial's steps were completed by the author on a Windows NT 4, SP 5 network with
Visual C++ 6.0 SP 4. The author hasn't tested this on Windows 2000 because he doesn't
have a copy. If anything in a particular step of this tutorial doesn't work or works
differently under Windows 2000, please post a message to the message board. Pretty
much anything which works under Windows NT 4 SP 5 should work the same under
Windows 2000, because of backward-compatibility, unless Microsoft's been making
changes.

What This Tutorial Assumes

I have a motto: never write anything if it asks too much of the reader and not enough of
the writer. To this end, I am going to tell you straight away what this tutorial assumes that
you know. I would also include a 'Who Needs This Tutorial" section, much like they
include a "Who Needs to Read This Book," but I'll let you be the judge of whether or not
you need something.

This tutorial assumes that:

• You know how to use a Windows-based computer;


• You know how to use AppWizard;
• You're familiar with basic MFC programming;
• You've heard about ATL, COM, and DCOM;
• You know how Services work under Windows NT and Windows 2000;
• And that you're developing all this on Windows NT 4.0 or higher.

I hope I'm not assuming too much of you. I'll guide you along doing this at the same level
or a little higher as how Microsoft guided us all through the Scribble tutorial. That is, I
won't hold your hand too much, but I won't confuse you either (at least, I don't think
Microsoft's explanations are confusing...).

I welcome any and all input to how I explain things; to yell at me or praise me (I can take
both), post on the message boards at the bottom of this step. Believe me, I will get
your post. This way, people can see both your question, and my answer.

Conventions Used

Since we'll be referring to class names, symbol names, and such, it's good to follow a
convention with this so that everything is consistent. Here's what we'll do:

Code Fragments

Code fragments will appear in their own yellowish blocks on the page. The lines of new
code that you'll have to add will appear in bold. If you don't have to add anything, but I'm
just pointing some code out for my health, then nothing will be in bold:

void CClass::Func()
{
// NOTE: Add this code
CAnotherClass class; // And this code
class.AnotherFunc(); // And this code

// Done adding code


}
Fair enough? OK. How about when some functions are a bajillion lines long, and we're
only interested in the three lines at the top of the function? Then I'll use an ellipsis (. . .),
like the Visual C++ docs do. This just means that there's a bunch of code there that we'll
ignore:

void CClass::LongFunc()
{
...

HRESULT hResult = S_OK;

CClass class;
hResult = class.Func();

...
}

Variable Names

I confess, I am a big fan of Microsoft's version of Hungarian notation. I guess that's a


failing of mine, but I went through the Scribble tutorial until it got involved with all the
OLE stuff. This drilled the MS Hungarian notation into my brain, unfortunately. I am a
strong supporter of using m_ for member variables, C for classes, I for interfaces, and D
for dispinterfaces, or those things that we use to fire off connection point events:

• m_szDisplayName
• CHelloWorld
• IHelloWorld
• DHelloWorldEvents instead of _IHelloWorldEvents

Functions

When I refer to a function, method, or variable, I will show it in a fixed-width font.


Whenever I refer to a compiler keyword, I'll also put it in a fixed-width font. Symbol
names, regardless of who uses them, will be in the fixed-width font. Chris goes around
shaking his fingers at us writers and making sure that we follow these conventions. (Just
kidding, ha ha ha...) Here are some examples:

• Function()
• THIS_IS_A_SYMBOL
• ISayHello::SayHello()
• CServiceModule
• [IDL_attribute]
• __stdcall and typedef
• END_OBJECT_MAP()

Acknowledgements and Attributions


I would like to take just a quick second and acknowledge some sources and people which
led to this, because without their work and contributions, I might still be in the dark. I
would like to thank, in particular, Dr. Richard Grimes, who wrote the excellent book
Professional DCOM Programming. Dr. Grimes is a highly knowledgeable authority on
DCOM and COM programming and he has a talent for explaining things in a way that
they're easy to understand.

Professional DCOM Programming, published by Wrox Press, very thoroughly covers


DCOM with intelligent discussion, working samples, and demystification of the really
complicated stuff. By this, I mean threading, security, IDL, marshaling, and the Microsoft
Transaction Server, to name just a few. I highly recommend that you buy this book (it's
$49.95), you don't have to have read it in order to understand this tutorial.

Also, I would like to acknowledge the contributors to various articles and columns in
MSDN Magazine (formerly MSJ), whose work, reprinted in the MSDN Library, helped
me through the jungle of DCOM. This included columns by George Shepard and Don
Box, two very knowledgeable COM experts. Thanks also go to Charles Steinhardt, and
Tony Smith, two authors who have written for Visual C++ Developer.

The Approach

We will proceed step by step, following the methodology employed by Microsoft for
writing the explanation of the Scribble tutorial. In this tutorial, you will develop:

• A DCOM server, implemented as a Windows NT Service. This server will


expose a 'SayHello()' method which will say hello to the client user with a
connection point.
• An MFC client, with support for smart pointers and connection points made
easy! We'll even use ClassWizard to implement the connection point handling
(!).

Before we plunge into writing any software, it's always good to design (just a little!) what
it's going to do! What we'll develop in this tutorial will work as shown in Figure 1 below:
Figure 1. The way things will work.

As you can see in Figure 1, our software will follow a simple pattern:

1. The client will call a SayHello() method;


2. The server will say hello to the client by firing an event over the network;
3. The client will show the user a message in response to this event to indicate
that the server did indeed say, "Hello!"

Enough With the Blathering; On to the Code!!

Yes, yes; I get the hint. We'll start by doing Step 1 of the tutorial in this article, and then
you can go on to the next step of the tutorial by simply clicking the Next button at the
bottom of this page.

• Step 1: Create the server, HelloServ, using the ATL COM AppWizard.
• Step 2: Modify the starter files provided by AppWizard.
• Step 3: Use the New ATL Object Wizard to add a simple COM object, the
HelloWorld object, to the server.
• Step 4: Modify the IHelloWorld interface to include a SayHello() method.
• Step 5: Add an event method, OnSayHello(), to the connection point source
interface, DHelloWorldEvents.
• Step 6: Build the server, and install it on the server computer.
• Step 7: Create a MFC client, HelloCli, which calls the server and handles the
connection point event sink.

When the steps above are done, we'll have a living, breathing, fully functional DCOM
application. At the bottom of the page for each step of this tutorial are Back and Next
links, which you can use to go to the previous or next step. There's also a link which takes
you to the 'Questions and Answers' page.

The Questions and Answers page gives me more space and the capability to display
screenshots when I post answers to readers' questions. Feel free to post your question to
The Code Project's message board at the bottom of each step. If there's a question that
gets asked often or whose answer is not so simple, I'll see it and put it on the Questions
and Answers page. This way, the answers can be helpful to all of the readers of The Code
Project. I'll then reply to the question on the message board and e-mail the author saying
"I answered this question on the Questions And Answers page!".

I really must stop flapping my gums so much. Some people like to talk to hear
themselves talk; I think that, maybe I like to write to read my own writing... But anyway,
enough. Let's plunge in with Step 1.

Step 1: Create the Skeleton HelloServ Server With the ATL


COM AppWizard
OK; time to get started. Close anything you're working on in Visual C++ and save the
changes. Then close any open Workspace too. Next, click the File menu, and then click
New. This should bring up the New dialog box, as shown in Figure 2 below. Click the
ATL COM AppWizard in the list, and type 'HelloServ' for the Project Name:

Figure 2. Selecting the ATL COM AppWizard in the New dialog box.

When the settings look the way you want them to, click OK. This brings up Step 1 of the
ATL COM AppWizard (which only has one step). This is shown in Figure 3, below, with
the 'Service' option button selected. This is OK; since the AppWizard doesn't let us set
anything else, the next thing to do is to click Finish:
Figure 3. Selecting that we want AppWizard to give us a service.

Why A Service?

A Windows Service is a great type of process to have as your ATL/COM server. Windows
Services, in my experience, have performed better as DCOM servers. Services can live
while the machine is not logged on, whereas most EXE programs may not run. Also,
Services are totally non-interactive, which is just fine when all you want your
components to do is to perform routine system tasks, such as reading files or running
programs, or providing monitoring services. You don't want windows popping up all
willy-nilly, say, on your server computer sitting in a room in Ireland when you are
running the client over in India. Plus, services are able to be started and stopped, and kept
track of, using the Control Panel.

And for that matter, why an EXE and not DLL?

See above as far as the stand-alone EXE is concerned. A DLL is not very utilitarian as a
remote server, because all DLLs *must* be mapped into the address space of an EXE
process on the server system. A special EXE process which maps DCOM server DLLs
into its address space, and then remotes the DLLs' component(s), is known as a
surrogate. DLLs and surrogates are altogether complicated beasts to maintain and
configure when you need remote access to your components. Especially since these are
not reference-counted or monitored by the system, in case the number of clients drops to
zero or if the surrogate hangs, leaving you dead in the water. So a Service is my favorite
choice.

Finishing Up

After you click Finish, the AppWizard displays the New Project Information dialog box,
shown in Figure 4. The New Project Information box only has a little bit of information
in it; it just tells us that AppWizard is going to give us the starting point of a brand-new
Windows NT Service which is also a DCOM server. However, this service doesn't have
any COM objects yet. We'll add those in Step 3.

Figure 4. The New Project Information dialog box.

Click OK to have AppWizard generate the HelloServ starter program. Now, we are
ready to go on to the next step, modifying the source files. We'll start with the modifying
in Step 2, which is the next step of this tutorial. Click the Next link below to proceed to
Step 2.
Introduction

Welcome to Step 2 of our DCOM tutorial. In this series, I will strip the mystique, the
headache, and confusion from DCOM by giving you a comprehensive tutorial with a
straightforward example. OK, no promises -- but I will give it a good try.

If you want to follow along with this tutorial and add code and use the Visual C++
Wizards as we go along, that's great. In fact, I very very highly recommend that, because
otherwise this tutorial is a big waste of electronic ink (?). However, I follow along exactly
with the tutorial myself, as I write it, and develop the code and use the Visual C++
wizards just as I say you should. The screenshots, in fact, are from my development of
the files for each step! To download this already-developed code to compare with your
own, simply click the 'Download the Step n Files - n KB" links at the top of each step.
There's also an archive of the files for all the steps at the Questions and Answers page for
this tutorial. I still recommend that you follow along with us as we go; this way, you can
learn while you code. If you ever have problems along the way with this tutorial, feel free
to:

• Send e-mail to me at brian@harttechservices.com.


• Post a message to the message board at the bottom of this page.
• Check out this tutorial's Questions and Answers page.

A diagram of how our software will eventually work is shown in Figure 1. The client
calls a method on the server, which then fires an event back to the client using a
connection point. This connection point's event sink is implemented in the client (using
MFC and ClassWizard!!!), and the client shows its user a message telling the user that the
server said "Hello!":
Figure 1. Diagram of our DCOM client/server set-up.

Remember, our steps in developing the software in this tutorial are as follows:

• Step 1: Create the server, HelloServ, using the ATL COM AppWizard.
• Step 2: Modify the starter files provided by AppWizard.
• Step 3: Use the New ATL Object Wizard to add a simple COM object, the
HelloWorld object, to the server.
• Step 4: Modify the IHelloWorld interface to include a SayHello() method.
• Step 5: Add an event method, OnSayHello(), to the connection point source
interface, DHelloWorldEvents.
• Step 6: Build the server, and install it on the server computer.
• Step 7: Create a MFC client, HelloCli, which calls the server and handles the
connection point event sink.

We're currently on Step 2 of the tutorial, where we modify some things in the starter
source code provided to us by the ATL COM AppWizard. We do this to:

• Provide a user-friendly "Display Name" for this service;


• Add code to initialize security properly.

Step 2: Modify the Starter Files Provided by AppWizard

To start, we need to provide Windows with a user-friendly name it can use to display this
service to the user. This is called the "Display Name" of the service. AppWizard already
set up and specified a "Service Name" for us: HelloServ. For the user's sake, let's come
up with something which is easier to understand: Hello World Server. To do this, we'll
add a m_szDisplayName member variable to the CServiceModule class, and a String
Table entry to hold the display name. We do things this way so that it's easy for us to
change the display name of the service to whatever we wish.

First, double-click on the CServiceModule icon in ClassView. This will jump you to
where the CServiceModule class is declared, in STDAFX.H. We need to add a
m_szDisplayName member variable to the class. Move the cursor to the // data
members section, and add the code shown below in bold:

class CServiceModule : public CComModule


{
...

// data members
public:
TCHAR m_szDisplayName[256]; // display name of service
TCHAR m_szServiceName[256];

...
};
Listing 1. Adding the m_szDisplayName member variable to the CServiceModule
class.

The next thing to do is to add an entry to the String Table, IDS_DISPLAY_NAME, to hold
the display name we want to use. Figure 2, shown below, illustrates adding the String
Table entry. To do this, complete these steps:

1. Click the ResourceView tab.


2. Double-click the String Table folder to open it.
3. Double-click the String Table icon in the folder to open the String Table.
4. Find the blank entry in the list, and double-click it. The Properties window
appears, as in Figure 2, below.
5. In the ID box, type IDS_DISPLAY_NAME.
6. Press TAB to move to the Caption box, which contains what the
IDS_DISPLAY_NAME symbol maps to in your code.
7. In the Caption box, type Hello World Server, as shown in Figure 2.
8. Press Enter. This saves the new String Table entry to the String Table.
Figure 2. Adding the IDS_DISPLAY_NAME entry to the String Table.

On to the next part of Step 2. We have a member variable, m_szDisplayName, which we


want to fill with the contents of the String Table entry IDS_DISPLAY_NAME. To do this,
double-click the CServiceModule::Init() function in ClassView, and then add the line
shown in bold:

inline void CServiceModule::Init(_ATL_OBJMAP_ENTRY* p, HINSTANCE h,


UINT nServiceNameID, const GUID* plibid)
{
...

LoadString(h, nServiceNameID, m_szServiceName,


sizeof(m_szServiceName) / sizeof(TCHAR));

LoadString(h, IDS_DISPLAY_NAME, m_szDisplayName,


sizeof(m_szDisplayName) / sizeof(TCHAR));

...
}
Listing 2. Adding a LoadString() call to CServiceModule::Init().

Now we have made sure that the IDS_DISPLAY_NAME string gets loaded into the
m_szDisplayName member variable of CServiceModule. The next thing to do is to
change the call to CreateService() in the CServiceModule::Install() function. The
call is shown in Listing 3 below in bold. The call is again shown in Listing 4, also
below, but this time the argument which you need to replace is shown in bold:
inline BOOL CServiceModule::Install()
{
...

SC_HANDLE hService = ::CreateService(


hSCM, m_szServiceName, m_szServiceName,
SERVICE_ALL_ACCESS, SERVICE_WIN32_OWN_PROCESS,
SERVICE_DEMAND_START, SERVICE_ERROR_NORMAL,
szFilePath, NULL, NULL, _T("RPCSS\0"), NULL, NULL);

...
}
Listing 3. The call to the Windows CreateService() function, as called by
AppWizard.

Change the second passing of m_szServiceName to m_szDisplayName, as shown in


Listing 4:

inline BOOL CServiceModule::Install()


{
...

SC_HANDLE hService = ::CreateService(


hSCM, m_szServiceName, m_szDisplayName,
SERVICE_ALL_ACCESS, SERVICE_WIN32_OWN_PROCESS,
SERVICE_DEMAND_START, SERVICE_ERROR_NORMAL,
szFilePath, NULL, NULL, _T("RPCSS\0"), NULL, NULL);

...
}
Listing 4. Changing the second m_szServiceName to m_szDisplayName.

Finally, the last part of Step 2 is to initialize everything properly. Go to the place in the
HELLOSERV.CPP file shown // PLACE THE CURSOR HERE comment in Listing 5:

#include "stdafx.h"
#include "resource.h"
#include < initguid.h >
#include "HelloServ.h"

#include "HelloServ_i.c"

#include < stdio.h >

CServiceModule _Module;

BEGIN_OBJECT_MAP(ObjectMap)
END_OBJECT_MAP()

// PLACE THE CURSOR HERE

LPCTSTR FindOneOf(LPCTSTR p1, LPCTSTR p2)


{
...
Listing 5. Where in the HELLOSERV.CPP file to place the cursor.

Add all of the following code at where I've just told you to place the cursor:

extern "C" BOOL WINAPI InitApplication()


{
HRESULT hResult = CoInitialize(NULL);
if (FAILED(hResult))
return FALSE; // failed to initialize COM

// Turn security off so that everyone has access to us


CoInitializeSecurity(NULL, -1, NULL, NULL, RPC_C_AUTHN_LEVEL_NONE,
RPC_C_IMP_LEVEL_IMPERSONATE, NULL, EOAC_NONE, NULL);

// Initialization successful
return TRUE;
}
Listing 6. Adding the InitApplication() function to the server. Next, we need to
replace some of the code in CServiceModule::Run() with a call to the
InitApplication() function which we added in Listing 6 above. Using ClassView,
go to the CServiceModule::Run() function, and delete the code shown in bold:
void CServiceModule::Run()
{
...

HRESULT hr = CoInitialize(NULL);
// If you are running on NT 4.0 or higher you can use the following
call
// instead to make the EXE free threaded.
// This means that calls come in on a random RPC thread
// HRESULT hr = CoInitializeEx(NULL, COINIT_MULTITHREADED);

_ASSERTE(SUCCEEDED(hr));

// This provides a NULL DACL which will allow access to everyone.


CSecurityDescriptor sd;
sd.InitializeFromThreadToken();
hr = CoInitializeSecurity(sd, -1, NULL, NULL,
RPC_C_AUTHN_LEVEL_PKT, RPC_C_IMP_LEVEL_IMPERSONATE, NULL,
EOAC_NONE, NULL);
_ASSERTE(SUCCEEDED(hr));

...
}
Listing 7. Code to delete from the implementation of CServiceModule::Run().

Now replace what I told you to delete with the code shown in Listing 8 below. The code
to add is shown in bold:

void CServiceModule::Run()
{
...
HRESULT hr = S_OK;

if (!InitApplication())
return;

...
}
Listing 8. Code to add in order to replace what we've deleted.

Notes From the Rear

We've now completed Step 2 of this tutorial. We added a display name to help the user,
and we fixed the security-initialization code for the service. Click Next to move on to the
next step of this tutorial, Step 3, click Back to go back to Step 1 of this tutorial, or click
the Questions and Answers button to jump to the Questions and Answers page! Good
luck.

Introduction

Welcome to Step 3 of our DCOM tutorial. In this series, I will strip the mystique, the
headache, and confusion from DCOM by giving you a comprehensive tutorial with a
straightforward example. OK, no promises -- but I will give it a good try.

If you want to follow along with this tutorial and add code and use the Visual C++
Wizards as we go along, that's great. In fact, I very very highly recommend that, because
otherwise this tutorial is a big waste of electronic ink (?). However, I follow along exactly
with the tutorial myself, as I write it, and develop the code and use the Visual C++
wizards just as I say you should. The screenshots, in fact, are from my development of
the files for each step! To download this already-developed code to compare with your
own, simply click the 'Download the Step n Files - n KB" links at the top of each step.
There's also an archive of the files for all the steps at the Questions and Answers page for
this tutorial. I still recommend that you follow along with us as we go; this way, you can
learn while you code. If you ever have problems along the way with this tutorial, feel free
to:

• Send e-mail to me at brian@harttechservices.com.


• Post a message to the message board at the bottom of this page.
• Check out this tutorial's Questions and Answers page.
A diagram of how our software will eventually work is shown in Figure 1. The client
calls a method on the server, which then fires an event back to the client using a
connection point. This connection point's event sink is implemented in the client (using
MFC and ClassWizard!!!), and the client shows its user a message telling the user that the
server said "Hello!":

Figure 1. Diagram of our DCOM client/server set-up.

Remember, our steps in developing the software in this tutorial are as follows:

• Step 1: Create the server, HelloServ, using the ATL COM AppWizard.
• Step 2: Modify the starter files provided by AppWizard.
• Step 3: Use the New ATL Object Wizard to add a simple COM object, the
HelloWorld object, to the server.
• Step 4: Modify the IHelloWorld interface to include a SayHello() method.
• Step 5: Add an event method, OnSayHello(), to the connection point source
interface, DHelloWorldEvents.
• Step 6: Build the server, and install it on the server computer.
• Step 7: Create a MFC client, HelloCli, which calls the server and handles the
connection point event sink.

We're currently on Step 3 of this tutorial, where we will use the ATL Object Wizard to
add a simple COM object, the HelloWorld object, to the server. This step will go by real
fast, so instead of my blathering any further, let's plunge in:

Step 3: Add a Simple HelloWorld COM Object to the Server

To add COM objects to ATL servers, we can either use the New ATL Object Wizard
provided by Visual C++, or we can add code by hand. I prefer to use the Wizards
provided by Visual C++ whenever I can, but then again, I'm lazy :)

Let's proceed. Open ClassView, and then right-click on the 'HelloServ classes' text at the
very top, and then click New ATL Object on the menu. The New ATL Object Wizard
appears, as shown in Figure 2, below. All we want for this tutorial is a simple COM
object, so click the Simple COM Object icon, and then click Next.

Figure 2. The New ATL Object Wizard with Simple COM Object selected.

After you've clicked Next, the ATL Object Wizard Properties dialog box should appear.
The cursor starts off with being in the Short Name text box. Type HelloWorld in this
box; as you type, the other fields are filled in automatically. Figure 3 below shows how
everything should look when you're done. Don't click OK yet, though!

Figure 3. The ATL Object Wizard Properties dialog with the Names tab filled in.

Now, click on the Attributes tab of the ATL Object Wizard Properties dialog box. Make
these selections:

• Under Threading Model, select Apartment.


• Under Interface, select Custom.
• Under Aggregation, select No.
• Check the Support Connection Points check box.

When everything is set correctly, the Attributes tab should appear as that in Figure 4,
below. When everything's ready, click OK to have the New ATL Object Wizard generate
code for us and add our new COM object to the server.

Figure 4. The Attributes tab of the ATL Object Wizard Properties dialog when we're done
changing settings.

When everything's been added, ClassView should look like that shown in Figure 5. There
is still one more change we need to make, though. Notice that, in Figure 5, the name of
the event interface (for the connection point) is not _IHelloWorldEvents but instead
DHelloWorldEvents? This is the result of a little change I made to the code.

Figure 5. ClassView after adding the HelloWorld object and changing the name of the
event interface.
The DHelloWorldEvents name for our event interface is a loose convention of
Microsoft's which I'm following. DHelloWorldEvents is a dispinterface, so a D
belongs in front of its name. Make sense? Good. Double-click the _IHelloWorldEvents
icon in ClassView. This jumps you to the project's IDL source code, which declares all of
the OLE stuff that our project uses. Next, replace all instances of _IHelloWorldEvents,
highlighted in bold in Listing 1 below, and change them to DHelloWorldEvents,
highlighted in bold in Listing 2 below.

[
...
]
library HELLOSERVLib
{
importlib("stdole32.tlb");
importlib("stdole2.tlb");

[
uuid(...),
helpstring("_IHelloWorldEvents event interface")
]
dispinterface _IHelloWorldEvents
{
...
};

[
uuid(...),
helpstring("HelloWorld Class")
]
coclass HelloWorld
{
[default] interface IHelloWorld;
[default, source] dispinterface _IHelloWorldEvents;
};
};
Listing 1. The instances of the _IHelloWorldEvents interface, which we need to
replace with DHelloWorldEvents:
[
...
]
library HELLOSERVLib
{
importlib("stdole32.tlb");
importlib("stdole2.tlb");

[
uuid(...),
helpstring("DHelloWorldEvents event interface")
]
dispinterface DHelloWorldEvents
{
...
};
[
uuid(...),
helpstring("HelloWorld Class")
]
coclass HelloWorld
{
[default] interface IHelloWorld;
[default, source] dispinterface DHelloWorldEvents;
};
};
Listing 2. The _IHelloWorldEvents name replaced with DHelloWorldEvents.

Notes From the Rear

That's it! We're finished with Step 3! See, that wasn't so bad, was it? Once you've made
the changes specified in the two Listings above to the IDL source code, click Save All on
the Visual C++ Toolbar. This should save changes to all your project files, and ClassView
should match Figure 5.

You're now ready to proceed to Step 4. To do so, either click the link preceeding this
sentence, or click Next below. You can find the source code from this step of the tutorial
available from the Download Link above. To go back to Step 2 of this tutorial, click Back
below.

Introduction

Welcome to Step 4 of our DCOM tutorial. In this series, I will strip the mystique, the
headache, and confusion from DCOM by giving you a comprehensive tutorial with a
straightforward example. OK, no promises -- but I will give it a good try.

If you want to follow along with this tutorial and add code and use the Visual C++
Wizards as we go along, that's great. In fact, I very very highly recommend that, because
otherwise this tutorial is a big waste of electronic ink (?). However, I follow along exactly
with the tutorial myself, as I write it, and develop the code and use the Visual C++
wizards just as I say you should. The screenshots, in fact, are from my development of
the files for each step! To download this already-developed code to compare with your
own, simply click the 'Download the Step n Files - n KB" links at the top of each step.
There's also an archive of the files for all the steps at the Questions and Answers page for
this tutorial. I still recommend that you follow along with us as we go; this way, you can
learn while you code. If you ever have problems along the way with this tutorial, feel free
to:

• Send e-mail to me at brian@harttechservices.com.


• Post a message to the message board at the bottom of this page.
• Check out this tutorial's Questions and Answers page.

Remember, our steps in developing the software in this tutorial are as follows:

• Step 1: Create the server, HelloServ, using the ATL COM AppWizard.
• Step 2: Modify the starter files provided by AppWizard.
• Step 3: Use the New ATL Object Wizard to add a simple COM object, the
HelloWorld object, to the server.
• Step 4: Modify the IHelloWorld interface to include a SayHello() method.
• Step 5: Add an event method, OnSayHello(), to the connection point source
interface, DHelloWorldEvents.
• Step 6: Build the server, and install it on the server computer.
• Step 7: Create a MFC client, HelloCli, which calls the server and handles the
connection point event sink.

We're currently on Step 4 of this tutorial, where we finally add working code to our
DCOM server. We'll add a method to the IHelloWorld interface, and we'll call this
method SayHello(). This method will get the network name of the host that it's
executing on, plus it will call a as-yet-unimplemented function Fire_OnSayHello(),
which in Step 5 we'll add as an event to our DHelloWorldEvents event interface. This
function will take a single [in] BSTR parameter, the name of the host. Anyway, enough
of my jabber; let's plunge in:

Step 4: Modify the IHelloWorld Interface to Add the SayHello()


Method

This step of the tutorial is really short. All we will do is add one method to our
ISayHello interface, and implement it using the CHelloWorld ATL class. Then we'll be
ready to move on to Step 5! Since the user of our client would like to have some
indication as to what computer on their network this code ran on, we'll add some code to
get the network name of that computer. The following listing, Listing 1, shows a piece of
code which you can cut-and-paste into any application you wish. This code calls the
Windows GetComputerName() function:

TCHAR szComputerName[MAX_COMPUTERNAME_LENGTH + 1];


DWORD dwSize = MAX_COMPUTERNAME_LENGTH + 1;

if (!GetComputerName(szComputerName, &dwSize))
{
// Display the cause of the error to the user with the
_com_error class
// To use the _com_error class, you need to #include <comdef.h>
in
// your STDAFX.H file

AfxMessageBox(_com_error(GetLastError()).ErrorMessage(),
MB_ICONSTOP);
return /*whatever error code: -1 or E_FAIL or whatnot here*/;
}

// Now szComputerName holds this computer's name


Listing 1. Calling the GetComputerName() function.

Let's now add the IHelloWorld::SayHello() method, and then add its code. To do this,
right-click the IHelloWorld interface icon in ClassView, and click Add Method. The Add
Method to Interface dialog box appears. Type SayHello in the Method Name box, and
leave the Return Type set to HRESULT.

TIP: When doing DCOM programming and adding methods to interfaces, *always*
set the Return Type of the method to HRESULT.
This allows DCOM to report network errors and other status to the client.

Anyway, getting back to what we're doing, when you're done filling in the Add Method to
Interface dialog box, it should look like that shown in Figure 1, below:

Figure 1. Adding the SayHello() method to the IHelloWorld interface.

Click OK. When you do, the Add Method to Interface dialog will add code in all the right
places to make sure that when a call to the IHelloWorld::SayHello() method comes in
over the wire, the CHelloWorld::SayHello() member function will get called and
executed. After the method has been added, ClassView should resemble that shown in
Figure 2 below:
Figure 2. ClassView after the SayHello() method has been added.

Look at Figure 2. See the highlighted item? Double-click that item in your ClassView to
open up the CHelloWorld::SayHello() member function. This function implements the
IHelloWorld::SayHello() method. Let's add some code, shown here in bold, to
implement the method:

STDMETHODIMP CHelloWorld::SayHello()
{
// Get the network name of this computer
TCHAR szComputerName[MAX_COMPUTERNAME_LENGTH + 1];
DWORD dwSize = MAX_COMPUTERNAME_LENGTH + 1;

if (!GetComputerName(szComputerName, &dwSize))
return E_FAIL; // failed to get the name of this computer

// TODO: Add more code here

return S_OK;
}
Listing 2. Adding code to implement the SayHello() method.

Notes From the Rear

Notice the line which says // TODO: Add more code here? This tells us that
we're not done implementing yet; according to the design, this method should fire off
some sort of event back to the client. To see how to do this, click Next to advance to Step
5, where we finish the implementation of the server and get ready to move on to the
client. To go to the previous step, Step 3, click Back below. If you have questions, try
clicking Questions and Answers to go to a page which might help you.
Introduction

Welcome to Step 5 of our DCOM tutorial. In this series, I will strip the mystique, the
headache, and confusion from DCOM by giving you a comprehensive tutorial with a
straightforward example. OK, no promises -- but I will give it a good try.

If you want to follow along with this tutorial and add code and use the Visual C++
Wizards as we go along, that's great. In fact, I very very highly recommend that, because
otherwise this tutorial is a big waste of electronic ink (?). However, I follow along exactly
with the tutorial myself, as I write it, and develop the code and use the Visual C++
wizards just as I say you should. The screenshots, in fact, are from my development of
the files for each step! To download this already-developed code to compare with your
own, simply click the 'Download the Step n Files - n KB" links at the top of each step.
There's also an archive of the files for all the steps at the Questions and Answers page for
this tutorial. I still recommend that you follow along with us as we go; this way, you can
learn while you code. If you ever have problems along the way with this tutorial, feel free
to:

• Send e-mail to me at brian@harttechservices.com.


• Post a message to the message board at the bottom of this page.
• Check out this tutorial's Questions and Answers page.

Connection Points Demystified

Before we plunge in with Step 5 of our tutorial, let's just take a moment for me to rip the
shrouds of mystery off of Connection Points. Figure 1 below shows a generic scenario
which is true for COM, DCOM, and even function call backs, for goodness' sake.
Figure 1. A source and a sink.

This involves two objects, a "source" and a "sink". Think of the "source" like the water
faucet of the kitchen sink at home. You turn a handle, and stuff comes out of it (hopefully
water). Where does it go? If nothing's backed up, this water flows down into the bottom
and goes into the drain (which can be thought of as the "sink"). OK, so things flow from
the source, to the sink. In the kitchen sink analogy above, this is water. However, I've
never seen a computer network system run with water flowing across the wires, so
obviously something else is at work in DCOM.

In DCOM, there is a "client," somewhere on the network, and there is a "server," also
somewhere on the network. Without the use of connection points, things flow only one
way: method calls replace our water, the client replaces our faucet, and the server
replaces the drain. This is way oversimplifying things, but the user "turns a handle" (that
is, clicks a button, for example), and "stuff" (that is, method calls) "comes out of" the
client. This "stuff that comes out" then "flows" over the network using DCOM. These
calls "flow" to the server, which then collects them and acts like the "drain", or our sink.
Here's Figure 2, which is almost exactly like Figure 1, but puts the client in place of the
"source" and the server in place of the "sink," with the network in between:

Figure 2. Our client and server as the source and the sink.

OK, so now we have method calls flowing like water; wonderful. However, when the
client calls methods, the server does all kinds of things that might be interesting to
clients. So the server fires events all over the place. If our client doesn't care if the server
fires events, it will just ignore them. However, if it cares, it will Advise() the server.
Then the source-sink relationship of Figure 2 can be thought of in reverse:

Figure 3. The reverse of Figure 2.

Connection points come in when you have the following happening:

1. The client is the source of a method call,


2. The server sinks (that is, acts as the sink for) the method call.
3. An "event call" comes out of the now-server-as-source.
4. The client sinks the event call and does something.

As you can see, this is a round-trip. A method call goes from the client to the server, and
then an event call goes from the server, to the client, as seen in Figure 4.

Figure 4. A round-trip.

The Advise() step is done before item number 1 above, and the Unadvise() step (where
the client goes back to being aloof) happens after item number 4. The points of contact on
both the client and server and the Advise()ing and Unadvise()ing that happens all
together form...

A CONNECTION POINT!!
Whew... what a revelation... Let's start Step 5 before I get too carried away...

Step 5: Add the OnSayHello Event to the Event Source


Interface DHelloWorldEvents

Let's plunge in, shall we? To add an event to the source, it's really, really easy. Just use
the Visual C++ Wizards! Open up ClassView, and right-click the DHelloWorldEvents
icon, and then click Add Method. The Add Method to Interface dialog box appears. Type
OnSayHello in the Method Name box, and type [in] BSTR bstrHost in the Parameters
box, as shown in Figure 5, below.

Figure 5. Adding the OnSayHello() event to the DHelloWorldEvents event interface.

Once you're done, click OK. ClassView should resemble Figure 6 below. Now click
FileView, and find the HelloServ.idl file, under the Source Files folder. Right-click
that baby, and then choose Compile. Watch the compiler work away in the Output
window, and wait until the build is complete.
Figure 6. ClassView after adding the OnSayHello event.

Once the build has been finished, click on ClassView. Right-click the CHelloWorld class,
and then click Implement Connection Point. The Implement Connection Point dialog box
appears. If you haven't compiled the IDL file yet like I told you to, Visual C++ will
prompt you to do so. Figure 7, below, shows you how to select that you want to make the
server able to fire off its OnSayHello event:

Figure 7. Specifying that we want to implement Connection Points for the


DHelloWorldEvents event interface.

When everything looks like Figure 7, click OK. The Visual C++ IDE will now generate
all the server-side code you need for Connection Points. Each time you change an event
in the DHelloWorldEvents event interface, you need to do the (1) compile the IDL, (2)
right-click CHelloWorld and choose Implement Connection Point, (3) check the box by
DHelloWorldEvents, and (4) click OK steps.

Notes From the Rear

The final part of Step 5 which we have to take care of is firing the event. Remember, we
declared the OnSayHello() event in the IDL file as:

HRESULT OnSayHello(BSTR bstrHost);


Listing 1. The declaration of the OnSayHello() event.

To fire the event from any CHelloWorld member function, just call Fire_OnSayHello().
It's a member function of a new base class, CProxyDHelloWorldEvents< > that the
Implement Connection Points dialog box just added for us. To this end, let's add code to
the CHelloWorld::SayHello() function to fire the event to the client:

STDMETHODIMP CHelloWorld::SayHello()
{
USES_CONVERSION;

// Get the network name of this computer


TCHAR szComputerName[MAX_COMPUTERNAME_LENGTH + 1];
DWORD dwSize = MAX_COMPUTERNAME_LENGTH + 1;

if (!GetComputerName(szComputerName, &dwSize))
return E_FAIL; // failed to get the name of this computer

// Say Hello to the client


Fire_OnSayHello(T2OLE(szComputerName));

return S_OK;
}
Listing 2. Code to add to finish the CHelloWorld::SayHello() member function.

That's it! We're finished with Step 5. Click Next to go on to Step 6, or click Back to step
back to Step 4 if you're browsing through the tutorial. If you have any questions, try
clicking on Questions and Answers to go to the page with the good stuff, and then e-mail
me at brian@harttechservices.com if you're still stuck.
Introduction

Welcome to Step 6 of our DCOM tutorial. In this series, I will strip the mystique, the
headache, and confusion from DCOM by giving you a comprehensive tutorial with a
straightforward example. OK, no promises -- but I will give it a good try.

If you want to follow along with this tutorial and add code and use the Visual C++
Wizards as we go along, that's great. In fact, I very very highly recommend that, because
otherwise this tutorial is a big waste of electronic ink (?). However, I follow along exactly
with the tutorial myself, as I write it, and develop the code and use the Visual C++
wizards just as I say you should. The screenshots, in fact, are from my development of
the files for each step! To download this already-developed code to compare with your
own, simply click the 'Download the Step n Files - n KB" links at the top of each step.
There's also an archive of the files for all the steps at the Questions and Answers page for
this tutorial. I still recommend that you follow along with me as we go; this way, you can
learn while you code. If you ever have problems along the way with this tutorial, feel free
to:

• Send an e-mail to me at brian@harttechservices.com.


• Post a message to the message board at the bottom of this page.
• Check out this tutorial's Questions and Answers page.

Remember, our steps in developing the software in this tutorial are as follows:

• Step 1: Create the server, HelloServ, using the ATL COM AppWizard.
• Step 2: Modify the starter files provided by the AppWizard.
• Step 3: Use the New ATL Object Wizard to add a simple COM object, the
HelloWorld object, to the server.
• Step 4: Modify the IHelloWorld interface to include a SayHello() method.
• Step 5: Add an event method, OnSayHello(), to the connection point source
interface, DHelloWorldEvents.
• Step 6: Build the server, and install it on the server computer.
• Step 7: Create a MFC client, HelloCli, which calls the server and handles the
connection point event sink.

We're currently on Step 6 of this tutorial, where we build the server and also build and
register the Proxy-Stub DLL which goes along with it. Let's plunge in:

Step 6: Build the Server and Install It on the Server


Computer

When you have reached this step, it's time to build our DCOM server, which is
implemented as a Windows NT Service. Before we click that Build button, there are
some things to do first. We start by making a few changes to the project settings, add a
Custom Build Step to the project in order to build and register our Proxy-Stub DLL, and
then we will make sure and change the configuration we're using. After doing all of this,
we will be ready to click the Build button.

To change the project settings, click the Project menu, and then click Settings. Click the
Custom Build tab; scroll over if you can't see it. Make sure you select the Win32 Release
MinDependency configuration in the Settings For drop-down. After you've done that,
erase everything in all of the fields of the Custom Build tab, so that what you have
matches Figure 1 below:

Figure 1. Removing the Custom Build step in the Project Settings dialog box.

Next, click the Post-Build Step tab. Again, before making changes, make sure you have
the Win32 Release MinDependency configuration selected in the Settings For
dropdown, as illustrated by Figure 2:
Figure 2. Making sure that Win32 Release MinDependency is selected.

Then type Building and registering Proxy-Stub DLL... in the Post-Build Description box,
and then type the following lines into the Post-Build Command(s) area, to match Figure
3:

start /wait nmake -f HelloServps.mk


regsvr32 HelloServps.dll
Figure 3. Specifying the Post-Build Step settings.

The last thing to make sure to do before beginning the build is to make sure that the right
configuration is the active configuration. In our case, this is the Win32 Release
MinDependency configuration. Click Build on the menu bar, and then click Set Active
Configuration. This brings up the Set Active Configuration dialog box, as shown in
Figure 4. Click the HelloServ - Win32 Release MinDependency entry in the listbox, and
then click OK.

Figure 4. Selecting the Win32 Release MinDependency configuration.


Building and Installing the Server, and Other Notes From
the Rear

At last! Now we're ready to build. Click that good ol' Build button on the toolbar, and
watch the magic happen. When everything's done, you should have a HelloServ.exe EXE
file in the \ReleaseMinDepenedency subfolder of the project, and you should also see a
HelloServps.dll DLL in the main project directory. Copy those two files to a floppy disk,
and then put those in the C:\Winnt\System32\ directory on the computer you want to use
as the server. Make sure the server machine is running Windows NT 4.0 Workstation or
Server, or Windows 2000. Then, using the Run dialog box from the Start menu, run the
following command lines, in this order:

1. HelloServ /Service
2. regsvr32 HelloServps.dll

Now, we'll use your development machine (the one you're following this tutorial with) as
the client computer. To proceed, however, we'll need to follow these steps if the client
machine is running either Windows NT 4.0 or Windows 2000:

1. Copy the HelloServ.exe and HelloServps.dll files from the floppy disk to the
C:\Winnt\System32\ directory of the client machine.
2. Click the Start button, and then click Run.
3. Run the command line HelloServ /Service.
4. Click the Start button, and then click Run again.
5. Run the command line regsvr32 HelloServps.dll.

If your client machine is not running either Windows NT or Windows 2000, then you
have to follow these steps:

1. Make sure that the DCOM98 extensions, available here, are installed.
2. Copy the HelloServ.exe and HelloServps.dll files from the floppy disk to the
C:\Windows\System directory of the client machine.
3. Click the Start button, and then click Run.
4. Run the command line HelloServ /RegServer.
5. Click the Start button, and then click Run again.
6. Run the command line regsvr32 HelloServps.dll.

Now we are ready to proceed with Step 7. To move to Step 7, click Next below. If you
need to go back to Step 5, click Back. If you have questions or problems, try clicking
Questions and Answers below to jump to a page which offers some help.


Figure 1. Image of the HelloCli sample program.

Introduction

Welcome to Step 7 of our DCOM tutorial. This is the last step!


If you want to follow along with this tutorial and add code and use the Visual C++
Wizards as we go along, that's great. In fact, I very very highly recommend that, because
otherwise this tutorial is a big waste of electronic ink (?). However, I follow along exactly
with the tutorial myself, as I write it, and develop the code and use the Visual C++
wizards just as I say you should. The screenshots, in fact, are from my development of
the files for each step! To download this already-developed code to compare with your
own, simply click the 'Download the Step n Files - n KB" links at the top of each step.
There's also an archive of the files for all the steps at the Questions and Answers page for
this tutorial. I still recommend that you follow along with us as we go; this way, you can
learn while you code. If you ever have problems along the way with this tutorial, feel free
to:

• Send e-mail to me at brian@harttechservices.com.


• Post a message to the message board at the bottom of this page.
• Check out this tutorial's Questions and Answers page.
Remember, our steps in developing the software in this tutorial are as follows:

• Step 1: Create the server, HelloServ, using the ATL COM AppWizard.
• Step 2: Modify the starter files provided by AppWizard.
• Step 3: Use the New ATL Object Wizard to add a simple COM object, the
HelloWorld object, to the server.
• Step 4: Modify the IHelloWorld interface to include a SayHello() method.
• Step 5: Add an event method, OnSayHello(), to the connection point source
interface, DHelloWorldEvents.
• Step 6: Build the server, and install it on the server computer.
• Step 7: Create a MFC client, HelloCli, which calls the server and handles the
connection point event sink.

We're currently on Step 7 of this tutorial (the last!), where we put together a little MFC
program, called HelloCli, to test our server. Let's plunge in:

Step 7: Create a MFC HelloCli Client to Test the Server

As you can see from the screenshot above, I built a dialog-based application using MFC
AppWizard (EXE). I added a status window to report status, so I can see just where errors
occurred, and I also successfully handled Connection Points. To add text to the status
window, which is just an Edit control with the read-only style set, I added a CString
member variable, m_strStatus to the dialog class with ClassWizard, and then anytime I
needed to add a message line to the edit control, it was made easy with this code:

m_strStatus += "This is a status line for the edit control\r\n";


UpdateData(FALSE);
Listing 1. Adding a status line to the edit control.

We add on text to the contents of m_strStatus with the += operator of CString, and then
we call UpdateData(FALSE) to move the contents of the member variable from the
variable to the edit control.

To start my sample project, I brought up the New dialog box, clicked 'MFC AppWizard
(EXE)' in the list, and then typed 'HelloCli' for the name of my project. After
completing AppWizard, I opened the STDAFX.H file and added the line shown in bold in
Listing 2:

#if !
defined(AFX_STDAFX_H__8495B5E0_67FF_11D4_A358_00104B732442__INCLUDED_)
#define AFX_STDAFX_H__8495B5E0_67FF_11D4_A358_00104B732442__INCLUDED_

#if _MSC_VER > 1000


#pragma once
#endif // _MSC_VER > 1000

#define VC_EXTRALEAN // Exclude rarely-used stuff from Windows


headers
#define _WIN32_WINNT 0x0400

#include < afxwin.h > // MFC core and standard components


#include < afxext.h > // MFC extensions
#include < afxdisp.h > // MFC Automation classes
#include < afxdtctl.h > // MFC support for Internet Explorer 4
Common Controls
#ifndef _AFX_NO_AFXCMN_SUPPORT
#include < afxcmn.h > // MFC support for Windows Common
Controls
#endif // _AFX_NO_AFXCMN_SUPPORT

//{{AFX_INSERT_LOCATION}}
// Microsoft Visual C++ will insert additional declarations immediately
before
// the previous line.

#endif // !
defined(AFX_STDAFX_H__8495B5E0_67FF_11D4_A358_00104B732442__INCLUDED_)
Listing 2. Adding the #define _WIN32_WINNT line to STDAFX.H so that DCOM works.

The next thing to do is to add something to let the client know about the interfaces and
everything else that the server supports. There are two ways we can do this:

• Using #import to bring in the type library of the server. This file,
HelloServ.tlb, is produced by MIDL when it compiles the HelloServ.idl
file.
• Using #include "HelloServ.h" to just include the C++ and C declarations of
interfaces. This is nice, but then you have to also define, in your code, all the
GUIDs that the server responds to. These are CLSID_HelloWorld,
IID_IHelloWorld, DIID_DHelloWorldEvents, and LIBID_HELLOSERVLib. If
you use #import, this is done for you.

I like the idea of using #import, because of not only what was explained above, but
also because you get to use smart pointers with #import, too. Be careful, though; we
have a custom (that is, IUnknown-derived) interface that our server uses. We can use
#import just fine in this example since the IHelloWorld::SayHello() method takes
no parameters. If the IHelloWorld::SayHello() method took parameters, and they
weren't of OLE-Automation-compatible types, then we would have to skip using
#import, because it will only recognize those types. However, if you mark your custom
interface with the [oleautomation] attribute and use OLE Automation-compatible types
in your methods, this will work.

With custom interfaces, it's generally a better idea to use the second method above.
However, like I said earlier, we'll go ahead and use #import this time because our
method doesn't take any parameters. So this means that we need to copy the
HelloServ.tlb file to our HelloCli project folder from the HelloServ project folder,
and then add an #import line somewhere. How about in good ol' STDAFX.H again?
We'll also add #include lines for atlbase.h and afxctl.h, since these files give us
support for things we'll use later on. Doing all this in STDAFX.H will help us when we
build our program to keep the build time down, too:

#if !
defined(AFX_STDAFX_H__8495B5E0_67FF_11D4_A358_00104B732442__INCLUDED_)
#define AFX_STDAFX_H__8495B5E0_67FF_11D4_A358_00104B732442__INCLUDED_

#if _MSC_VER > 1000


#pragma once
#endif // _MSC_VER > 1000

#define VC_EXTRALEAN // Exclude rarely-used stuff from Windows


headers

#define _WIN32_WINNT 0x0400

#include < afxwin.h > // MFC core and standard components


#include < afxext.h > // MFC extensions
#include < afxdisp.h > // MFC Automation classes
#include < afxdtctl.h > // MFC support for Internet Explorer 4
Common
// Controls
#ifndef _AFX_NO_AFXCMN_SUPPORT
#include < afxcmn.h > // MFC support for Windows Common Controls
#endif // _AFX_NO_AFXCMN_SUPPORT

#include < atlbase.h > // Support for CComPtr< >


#include < afxctl.h > // MFC support for Connection Points

#import "HelloServ.tlb" no_namespace named_guids raw_interfaces_only

//{{AFX_INSERT_LOCATION}}
// Microsoft Visual C++ will insert additional declarations immediately
before
// the previous line.

#endif // !
defined(AFX_STDAFX_H__8495B5E0_67FF_11D4_A358_00104B732442__INCLUDED_)
Listing 3. Adding other needed code to STDAFX.H.

Bring in the Connection Point

NOTE: This only works if the event source interface is a dispinterface, like our
DHelloWorldEvents interface.

The next thing to do is to use ClassWizard to give us a class which will implement our
connection point for us (!). Yes, we've finally arrived!! To do this, open up ClassWizard,
click the Message Maps tab, click Add Class, and then click New, as shown below in
Figure 2:
Figure 2. Adding a new class with ClassWizard.

The next thing to do is to specify the new class we want to add to our project. Since this
class is (kind of) implementing an interface, that is, the DHelloWorldEvents
dispinterface, we'll call this class the CHelloWorldEvents class. Next, we specify that we
want to derive this class from the MFC CCmdTarget class, which helps us with all the
COM implementation. People have often said that "MFC doesn't really have any COM
support besides that needed for OLE and UI stuff. And then, it only does dispinterfaces."
None of the preceeding sentence is entirely correct. MFC is great at helping us out with
UI stuff, but I have seen example code (that works) where MFC is used to implement any
interface you like, even in COM servers with non-dispinterface and non-IDispatch
interfaces! The CCmdTarget class is the key.

Anyway, enough of my blathering. The last thing to do before we can click OK in the
New Class dialog box is to click the Automation option button. This turns on the support
in CCmdTarget that we need to use; don't worry, choosing this won't even add so much as
an .ODL file to your project, and you needn't have checked 'Automation' in AppWizard to
use this. When everything in the New Class dialog box is as it should be, it should look
like Figure 3, below:
Figure 3. Specifying the settings for our new CHelloWorldEvents class in ClassWizard.

ClassWizard will add the CHelloWorldEvents class to your project, but it will whine
because you didn't specify Automation support in AppWizard. Since you didn't, your
project doesn't have a HelloCli.odl file. Too bad for ClassWizard; it shows you the
protest message below, but you can click OK and ignore it:

Figure 4. ClassWizard should just grow up, and quit its whining; but, oh well... Ignore
this warning and click OK.

Make Changes to CHelloWorldEvents

ClassWizard, helpful as it is, did make one booboo that we'll want to erase. Open the
HelloWorldEvents.cpp file and remove the line shown below in bold:

BEGIN_MESSAGE_MAP(CHelloWorldEvents, CCmdTarget)
//{{AFX_MSG_MAP(CHelloWorldEvents)
// NOTE - the ClassWizard will add and remove mapping macros
here.
//}}AFX_MSG_MAP
END_MESSAGE_MAP()
BEGIN_DISPATCH_MAP(CHelloWorldEvents, CCmdTarget)
//{{AFX_DISPATCH_MAP(CHelloWorldEvents)
// NOTE - the ClassWizard will add and remove mapping macros
here.
//}}AFX_DISPATCH_MAP
END_DISPATCH_MAP()

// Note: we add support for IID_IHelloWorldEvents to support typesafe


binding
// from VBA. This IID must match the GUID that is attached to the
// dispinterface in the .ODL file.

// {B0652FB5-6E0F-11D4-A35B-00104B732442}
static const IID IID_IHelloWorldEvents =
{ 0xb0652fb5, 0x6e0f, 0x11d4, { 0xa3, 0x5b, 0x0, 0x10, 0x4b, 0x73,
0x24, 0x42 } };
Listing 4. Delete the lines of code that are shown in bold.

Next, find the code shown in bold in Listing 5, below. We're going to replace it with the
DIID (DispInterfaceID) of the DHelloWorldEvents interface:

BEGIN_INTERFACE_MAP(CHelloWorldEvents, CCmdTarget)
INTERFACE_PART(CHelloWorldEvents, IID_IHelloWorldEvents, Dispatch)
END_INTERFACE_MAP()
Listing 5. The code to look for, shown in bold.

Replace IID_IHelloWorldEvents with DIID_DHelloWorldEvents, as shown in Listing


6:

BEGIN_INTERFACE_MAP(CHelloWorldEvents, CCmdTarget)
INTERFACE_PART(CHelloWorldEvents, DIID_DHelloWorldEvents,
Dispatch)
END_INTERFACE_MAP()
Listing 6. Putting DIID_DHelloWorldEvents in place of the Class-Wizard-added
IID_IHelloWorldEvents identifier.

Now, we're going to use ClassWizard to add the handler function which gets called when
the server fires the OnSayHello event. Bring up ClassWizard, and select the Automation
tab. Make sure that CHelloWorldEvents is selected in the Class Name box, as shown in
Figure 5 below:
Figure 5. Selecting the CHelloWorldEvents class on the Automation tab of
ClassWizard.

Click Add Method. The Add Method dialog box appears, as shown in Figure 6 below.
Here we're going to specify the "external name" for our event handler method, as well as
other information. The "external name" should ALWAYS match the name of the event
method that we used when we added it to the server! The Internal Name box should hold
the name of the member function that will get called when the event comes in; this can be
whatever you please. We're going to use what ClassWizard suggests; a name that matches
the OnSayHello "external name." ALWAYS specify void for the return type of an event
handler, because the server always uses HRESULT as its return types. For clients, anytime
we're handling connection point events, the return type should always be void. Next,
specify a parameter, LPCTSTR lpszHost as the event handler's single parameter. You
notice that BSTR isn't in the list of event handler types (alright!). This is because you use
LPCTSTR instead; ClassWizard makes sure that MFC will convert between BSTR and
LPCTSTR for you (!).
Figure 6. Setting up a handler for the OnSayHello event.

Click OK. ClassWizard adds code to CHelloWorldEvents to make the magic happen
(with CCmdTarget's help), and then shows a new entry in its External Names listbox to
show that the event handler has been added:
Figure 7. ClassWizard showing the addition of our new event handler.

Save your changes! Make sure and click OK in ClassWizard, otherwise it will roll-
back all of its changes that it made. If you click Cancel, you'll have to add the event
handler again. Just a word of warning. Now it's time to implement our event handler.
We'll grab the name of the server computer from lpszHost, and then we'll show the user
a message box saying that the server said Hello. We might also want to add text to the
Status window of our dialog saying that the event handler function got called. Here's how
I did that:

#include "HelloCliDlg.h"
void CHelloWorldEvents::OnSayHello(LPCTSTR lpszHost)
{
CHelloCliDlg* pDlg = (CHelloCliDlg*)AfxGetMainWnd();

if (pDlg != NULL)
{
pDlg->m_strStatus
+= "The OnSayHello() connection point method has been
called\r\n";
pDlg->UpdateData(FALSE);
}

// Show a message box saying 'Hello, world, from host ' + lpszHost:
CString strMessage = "Hello, world, from ";
strMessage += lpszHost;

AfxMessageBox(strMessage, MB_ICONINFORMATION);
}
Listing 7. Implementing the CHelloWorldEvents::OnSayHello() event handler
function.

I also had to add a friend statement to the declaration of CHelloWorldEvents, because


CWnd::UpdateData() is a protected function:

class CHelloWorldEvents : public CCmdTarget


{
friend class CHelloCliDlg;

...
};

The next thing to do is to add some data members to the CHelloCliDlg class in order to
hold the pointers and objects that we'll be using in working with the server. There are
quite a few of them, and you'll have to make sure to add the line

#include "HelloWorldEvents.h"
to the top of the HelloCliDlg.h file:
// Implementation
protected:
HICON m_hIcon;

DWORD m_dwCookie; // Cookie to keep track of connection


point
BOOL m_bSinkAdvised; // Were we able to advise the
server?

IHelloWorldPtr* m_pHelloWorld; // Pointer to the IHelloWorld


interface pointer
IUnknown* m_pHelloWorldEventsUnk; // Pointer to the IUnknown
of the
// event "sink"

CHelloWorldEvents m_events; // Our event-handler object


Listing 8. Data members we need to add to the CHelloCliDlg class.

Next, we need to add code to the dialog's constructor:

CHelloCliDlg::CHelloCliDlg(CWnd* pParent /*=NULL*/)


: CDialog(CHelloCliDlg::IDD, pParent)
{
...

m_dwCookie = 0;
m_pHelloWorldEventsUnk = m_events.GetIDispatch(FALSE);
// So we don't have to call Release()
m_bSinkAdvised = FALSE;
}
Listing 9. Code to add to the CHelloCliDlg::CHelloCliDlg() constructor function.
To advise the server, I added a AdviseEventSink(), protected member function to
CHelloCliDlg using ClassView. This function is implemented the basically the same for
anytime you want to advise a server about your MFC connection point:

BOOL CHelloCliDlg::AdviseEventSink()
{
if (m_bSinkAdvised)
return TRUE;

IUnknown* pUnk = NULL;


CComPtr< IUnknown > spUnk = (*m_pHelloWorld);
pUnk = spUnk.p;

// Advise the connection point


BOOL bResult = AfxConnectionAdvise(pUnk, DIID_DHelloWorldEvents,
m_pHelloWorldEventsUnk, TRUE, &m_dwCookie);

return bResult;
}
Listing 10. Implementation of advising the server. This demonstrates how to call
AfxConnectionAdvise(). You must have properly registered the server like we did in
Step 6, or else this won't work.

When we're ready to go back to being aloof to the server and its events that it fires, we
can call AfxConnectionUnadvise():

BOOL CHelloCliDlg::UnadviseEventSink()
{
if (!m_bSinkAdvised)
return TRUE;

// Get the IHelloWorld IUnknown pointer using a smart pointer.


// The smart pointer calls QueryInterface() for us.
IUnknown* pUnk = NULL;

CComPtr< IUnknown > spUnk = (*m_pHelloWorld);


pUnk = spUnk.p;

if (spUnk.p)
{
// Unadvise the connection with the event source
return AfxConnectionUnadvise(pUnk, DIID_DHelloWorldEvents,
m_pHelloWorldEventsUnk, TRUE, m_dwCookie);
}

// If we made it here, QueryInterface() didn't work and we can't


// unadvise the server
return FALSE;
}
Listing 11. Unadvising the event source and sink with AfxConnectionUnadvise().
To actually make the method call, you can see how I implemented all of this and where
my AdviseEventSink() and UnadviseEventSink() play in in the sample program.
Remember, though, to add this code to OnInitDialog():

BOOL CHelloCliDlg::OnInitDialog()
{
CDialog::OnInitDialog();

...

CoInitialize(NULL);

CoInitializeSecurity(NULL, -1, NULL, NULL, RPC_C_AUTHN_LEVEL_NONE,


RPC_C_IMP_LEVEL_IMPERSONATE, NULL, EOAC_NONE, NULL);

return TRUE;
}
Listing 12. Adding intialization code to OnInitDialog().

The OnStartServer() function handles a button the user clicks when they want to start
the server (how circular). As you can see, I had a server computer on the network named
\\Viz-06 which I connected to with DCOM:

void CHelloCliDlg::OnStartServer()
{
COSERVERINFO serverInfo;
ZeroMemory(&serverInfo, sizeof(COSERVERINFO));

COAUTHINFO athn;
ZeroMemory(&athn, sizeof(COAUTHINFO));

// Set up the NULL security information


athn.dwAuthnLevel = RPC_C_AUTHN_LEVEL_NONE;
athn.dwAuthnSvc = RPC_C_AUTHN_WINNT;
athn.dwAuthzSvc = RPC_C_AUTHZ_NONE;
athn.dwCapabilities = EOAC_NONE;
athn.dwImpersonationLevel = RPC_C_IMP_LEVEL_IMPERSONATE;
athn.pAuthIdentityData = NULL;
athn.pwszServerPrincName = NULL;

serverInfo.pwszName = L"\\\\Viz-06";
serverInfo.pAuthInfo = &athn;
serverInfo.dwReserved1 = 0;
serverInfo.dwReserved2 = 0;

MULTI_QI qi = {&IID_IHelloWorld, NULL, S_OK};

...

try
{
m_pHelloWorld = new IHelloWorldPtr;
}
catch(...)
{
AfxMessageBox(AFX_IDP_FAILED_MEMORY_ALLOC, MB_ICONSTOP);

...
return;
}

HRESULT hResult = CoCreateInstanceEx(CLSID_HelloWorld, NULL,


CLSCTX_LOCAL_SERVER | CLSCTX_REMOTE_SERVER, &serverInfo, 1,
&qi);

if (FAILED(hResult))
{
...
return;
}

m_pHelloWorld->Attach((IHelloWorld*)qi.pItf);

// Now we have a live pointer to the IHelloWorld interface


// on the remote host

...

return;
}
Listing 13. How to get an interface pointer to the IHelloWorld interface on the
remote server.

Calling the method is a simple matter of executing this statement:

HRESULT hResult = (*m_pHelloWorld)->SayHello();


Listing 14. Calling the IHelloWorld::SayHello() method.

To release the server when we're done with it, simply delete the m_pHelloWorld pointer:

delete m_pHelloWorld;
m_pHelloWorld = NULL;
Listing 15.

The End... My Friend...

There! Now we have a living, breathing, DCOM client/server software system. It doesn't
do much, but it can do a lot... Anyway, I hope this tutorial has been enlightening, and
DCOM demystified. I always encourage you to e-mail me just whenever, and ask me
questions. No question is a stupid question, and I will be happy to help you. Click Back
below if you want to go back to Step 6, or click Questions and Answers to see if someone
else asked a question you need answered.

Until next time... it's been fun.

History

Introduction

Windows applications that do not export any program interface may be, however,
converted to an automation server. The term "Automation" in this context means that
clients running in separate processes are able to

• control converted to server application,


• get some services form it in synchronous and/or asynchronous (by receiving event
notifications) modes.

This is useful particularly in the case of old legacy and the third party applications. Their
functionality and user interface may be upgraded, and their services become available for
another applications. This may be achieved with COM object(s) injected to the process.
The DLL injection technique in Win32 NT was described by Jeffrey Richter [1] and has
already become almost conventional. Now we try to move a step further: to embed a
COM object into a target application (now "promoted" to server) and, using this
component, communicate with the application from outside of its process.

Brief Technique Description


Let's assume for simplicity that our target application is running in just one thread. This
application has one main frame window having one client (view) window. To automate
such an application, the following steps should be taken.

• A special Loader application injects a Plugin DLL into the target process using
a remote thread. This is a worker thread, and its function is DllMain() of the
Plugin DLL. After DllMain() returns, the thread will be terminated.
• The Plugin DLL contains also callback window procedures to subclass the
frame and view windows of a target application. These window procedures
include message handlers with new functionality for the target application.
DllMain() of the Plugin DLL actually performs the subclassings.
• New window procedure for, say, a frame window has a handler for an
additional Plugin-specific Windows message, WM_CREATE_OBJECT. DllMain()
performs the target application window's subclassing, and after that posts the
WM_CREATE_OBJECT message to a frame window. Then DllMain() returns.
• Upon receiving the WM_CREATE_OBJECT message, a new frame window
procedure creates a COM object and calls its method for registration with
Running Object Table (ROT). If asynchronous notification to client(s) is
required then the embedded COM object should support the Connection Point
mechanism and define outgoing sink interface in its IDL file. It is preferable to
have the outgoing interface being an IDispatch-based (or dual) one to allow
a script client to implement it.
• Client applications obtain a proxy of the injected COM object via ROT, thus
gaining control over the target application. To subscribe for the server's
events notification, the clients should implement sink interface and marshal
("advise") its pointer to an embedded object.

To accomplish automation task information about the target application, the window class
name is required. It may be revealed by using Spy utility, which is part of the Visual
Studio installation. Another useful tool is ROT Viewer. It is also a Visual Studio utility.
ROT viewer allows developer to inspect the ROT and therefore check the proper
registration of a COM object embedded into the target process.

Test Sample

A well-known Notepad text editor is taken as a target application for automation.

The Loader.exe application is responsible for the injection of the NotepadPlugin.dll in


Notepad.exe. Loader finds a running instance of Notepad (or starts a new instance in case
no running instance is available), obtains its process handle, and actually performs the
injection of NotepadPlugin.dll using a remote thread (please refer to [1, 2] and code
sample for details).

A NotepadPlugin.dll is to be injected into Notepad.exe. Its DllMain() method first finds


the frame and view windows of Notepad.exe and then subclasses both of them with
appropriate custom windows procedures. As it was stated above, DllMain() is running
not in the Notepad.exe main thread, but in an additional thread created remotely by the
Loader application. This additional thread vanishes when NotepadPlugin.dll is
completely loaded. A custom WM_CREATE_OBJECT Windows message is posted to the
frame window to initiate creation of the COM object for automation and its registration
with ROT. The message WM_CREATE_OBJECT handler of a new frame window procedure
initializes COM, creates NotepadHandler COM object, and calls its appropriate method
for its ROT registration.

A COM in-process server component, NotepadHandler.dll, implements the dual-interface


IHandler, specially tailored for our custom Notepad automation. IHandler has methods
to register/unregister the object with the ROT. The outgoing source dual interface
IHandlerEvents is added to the NotepadHandler project (in file NotepadHandler.idl).
The interface contains method HRESULT SentenceCompleted([in] BSTR bsText). The
NotepadHandler component implements the connection point mechanism (appropriate
code may be added by activating of the Implement Connection Point... item in the right-
click menu on CHandler in ClassView tab of Visual Studio workspace and the dialog
followed).

According to my scenario (just for illustration), the server (embedded COM object) picks
all Notepad editor keyboard input symbols (including non-alphabetic ones). As soon
as one of the end-of-the-sentence symbols (".", "?", and "!" characters) appears, the server
Fire_SentenceCompleted() event, thus providing all the event's subscribers with the
sentence completed (content of appropriate buffer).

Below two variants of clients, for Win32 and .NET platforms are discussed. Each of them
supports two ROT registration approaches: ActiveObject and moniker registration.
Registration details may be seen with the ROT Viewer tool.

Win32 Client

The AutomClient.exe application is a sample of an Win32 automation client. It creates a


proxy for the NotepadHandler COM object by using the component's ROT registration
and actually controls the automated instance of Notepad through the IHandler interface
implemented by NotepadHandler.

The client application process should implement the IHandlerEvents interface. It may
be done in various ways. I choose to create a special COM component (in-process
server), Notification. To construct it, the additional project NotepadHandlerNotification
was added to the workspace with the ATL COM AppWizard. Then an ATL object was
inserted into the component, using the right-click menu. This object implements
additional an interface that I called INotification. This interface is not a compulsory
one, but may be useful to provide the component with data from the client application via
appropriate methods (I supply to the component handle of the client application's main
window that way). The CNotification class implements IHandlerEvents interface (by
activating the Implement Interaface... right-click menu item followed by the
corresponding dialog). A client application class CAdvise contains advising mechanism.
The method CAutomationClientDlg::OnButtonAutomate() contains code responsible
for advising.
Implemented in the Notification component, the SentenceCompleted() method of
the IHandlerEvents interface is called by the server. The application main window
handle (in this case) is supplied to the Notification component with the
SetMainWnd() method of the INotification interface. Having this handle in its
possession, the SentenceCompleted() method posts a notification message to the
main window of the client application, passing a pointer to a buffer with data
received from the server.

By default the ActiveObject registration is used. To switch to moniker registration,


definition of _MONIKER should be uncommented in file .\Include\DefineMONIKER.h
before compilation.

.NET Client

The AutomClientNET.exe application is a sample of a .NET automation client. From user


perspective it acts similarly to AutomClient.exe application. AutomClientNET.exe
application uses RotHelper assembly classes to create .NET Interop proxy for the
NotepadHandler COM object. The RotHelper and NotepadHandler Interop (as
NOTEPADHANDLERLib) have to be added to Referances of AutomClientNET project.
This is done using standard VS.NET Add Reference... dialog: RotHelper through Projects
tab, and through NOTEPADHANDLERLib selecting the NotepadHandler Type Library in
COM tab.

By default the ActiveObject registration is used. To switch to moniker registration,


definition of _MONIKER should be uncommented (in the beginning of file
.\AutomationClientNET\NotepadClientForm.cs) before compilation.

Running the Test

An already compiled demo is available for the test sample. Please note that before you
run it, the NotepadHandler.dll and NotepadHandlerNotification.dll COM components
has to be registered with the regsvr32.exe utility. For the registration, you have to run
the file Register Components.bat, located in the demo directory. Then one or more copies
of AutomationClient.exe and AutomationClientNET.exe may be started.

By pressing the NOTEPAD AUTOMATE button, client internally starts the Loader.exe
application. The latter starts Notepad and automates it. A word [Automated] appears in
caption of Notepad main window. Alternatively, you may run Notepad manually before
the AutomationClient.exe. In this case, Loader automates the already running instance of
Notepad. As soon as Notepad has been automated client subscribes to its Sentence
Completed event.

Now you may type some text in the automated Notepad instance and press the Copy Text
button of the client application. The last text fragment you typed will appear in the edit
box of the client application. To simplify things, only actually typed characters and
symbols are copied, not copied-and-pasted text. Pressing the Find and Append Menu
buttons of the client causes corresponding Notepad response.
If user types some characters followed by a sentence conclusion sign (".", "?", or "!"), the
character sequence will be reproduced in an edit box of all clients' applications
subscribed to the event. The automated Notepad sends ("fires") the Sentence Completed
event to its subscribers (clients) as soon as any character from the set .?! is input. Upon
this event, the appropriate buffer content is sent to subscribers and displayed by them.

On exit from Notepad, a "Bye-Bye..." message box is generated by NotepadPlugin.dll on


behalf of Notepad to amuse user :) .

Compiling the Test

The test sample consists of NotepadAuto workspace for Visual C++ 6.0 and
AutomationClientNET solution for Visual Studio .NET (files NotepadAuto.dsw and
AutomationClientNET.sln are located in main directory of the source). VC++ 6.0 stuff is
completely independent from .NET solution and may be tested separately.

First, file NotepadAuto.dsw should be loaded to VC++ 6.0 Studio and


_Build_All_Projects project should be built. Then, if .NET client is of your interest, file
AutomationClientNET.sln has to be loaded to VS.NET and its projects should be built (be
sure that references to RotHelper and NOTEPADHANDLERLib interop are added to
references of AutomationClientNET project).

The psapi library contains EnumWindows() and EnumProcesses() functions is required


for a system-wide search for particular windows and processes. So, if psapi.dll file is not
installed in your system directory then it should be copied from .\Psapi_library directory
either to system directory, or it should be made loadable in some other way (e.g., by
coping to directories available through environmental path variable, working directories
or with Visual Studio's Tools->Option dialog->Directories). It is also assumed that
Notepad.exe is located in your [Windows]\System32 directory. After the above
arrangements have been made, you may build the _Build_All_Projects project (the rest
are its dependants) and run the sample.

The AutomationClient.exe application should be run to test the Win32 sample. Please
note that sample should be tested outside Visual Studio. The AutomationClientNET.exe
application should be run to test the .NET sample. This test may be performed from
VS.NET too.

Some Ways for Further Development

Design Modification Description


The frame and view windows of a target application may
be subclassed by an MFC-aware plugin DLL. This allows
User interface (UI) upgrade
considerable UI upgrade, like, for example, adding
toolbars to an old-style application.
Actions Sequence The client may implement a sequence of commands
given to the target application. This sequence could be
implemented with some scripting.
An object embedded into the target process may serve as
an "objects factory." With its help, another COM object(s)
may be created within the target process to accomplish
Configurator
various specific tasks. The object factory component
obtains data for new objects' construction through its
methods and/or Registry.

Automated applications may be included to distributed


Part of distributed system
systems (see e.g., [3]).

Conclusion

A technique for automation of Windows applications that do not export program interface
is presented. Usage of COM objects in a DLL injected into a target process allows
developer to automate such applications. This approach is useful to upgrade functionality
and GUI of target applications (particularly, legacy applications) and expose their
services to out-process clients. Source sample demonstrates such an automation for both
Win32 (MFC) and .NET (C#) clients.

References

[1] Jeffrey Richter. Advanced Windows. Third edition. Microsoft Press, 1997.
[2] John Peloquin. Remote Library Loading.
[3] Dino Esposito. Add Object Models to Non-COM Apps.

Update

The .NET client is added. ROT registration of injected COM object may be carried out
with ActiveObject or moniker. Text of the article is changed to reflect changes in code.

Thanks

My thanks to all people who read this article and expressed their opinion and suggestions.
My special thanks to Alex Furman for his refinement of RotHelper.Moniker class.

Igor Ladnik


Introduction

This demo application is an example of how classes IStoreNamespace and


IStoreFolder should be used to list folder / messages in Outlook Express.

The application has the following features:

1. List local folders.


2. Create / Rename / Delete local folder.
3. List messages of a local folder.
4. Get message properties.
5. Get message source.
6. Create / Copy / Move / Delete messages.
7. Mark messages as Read or Unread.

Using the code

This code was written to provide an initial example of the IStoreFolder /


IStoreNamespace classes recently documented by Microsoft.
Examples about these classes cannot be found in the net and a lot of people asked us in
our Company to write some examples, so we decided to write some articles here about
these groups of classes.

In the initial dialog, all the local folders of the main identity are listed to let the user
modify them.

In the message dialog, you will be able to view all the messages of the folder that you
selected in the folder dialog. Message source and other message operations can be done
here.

Points of Interest

List all local folders and add them to the list box:

// add all the folders to the list box recursively


void CDemoDlg::AddFolders(STOREFOLDERID dwFolderId)
{
FOLDERPROPS props;
HENUMSTORE hEnum;
int nIndex;

hEnum = NULL;

// set the size of the structure or the function return error


props.cbSize = sizeof(FOLDERPROPS);

HRESULT hr = m_pStoreNamespace->GetFirstSubFolder(dwFolderId,
&props, &hEnum);

while(SUCCEEDED(hr) && hr != S_FALSE && hEnum != NULL) {


nIndex = m_listFolder.AddString(props.szName);

if(nIndex != LB_ERR && nIndex != LB_ERRSPACE) {


// set the folder id as the data of the item
m_listFolder.SetItemData(nIndex, props.dwFolderId);

// add children of this folder too


AddFolders(props.dwFolderId);
}

hr = m_pStoreNamespace->GetNextSubFolder(hEnum, &props);
}

// close the enum


if(hEnum) {
m_pStoreNamespace->GetSubFolderClose(hEnum);
}
}

List all messages of a specific folder and add all 'Subject' and 'From' to the list box:
MESSAGEPROPS msgProps;
HENUMSTORE hEnumMsg;
CString item;
int nIndex;

hEnumMsg = NULL;

// set the size of the structure or the function return error


msgProps.cbSize = sizeof(MESSAGEPROPS);

// as we want the subject and other staff we get all the properties.
// you can use MSGPROPS_FAST as first parameter
// to get only a few properties of the message.
HRESULT hr = m_pStoreFolder->GetFirstMessage(0,
0,
MESSAGEID_FIRST,
&msgProps,
&hEnumMsg);

while(SUCCEEDED(hr) && hr != S_FALSE) {


item = msgProps.pszDisplayFrom;
item += _T(" ");
item += msgProps.pszNormalSubject;

// message subject and from is displayed in the list box.


// data of each item is the message id.
nIndex = m_listMsg.AddString(item);

if(nIndex != LB_ERR && nIndex != LB_ERRSPACE) {


m_listMsg.SetItemData(nIndex, msgProps.dwMessageId);
}

// free the message properties as they are allocated by


IStoreFolder.
m_pStoreFolder->FreeMessageProps(&msgProps);

hr = m_pStoreFolder->GetNextMessage(hEnumMsg, 0, &msgProps);
}

// close the enum


if(hEnumMsg) {
m_pStoreFolder->GetMessageClose(hEnumMsg);
}

Display full source of a specific message:

// this function displays the source of the selected message in the


list box
void CMsgDlg::OnView()
{
ULONG ulReaded = 0;
int nIndex;
STOREFOLDERID dwSelMsg;
HRESULT hr;
IStream *pTextStream;
char buffer[4096];
// Get selected folder id
nIndex = m_listMsg.GetCurSel();
if(nIndex == LB_ERR) {
MessageBox(_T("Select a message first."), _T("Demo Error"));
return;
}

dwSelMsg = m_listMsg.GetItemData(nIndex);

// create a IStream from the message


hr = m_pStoreFolder->OpenMessage(dwSelMsg, IID_IStream, (VOID **)
&pTextStream);
if(FAILED(hr)) {
MessageBox(_T("Error opening message."), _T("Demo Error"));
return;
}

CMsgSrcDlg msgSrcDlg;

// read all the message


do {
hr = pTextStream->Read(buffer, sizeof(buffer)-1, &ulReaded);

if(FAILED(hr)) {
MessageBox(_T("Error reading message."), _T("Demo Error"));
}
else {
buffer[ulReaded] = 0;

msgSrcDlg.AddMessageSource(buffer);
}
} while(SUCCEEDED(hr) && ulReaded != 0);

if(SUCCEEDED(hr)) {
// display message
msgSrcDlg.DoModal();
}

pTextStream->Release();
}

Create a message in a specific folder:

IStream *newMail = NULL;


MESSAGEID msgId;
HRESULT hr;
ULONG len;
CString msgSource;

// Set msgSource to contain the source of the new message


...

// Create the IStream to write the new message


// this function returns the id of the new message
hr = m_pFolder->CreateStream(0, 0, &newMail, &msgId);
if(FAILED(hr)) {
MessageBox(_T("Cannot Create Stream."), _T("Demo Error"));
return;
}

// write message source in the IStream


hr = newMail->Write((const char *) msgSource, msgSource.GetLength(),
&len);
if(FAILED(hr)) {
MessageBox(_T("Cannot Write message."), _T("Demo Error"));
newMail->Release();
return;
}

// Commit the IStream in the folder and use the returned msgId
hr = m_pFolder->CommitStream(0, 0, 0, newMail, msgId, NULL);
if(FAILED(hr)) {
MessageBox(_T("Cannot Commit Stream."), _T("Demo Error"));
newMail->Release();
return;
}

// release the IStream


newMail->Release();

Next

In the next article, I will describe how IMimeMessage / IMimeMessageTree / IMimeBody


interfaces should be used to create or change a message.

Introduction

Advanced COM-based projects often require the passing of objects across threads.
Besides the requirement to invoke the methods of these objects from various threads,
there is sometimes even the need to fire the events of these objects from more than one
thread. This two-part article is aimed at the beginner level COM developer who has just
crossed the initial hurdles of understanding the basics of IUnknown and IDispatch and is
now considering the use of objects in multiple threads. This is where the need to
understand COM Apartments come in.

I aim to explain in as much detail as possible the fundamental principles of how COM
object methods may be invoked from multiple threads. We shall explore COM
Apartments in general and the Single Threaded Apartment (STA) Model in particular
in an attempt to demystify both what they are designed to achieve and how they achieve
their design.

COM Apartments form a topic worthy of close study of its own. It is not possible to cover
in detail everything that pertains to this subject in one single article. Instead of doing that,
I will focus on Single-Threaded Apartments for now and will return to the other
Apartment Models in later articles. In fact, I have found quite a lot of ground to cover on
STAs alone and thus the need to split up this article into two parts.

This first part will concentrate on theory and understanding of the general architecture of
STAs. The second part will focus on solidifying the foundations built up in part one by
looking at more sophisticated examples.

I will present several illustrative test programs as well as a custom-developed C++ class
named CComThread which is a wrapper/manager for a Win32 thread that contains COM
objects or references to COM objects. CComThread also provides useful utilities that help
in inter-thread COM method calls.

I will show how to invoke an object's methods from across different threads. I will also
invoke an event of an object from another thread. Throughout this article, I will
concentrate my explanations on Single Threaded Apartment COM objects and threads
with some mention of other Apartment Models for comparison purposes. I chose to
expound on the STA because this is the Apartment Model most frequently recommended
by Wizards. The default model set by the ATL wizard is the STA. This model is useful in
ensuring thread-safety in objects without the need to implement a sophisticated thread-
synchronization infrastructure.

Synopsis

Listed below are the main sections of this article together with general outlines of each of
their contents:

COM Apartments

This section gives a general introduction to COM Apartments. We explore what they are,
what they are designed for, and why the need for them. We also discuss the relationship
between apartments, threads and COM objects and learn how threads and objects are
taught to live with each other in apartments.

The Single-Threaded Apartment

This section begins our in-depth study of the Single-Threaded Apartments and serves as a
"warm-up" to the heavy-going sections that follow. We layout clearly the thread access
rules of an STA. We also see how COM makes such effective use of the good old
message loop. We then touch on the advantages and disadvantages of STAs in general
before proceeding to discuss implementation issues behind the development of STA
COM objects and STA threads.

Demonstrating The STA

This section and the next ("EXE COM Servers And Apartments") are filled with detailed
descriptions of several test programs. This is the main aim of this article: to show
concepts by clear examples. In this section, each test program is aimed at demonstrating
one particular type of STA (beginners may be surprised to learn that there are actually
three types of STAs !). The reader will note that our approach to demonstrating STAs is
very simple. The challenge for me is to demonstrate clearly the different types of STAs
using this simple test principle.

EXE COM Servers And Apartments

The last major section of this article explores EXE COM Servers and their relationship
with Apartments. Some of the important differences between a DLL Server and an EXE
Server are listed. From this section, I hope the reader gets to understand the important
role that Class Factories play. I have deliberately written by hand the source codes used
for the demonstration program in order to illustrate some concepts. The use of ATL
Wizards will have made this more troublesome.

Without further ado, let us begin by exploring the principles behind the COM Apartments
in general.

COM Apartments

To understand how COM deals with threads, we need to understand the concept of an
apartment. An apartment is a logical container inside an application for COM objects
which share the same thread access rules (i.e., regulations governing how the methods
and properties of an object are invoked from threads within and without the apartment in
which the object belongs).

It is conceptual in nature and does not present itself as an object with properties or
methods. There is no handle type that can be used to reference it nor are there APIs that
can be called to manage it in any way.

This is perhaps one of the most important reasons why it is so difficult for newbies to
understand COM Apartments. It is so abstract in nature.

Apartments may have been much easier to understand and learn if there was an API
named CoCreateApartment() (with a parameter that indicates the apartment type), and
some other supporting APIs like CoEnterApartment(). It would have been even better
still if there was a Microsoft supplied coclass with an interface like IApartment with
methods that manage the threads and objects inside an apartment. Programmatically,
there seem to be no tangible way to look at apartments.
To help the newbie cope with the initial learning curve, I have the following advise on the
way to perceive apartments:

1. They are created by implication. There are no direct function calls to create
them or to detect their presence.
2. Threads and objects enter apartments and engage in apartment-related
activities also by implication. There are also no direct function calls to do so.
3. Apartment Models are more like protocols, or a set of rules to follow.

What Do COM Apartments Aim To Achieve?

In an operating environment in which multiple-threads can have legitimate access to


various COM objects, how can we be sure that the results we expect from invoking the
methods or properties of an object in one thread will not be inadvertently undone by the
invocation of methods or properties of the same object from another thread?

It is towards resolving this issue that COM Apartments are created. COM Apartments
exist for the purpose of ensuring something known as thread-safety. By this, we mean the
safe-guarding of the internal state of objects from uncontrolled modification via equally
uncontrolled access of the objects' public properties and methods running from different
threads.

There are three types of Apartment Models in the COM world: Single-Threaded
Apartment (STA), Multi-Threaded Apartment (MTA), and Neutral Apartment. Each
apartment represents one mechanism whereby an object's internal state may be
synchronized across multiple threads.

Apartments stipulate the following general guidelines for participating threads and
objects:

• Each COM object is assigned to live in one and only one apartment. This is
decided at the time the object is created at runtime. After this initial setup,
the object remains in that apartment throughout its lifetime.
• A COM thread (i.e., a thread in which COM objects are created or COM
method calls are made) also belongs to an apartment. Like COM objects, the
apartment in which a thread lives is also decided at initialization time. Each
COM thread also remains in their designated apartment until it terminates.
• Threads and objects which belong to the same apartment are said to follow
the same thread access rules. Method calls which are made inside the same
apartment are performed directly without any assistance from COM.
• Threads and objects from different apartments are said to play by different
thread access rules. Method calls made across apartments are achieved via
marshalling. This requires the use of proxies and stubs.

Besides ensuring thread-safety, another important benefit that Apartments deliver to


objects and clients is that neither an object nor its client needs to know nor care about the
Apartment Model used by its counterpart. The low-level details of Apartments (especially
its marshalling mechanics) are managed solely by the COM sub-system and need not be
of any concern to developers.

Specifying The Apartment Model Of A COM Object

From here onwards until the section "EXE COM Servers And Apartments" later on
below, we will refer to COM objects which are implemented in DLL servers.

As mentioned, a COM object will belong to exactly one runtime apartment and this is
decided at the time the object is created by the client. However, how does a COM object
indicate its Apartment Model in the first place?

Well, for a COM coclass implemented in a DLL Server, when COM proceeds to
instantiate it, it refers to the registry string value named "ThreadingModel" which is
located in the component's "InProcServer32" registry entry.

This setting is controlled by the developers of the COM object themselves. When you
develop a COM object using ATL, for example, you can specify to the ATL Wizard the
threading model the object is to use at runtime.

The table below shows the appropriate string values and the corresponding Apartment
Model that each indicates:

S/No Registry Entry Apartment Model


1 "Apartment" STA
2 "Single" or value absent Legacy STA
3 "Free" MTA
4 "Neutral" Neutral Apartment
5 "Both" The Apartment Model of the creating thread.
We will be talking about the Legacy STA later on in this article. The "Both" string value
indicates that the COM object can live equally well inside an STA and inside an MTA.
That is, it can live in either model. We shall return to this registry entry in a later article
after the MTA has been fully expounded.

Specifying The Apartment Model Of A COM Thread

Now, onto threads. Every COM thread must initialize itself by calling the API
CoInitializeEx() and passing as the second parameter either
COINIT_APARTMENTTHREADED or COINIT_MULTITHREADED.

A thread which has called CoInitializeEx() is a COM thread and is said to have
entered an apartment. This will be so until the thread calls CoUninitialize() or simply
terminates.

The Single-Threaded Apartment

A single-threaded apartment can be illustrated by the following diagram:

An STA can contain exactly one thread (hence the term single-threaded). However, an
STA can contain as many objects as it likes. The special thing about the thread contained
within an STA is that it must, if the objects are to be exported to other threads, have a
message loop. We will return to the subject of message loops in a sub-section later on and
explore how they are used by STAs.

A thread enters an STA by specifying COINIT_APARTMENTTHREADED when it calls


CoInitializeEx(), or by simply calling CoInitialize() (calling CoInitialize()
will actually invoke CoInitializeEx() with COINIT_APARTMENTTHREADED). A thread
which has entered an STA is also said to have created that apartment (after all, there are
no other threads inside that apartment to first create it).

A COM object enters an STA both by specifying "Apartment" in the appropriate string
value in the registry and by being instantiated inside an STA thread.

In the above diagram, we have two apartments. Each apartment contains two objects and
one thread. We can postulate that each thread has, early in their life, called
CoInitialize(NULL) or CoInitializeEx(NULL, COINIT_APARTMENTTHREADED).

We can also tell that Obj1, Obj2, Obj3 and Obj4 are each marked as of "Apartment"
threading model in the registry, and that Obj1 and Obj2 were created inside Thread1 and
Obj3 and Obj4 were created inside Thread2.

STA Thread Access Rules

The following are the thread access rules of an STA:

1. An STA object created inside an STA thread will reside in the same STA as its
thread.
2. All objects inside an STA will receive method calls only from the thread of the
STA.

Point 1 is natural and is easily understood. However, note that two objects of the same
coclass and from the same DLL server created in separate STA threads will not be in the
same apartment. This is illustrated in the diagram below:
Hence any method calls between Obj1 and Obj2 are considered cross-apartment and must
be performed with COM marshalling.

Concerning point 2, there are only two ways that an STA object's methods are invoked:

1. From its own STA thread. In this case, the method call is naturally serialized.
2. From another thread (whatever the Apartment). In this case, COM ensures
that the object will receive method calls only from its own STA thread by
stipulating that this STA thread must contain a message loop.

We have mentioned this point about message loops previously, and before we can go on
discussing the internals of STAs, we must cover the subject of message loops and see
how they are intimately connected with STAs. This is discussed next.
The Message Loop

A thread that contains a message loop is also known as a user-interface thread. A user-
interface thread is associated with one or more windows which are created in that thread.
The thread is often said to own these windows. The window procedure for a window is
called only by the thread that owns the window. This happens when the
DispatchMessage() API is called inside the thread.

Any thread may send or post a message to any window but the window procedure of the
target window will only be executed by the owning thread. The end result is that all
messages to a target window are synchronized. That is, the window is guaranteed to
receive and process messages in the order in which the messages are sent/posted.

The benefit to Windows application developers is that window procedures need not be
thread-safe. Each window message becomes an atomic action request which will be
processed completely before the next message is entertained.
This presents to COM a readily available, built-in facility in Windows that can be used to
achieve thread-safety for COM objects. Simply put, all method calls from external
apartments to an STA object are accomplished by COM posting private messages to a
hidden window associated with that object. The window procedure of that hidden
window then arranges the call to the object and arranges the return value back to the
caller of the method.

Note that when external apartments are involved, COM will always arrange for proxies
and stubs to be involved as well so message loops form only part of the STA protocol.

There are two important points to note:

1. The above-mentioned system of using a message-loop to invoke STA COM


object methods is only applicable when the calls are from an external
apartment (whatever model it takes). Remember that calls made from inside
an STA goes without any intervention by COM. These are naturally serialized
by the execution sequence of the STA thread itself.
2. If an STA thread fails to get and dispatch the messages in its message queue,
the COM objects in the thread's apartment will not receive incoming inter-
apartment calls.

Concerning point 2, it is important to note that APIs like Sleep(),


WaitForSingleObject(), WaitForMultipleObjects() will disrupt the flow of thread
message handling. As such, if an STA thread needs to wait on some synchronization
object, special handling will need to be arranged to ensure that the message loop is not
disrupted. We shall examine how this can be done when we study our sample code later
on.

Take note that in some circumstances, an STA thread need not contain a message loop.
We will return to explain this in the section "Implementing an STA Thread" later on.

It should be clear now how an STA achieves its thread access rules.

Benefits Of Using STA

The main advantage to using an STA is simplicity. Besides a few basic code overheads
for COM object servers, relatively few synchronization code is necessary for the
participating COM objects and threads. All method calls are automatically serialized.
This is especially useful for user-interface-based COM objects (a.k.a. COM ActiveX
Controls).

Because STA objects are always accessed from the same thread, it is said to have thread
affinity. And with thread affinity, STA object developers can use thread local storage to
keep track of an object's internal data. Visual Basic and MFC use this technique for
development of COM objects and hence are STA objects.
Besides using it for benefits, it is sometimes inevitable to use STAs when there is a need
to support legacy COM components. COM components developed in the days of
Microsoft Windows NT 3.51 and Microsoft Windows 95 could only use the Single-
Threaded Apartment. Multi-Threaded Apartments became available for usage in
Windows NT 4.0 onwards and in Windows 95 with DCOM extensions.

Disadvantages Of Using STA

There is a flip side to everything in life and there are disadvantages to using STA. The
STA architecture can impose significant performance penalties when an object is
accessed by many threads. Each thread's access to the object is serialized and so each
thread must wait in line for its turn to have a go with the object. This waiting time may
result in poor application response or performance.

The other issue which can result in poor performance is when an STA contains many
objects. Remember that an STA contains only one thread and hence will contain only one
thread message queue. This being the case, calls to separate objects within that STA will
all be serialized by the message queue. Whenever a method call is made on an STA
object, the STA thread may be busy servicing another object.

The disadvantages of using the STA must be measured against the possible advantages. It
all depends on the architecture and design of the project at hand.

Implementing An STA COM Object And Its Server

Implementing an STA COM object generally frees the developer from having to serialize
access to the object's internal member data. However, the STA cannot ensure the thread-
safety of a COM server DLL's global data and global exported functions like
DllGetClassObject() and DllCanUnloadNow(). Remember that a COM server's
objects could be created in any thread and that two STA objects from the same DLL
server can be created in two separate STA threads.

In this situation, the global data and functions of the server may well be accessed from
two different threads without any serialization from COM. The message loops of the
threads cannot lend any help either. After all, it is not an object's internal state that is at
stake here. It is the server's internal state. Hence all access to global variables and
functions of the server will need to be serialized properly because more than one object
may try to access these from different threads. This rule also applies to class static
variables and functions.

One well-known global variable of COM servers is the global object count. This
variable is accessed by the equally well-known global exported functions
DllGetClassObject() and DllCanUnloadNow(). The APIs InterlockedIncrement()
and InterlockedDecrement() may be used to protect simultaneous access (from
different threads) to the global object count. DllGetClassObject() will in turn make
use of the class factories of COM objects and these must be examined for thread-safety
too.

Hence the following is a general guideline for implementing STA Server DLLs:

1. Server DLLs must have thread-safe standard entry point functions (e.g.,
DllGetClassObject() and DllCanUnloadNow()).
2. Private (non-exported) global functions of the Server DLL must be thread-
safe.
3. Private global variables (especially the global object count) must be thread-
safe.

The purpose of the DllGetClassObject() function is to supply to callers a class object.


This class object is returned based on a CLSID and will be referenced by a pointer to one
of its interfaces (usually, IClassFactory). DllGetClassObject() is not called directly
by COM object consumers. It is instead called from within the CoGetClassObject()
API.

It is from this class object that instances of a CLSID is created (via


IClassFactory::CreateInstance()). We can look at the DllGetClassObject()
function as the gateway to the COM object creation. The important point to note about
DllGetClassObject() is that it affects the global object count.

The DllCanUnloadNow() function returns a value to its caller that determines whether
the COM Server DLL contains objects which are still alive and are servicing clients. This
DllCanUnloadNow() function uses the global object count to decide its return value. If no
more objects are still alive, the caller can safely unload the COM Server DLL from
memory.

The DllGetClassObject() and DllCanUnloadNow() functions should be arranged for


thread-safety such that at least the global object count is kept in synch. A common way
that the global object count is incremented and decremented is when an object is created
and destroyed respectively (i.e., during the constructor and destructor of the object's
implementation). The following sample code illustrates this:

CSomeObject::CSomeObject()
{
// Increment the global count of objects.
InterlockedIncrement(&g_lObjsInUse);
}
CSomeObject::~CSomeObject()
{
// Decrement the global count of objects.
InterlockedDecrement(&g_lObjsInUse);
}

The above code snippets show how the global object counter "g_lObjsInUse" is
incremented using the InterlockedIncrement() API during the constructor of an object
implemented by the C++ class CSomeObject. Conversely, during the destructor of
CSomeObject, "g_lObjsInUse" is decremented by the InterlockedDecrement() API.

No details can be advised on how to ensure the thread-safety of private global functions
and global variables. This must be left to the expertise and experience of the developers
themselves.

Ensuring thread-safety for a COM server need not be a complicated process. In many
situations, it requires simple common sense. It is safe to say that the above guidelines are
relatively easy to comply with and do not require constant re-coding once put in place.
Developers using ATL to develop COM servers will have these covered for them (except
for the thread-safety of private global data and functions) so that they can concentrate
fully on the business logic of their COM objects.

Implementing An STA Thread

An STA thread needs to initialize itself by calling CoInitialize() or


CoInitializeEx(COINIT_APARTMENTTHREADED). Next, if the objects it creates are to be
exported to other threads (i.e., other Apartments), it must also provide a message loop to
process incoming messages to the hidden windows of COM objects. Take note that it is
the hidden windows' window procedures that receive and process these private messages
from COM. The STA thread itself does not need to process the message.

The following code snippet presents the skeleton of an STA thread:

DWORD WINAPI ThreadProc(LPVOID lpvParamater)


{
/* Initialize COM and declare this thread to be an STA thread. */
::CoInitialize(NULL);
...
...
...
/* The message loop of the thread. */
MSG msg;
while (GetMessage(&msg, NULL, NULL, NULL))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
::CoUninitialize();
return 0;
}

The code snippet above looks vaguely similar to a WinMain() function. In fact, the
WinMain() of a Windows application runs in a thread too.

In fact, you can implement your STA thread just like a typical WinMain() function. That
is, you can create windows just prior to the message loop and run your windows via
appropriate window procedures. You may opt to create COM objects and manage them in
these window procedures. Your window procedures may also make cross-apartment
method calls to external STA objects.

However, if you do not intend to create windows inside your thread, you will still be able
to create, run objects and make cross-apartment method calls across external threads.
These will be explained when we discuss some of the advanced example codes in part
two of this article.

Special cases where no message loop is required in an STA Thread

Take note that in some cases, a message loop is not required in an STA thread. An
example of this can be seen in simple cases where an application simply creates and uses
objects without having its objects marshaled to other apartments. The following is an
example:

int main()
{
::CoInitialize(NULL);
if (1)
{
ISimpleCOMObject1Ptr spISimpleCOMObject1;
spISimpleCOMObject1.CreateInstance(__uuidof(SimpleCOMObject1));
spISimpleCOMObject1 -> Initialize();
spISimpleCOMObject1 -> Uninitialize();
}
::CoUninitialize();
return 0;
}

The above example shows the main thread of a console application in which an STA is
established when we call CoInitialize(). Note that there is no message loop defined
inside this thread. We also go on to create a COM object based on the
ISimpleCOMObject1 interface. Note that our calls to Initialize() and
Uninitialize() go successfully. This is because the method calls are made inside the
same STA and no marshalling and no message loop is required.

However, if we had called ::CoInitializeEx(NULL, COINIT_MULTITHREADED) instead


of CoInitialize(), thereby making the main() thread an MTA thread instead of an STA
thread, four things will happen:

1. The calls to Initialize() and Uninitialize() will be made with the help of
COM marshalling.
2. The COM object spISimpleCOMObject1 will reside in a default STA created by
the COM sub-system.
3. The main() thread still does not need any message loop, but...
4. A message loop will be used in the calls to Initialize() and
Uninitialize().
The message loop that is used in this context is the message loop that is defined in the
default STA. We will talk about the default STA later on in the section on "The Default
STA".

Note that whenever you do need to provide a message loop for an STA thread, then you
must ensure that this message loop is serviced constantly without disruption.

Demonstrating The STA

We will now attempt to demonstrate STAs. The approach we use is to observe the ID of
the thread which is executing when a COM object's method is invoked. For a standard
STA object, this ID must match that of the thread of the STA.

If an STA object does not reside in the thread in which it is created (i.e., this thread is not
an STA thread), then the ID of this thread will not match that of the thread which
executes the object's methods. This basic principle is used throughout the examples of
this article.

The Standard STA

Let us now observe STAs in action. To start, we examine the standard STA. A process
may contain as many standard STAs as is required. Our example uses a simple example
STA COM object (coclass SimpleCOMObject2 which implements interface
ISimpleCOMObject2). The source for this STA object is located in the
"SimpleCOMObject2" folder in the ZIP file accompanying this article. The
ISimpleCOMObject2 interface includes just one method: TestMethod1().

TestMethod1() is very simple. It displays a message box which shows the ID of the
thread in which the method is running on:

STDMETHODIMP CSimpleCOMObject2::TestMethod1()
{
TCHAR szMessage[256];
sprintf (szMessage, "Thread ID : 0x%X", GetCurrentThreadId());
::MessageBox(NULL, szMessage, "TestMethod1()", MB_OK);
return S_OK;
}

We will also be using a sample test program which instantiates coclass


SimpleCOMObject2 and calls its method. The source for this test program can be found in
the folder "Test Programs\VCTests\DemonstrateSTA\VCTest01" in the source ZIP file.

The test program consists of a main() function ...:

int main()
{
HANDLE hThread = NULL;
DWORD dwThreadId = 0;
::CoInitializeEx(NULL, COINIT_APARTMENTTHREADED);
DisplayCurrentThreadId();
if (1)
{
ISimpleCOMObject2Ptr spISimpleCOMObject2;
spISimpleCOMObject2.CreateInstance(__uuidof(SimpleCOMObject2));
spISimpleCOMObject2 -> TestMethod1();
hThread = CreateThread
(
(LPSECURITY_ATTRIBUTES)NULL, // SD
(SIZE_T)0, // initial stack size
(LPTHREAD_START_ROUTINE)ThreadFunc, // thread function
(LPVOID)NULL, // thread argument
(DWORD)0, // creation option
(LPDWORD)&dwThreadId // thread identifier
);
WaitForSingleObject(hThread, INFINITE);
spISimpleCOMObject2 -> TestMethod1();
}
::CoUninitialize();
return 0;
}

... a thread entry point function named ThreadFunc():

DWORD WINAPI ThreadFunc(LPVOID lpvParameter)


{
::CoInitializeEx(NULL, COINIT_APARTMENTTHREADED);
DisplayCurrentThreadId();
if (1)
{
ISimpleCOMObject2Ptr spISimpleCOMObject2A;
ISimpleCOMObject2Ptr spISimpleCOMObject2B;
spISimpleCOMObject2A.CreateInstance(__uuidof(SimpleCOMObject2));
spISimpleCOMObject2B.CreateInstance(__uuidof(SimpleCOMObject2));
spISimpleCOMObject2A -> TestMethod1();
spISimpleCOMObject2B -> TestMethod1();
}
::CoUninitialize();
return 0;
}

... and a utility function named DisplayCurrentThreadId() that shows a message box
displaying the ID of the currently running thread:

/* Simple function that displays the current thread ID. */


void DisplayCurrentThreadId()
{
TCHAR szMessage[256];
sprintf (szMessage, "Thread ID : 0x%X", GetCurrentThreadId());
::MessageBox(NULL, szMessage, "TestMethod1()", MB_OK);
}
The above example shows the creation of two STAs. We prove it by way of thread IDs.
Let us go through the program carefully, starting with the main() function:

1. The main() function calls the CoInitializeEx() API with parameter


COINIT_APARTMENTTHREADED. This makes main()'s thread enter an STA. From
here onwards, any STA object created in main()'s thread will be part of the
STA headed by main()'s thread.
2. We call the function DisplayCurrentThreadId(). The ID of main()'s thread is
displayed. Let's say this is thread_id_1.
3. Next, an instance of coclass SimpleCOMObject2 is created (represented by
spISimpleCOMObject2). This object is an STA object and so it will be in the
same STA as main()'s thread.
4. The TestMethod1() method is invoked on spISimpleCOMObject2.
TestMethod1() will display the ID of the thread in which TestMethod1() is
executing in. You will note that this will be thread_id_1. That is, it will be the
same as main()'s thread ID. Next, we start a thread headed by the entry
function ThreadFunc(). Thereafter, we wait for ThreadFunc() to end by
calling the WaitForSingleObject() API and waiting on the handle of the
ThreadFunc() thread.
5. In the ThreadFunc() thread, we invoke the CoInitializeEx() API with
parameter COINIT_APARTMENTTHREADED. This makes ThreadFunc()'s thread
enter an STA. Note that this STA is different from main()'s STA. This is a
second STA of the process.
6. We call on DisplayCurrentThreadId() and note that the thread ID of
ThreadFunc()'s thread is indeed different. Let's say this is thread_id_2.
7. We next create two instances of coclass SimpleCOMObject2
(spISimpleCOMObject2A and spISimpleCOMObject2B).
8. We then call the TestMethod1() method of spISimpleCOMObject2A and
spISimpleCOMObject2B.
9. The IDs of the threads that are running when the TestMethod1() methods
are invoked from spISimpleCOMObject2A and spISimpleCOMObject2B are
displayed one at a time.
10. You will note that this ID will be the same as the thread ID of ThreadFunc().
That is, it will be displayed as thread_id_2.
11. The ThreadFunc() thread will come to an end and we will return to main().
12. We once again invoke the TestMethod1() method on spISimpleCOMObject2
to show that nothing has changed for spISimpleCOMObject2. TestMethod1()
will still run on main()'s thread (i.e., ID: thread_id_1).

What we have demonstrated here is the straightforward creation of two STAs which were
initialized by main()'s thread and by ThreadFunc()'s thread. main()'s STA then
proceeds to contain the STA object spISimpleCOMObject2. ThreadFunc()'s thread will
also contain the STA objects spISimpleCOMObject2A and spISimpleCOMObject2B. The
following example illustrates the above:
An important point to note is that spISimpleCOMObject2, spISimpleCOMObject2A and
spISimpleCOMObject2B are all instances of the same coclass yet it is possible that they
reside in separate STAs. For a standard STA object, what matters is which STA first
instantiates it.

Notice also in this example that we had not supplied any message loops in both main()
and ThreadFunc(). They are not needed. The objects in both STAs are used within their
own Apartments and are not used across threads. We even included a call to
WaitForSingleObject() in main() and it did not cause any trouble. There were no
occasions to use the hidden windows of these STA objects. No messages were posted to
these hidden windows and so no message loops were needed.

In the next section, we will discuss something known as the Default STA. We will also
demonstrate it by example codes. The examples will also enhance the validity of the
above example which we have just studied.

The Default STA

What happens when an STA object gets instantiated inside a non-STA thread? Let us look
at a second set of example codes which will be presented below. This new set of source
codes are listed in "Test Programs\VCTests\DemonstrateDefaultSTA\VCTest01". It also
uses the example STA COM object of coclass SimpleCOMObject2 (implements interface
ISimpleCOMObject2) which was seen in the last example. The current example also uses
the utility function DisplayCurrentThreadId() that shows a message box displaying
the ID of the thread currently running.

Let's examine the code:

int main()
{
::CoInitializeEx(NULL, COINIT_MULTITHREADED);
DisplayCurrentThreadId();
if (1)
{
ISimpleCOMObject2Ptr spISimpleCOMObject2;
/* If a default STA is to be created and used, it will be created
*/
/* right after spISimpleCOMObject2 (an STA object) is created. */
spISimpleCOMObject2.CreateInstance(__uuidof(SimpleCOMObject2));
spISimpleCOMObject2 -> TestMethod1();
}
::CoUninitialize();
return 0;
}

Let us go through the program carefully:

1. The main() function calls on CoInitializeEx(NULL,


COINIT_MULTITHREADED). This way, main()'s thread initializes itself as
belonging to an MTA.
2. We next call DisplayCurrentThreadId(). The ID of main()'s thread will be
displayed.
3. Next, an STA object spISimpleCOMObject2 is instantiated inside this thread.
4. Note that spISimpleCOMObject2 is an STA object which is instantiated inside
a non-STA thread. spISimpleCOMObject2 will not reside in the MTA and will
instead be created inside a default STA.
5. We call TestMethod1() on spISimpleCOMObject2. You will note that the ID of
the thread in which TestMethod1() executes is not the same as main()'s
thread.

What happened was that spISimpleCOMObject2 will live inside a default STA. All STA
objects in a process which are created inside non-STA threads will reside in the default
STA.

This default STA was created at the same point when the affected object
(spISimpleCOMObject2, in our example) is created. This is illustrated by the following
diagram:
As can be seen in the above diagram, since spISimpleCOMObject2 lives in the default
STA and not within main()'s MTA, main()'s call to spISimpleCOMObject2 ->
TestMethod1() is an inter-apartment method call. This requires marshalling, and hence
what main() receives from COM is not an actual pointer to spISimpleCOMObject2 but a
proxy to it.

And since inter-apartment calls are actually performed, the default STA must contain a
message loop. This is provided for by COM.

Developers new to the world of COM Apartments please note well this intriguing
phenomenon: that even though a call to CreateInstance() or CoCreateInstance() is
made inside a thread, the resulting object can actually be instantiated in another thread.
This is performed transparently by COM behind the scenes. Please therefore take note of
this kind of subtle maneuvering by COM especially during debugging.

Let us now look at a more sophisticated example. This time, we use the sources listed in
"Test Programs\VCTests\DemonstrateDefaultSTA\VCTest02". This new set of sources
also use the same STA COM object of coclass SimpleCOMObject2 (implements interface
ISimpleCOMObject2) which was seen in the last example. The current example also uses
the utility function DisplayCurrentThreadId() that shows a message box displaying
the ID of the thread currently running when this function is invoked.

Let's examine the code:

int main()
{
HANDLE hThread = NULL;
DWORD dwThreadId = 0;
::CoInitializeEx(NULL, COINIT_MULTITHREADED);
DisplayCurrentThreadId();
if (1)
{
ISimpleCOMObject2Ptr spISimpleCOMObject2;
spISimpleCOMObject2.CreateInstance(__uuidof(SimpleCOMObject2));
spISimpleCOMObject2 -> TestMethod1();
hThread = CreateThread
(
(LPSECURITY_ATTRIBUTES)NULL, // SD
(SIZE_T)0, // initial stack size
(LPTHREAD_START_ROUTINE)ThreadFunc, // thread function
(LPVOID)NULL, // thread argument
(DWORD)0, // creation option
(LPDWORD)&dwThreadId // thread identifier
);
WaitForSingleObject(hThread, INFINITE);
spISimpleCOMObject2 -> TestMethod1();
}
::CoUninitialize();
return 0;
DWORD WINAPI ThreadFunc(LPVOID lpvParameter)
{
::CoInitializeEx(NULL, COINIT_MULTITHREADED);
DisplayCurrentThreadId();
if (1)
{
ISimpleCOMObject2Ptr spISimpleCOMObject2A;
ISimpleCOMObject2Ptr spISimpleCOMObject2B;
spISimpleCOMObject2A.CreateInstance(__uuidof(SimpleCOMObject2));
spISimpleCOMObject2B.CreateInstance(__uuidof(SimpleCOMObject2));
spISimpleCOMObject2A -> TestMethod1();
spISimpleCOMObject2B -> TestMethod1();
}
::CoUninitialize();
return 0;
}

Let us go through the program carefully:

1. The main() function calls CoInitializeEx(NULL, COINIT_MULTITHREADED)


thereby making main()'s thread enter an MTA.
2. We call DisplayCurrentThreadId() and note the ID of main()'s thread. Let's
say this is thread_id_1.
3. We then instantiate coclass SimpleCOMObject2 which implements interface
ISimpleCOMObject2. This object is spISimpleCOMObject2.
4. We call TestMethod1() of this STA object. The ID of the thread under which
TestMethod1() executes will be displayed. You will note that this id will not
be thread_id_1. That is, it will not be the same as main()'s thread ID. Let's
say this ID is thread_id_2.
5. We then start a second thread, executing with the entry function
ThreadFunc(), which initializes itself as belonging to an MTA.
6. The ID of this second thread will be displayed when we call
DisplayCurrentThreadId(). Let's say this is thread_id_3.
7. Two STA objects of coclass SimpleCOMObject2 (implementing
ISimpleCOMObject2) are instantiated inside this second thread.
8. We call TestMethod1() of the two STA objects inside the second thread. You
will see that the ID of the thread in which TestMethod1() executes will not
be thread_id_3. That is, it will not be the same as the ID of ThreadFunc()'s
thread.
9. Instead, the ID of the thread in which TestMethod1() executes is actually
thread_id_2! That is, it runs in the same thread as spISimpleCOMObject2 of
main().

What we have shown here is a more complicated example of the creation and use of the
default STA. spISimpleCOMObject2 is an STA object that got instantiated inside a non-
STA thread (main()'s thread). spISimpleCOMObject2A and spISimpleCOMObject2B
were also instantiated inside a non-STA thread (ThreadFunc()'s thread). Therefore, all
three objects spISimpleCOMObject2, spISimpleCOMObject2A and
spISimpleCOMObject2B will all reside in the default STA which is first created when
spISimpleCOMObject2 is created.

I strongly encourage the reader to modify the source codes and see different results.
Change one or more ::CoInitializeEx() calls from using
COINIT_APARTMENTTHREADED to COINIT_MULTITHREADED and vice versa. Put a
breakpoint in "CSimpleCOMObject2::TestMethod1()" to see the difference when it is
invoked from an STA thread and when it is invoked from an MTA thread.

In the latter case, you will see that the invocation is indirect and that some RPC calls are
involved (see diagram below).
These calls are part of the marshalling code put in motion during inter-apartment calls.

The Legacy STA

There is another type of default STA known as the Legacy STA. This STA is where the
legacy COM objects will reside in. By legacy, we mean those COM components that
have no knowledge of threads whatsoever. These objects must have their
ThreadingModel registry entry set to "Single" or have simply left out any
ThreadingModel entry in the registry.

The important point to note about these Legacy STA objects is that all instances of these
objects will be created in the same STA. Even if they are created in a thread initialized
with ::CoInitializeEx(NULL, COINIT_APARTMENTTHREADED), they will still live and
run in the legacy STA if it has already been created.

The legacy STA is usually the very first STA created in a process. If a legacy STA object
is created before any STA is created, one will be created by the COM sub-system.

The advantage of developing a legacy STA object is that all access to all instances of such
objects are serialized. You do not need any inter-apartment marshalling between any two
legacy STA objects. However, non-legacy STA objects living in non-legacy STAs that
want to make calls to legacy-STA objects must, nevertheless, arrange for inter-apartment
marshalling. The converse (legacy-STA objects making calls to non-legacy STA objects
living in non-legacy STAs) also requires inter-apartment marshalling. Not a very
attractive advantage, I think.

Let us showcase two examples. The first example we will cover uses an example Legacy
STA COM object of coclass LegacyCOMObject1. The source codes for this COM object
is listed in "LegacyCOMObject1". This COM object functions similarly with the COM
object of coclass SimpleCOMObject2 which we have seen in previous examples.
LegacyCOMObject1 also has a method named TestMethod1() which also displays the ID
of the thread in which the TestMethod1() function is executing.

The test program which uses LegacyCOMObject1 has its source codes listed in "Test
Programs\VCTests\DemonstrateLegacySTA\VCTest01". This current test program also
uses the same utility function DisplayCurrentThreadId() that shows a message box
displaying the ID of the thread currently running when this function is invoked.

Let us take a look at the code of the test program:

int main(){ ::CoInitializeEx(NULL,COINIT_APARTMENTTHREADED);


/*::CoInitializeEx(NULL, COINIT_MULTITHREADED); */
DisplayCurrentThreadId();
if (1)
{
ILegacyCOMObject1Ptr spILegacyCOMObject1;
spILegacyCOMObject1.CreateInstance(__uuidof(LegacyCOMObject1));
spILegacyCOMObject1 -> TestMethod1();
}
::CoUninitialize();
return 0;
}

Here, I added a call to ::CoInitializeEx(NULL, COINIT_APARTMENTTHREADED)


together with a commented out call to ::CoInitializeEx(NULL,
COINIT_MULTITHREADED). I added in the commented out code to easily illustrate the
effects when main()'s thread is a non-STA thread. Simply uncomment this code (and
comment the code above it!) and see different results. More on this later.

Let us go through the program carefully:

1. The thread running main() enters a standard STA.


2. We display the ID of main()'s thread. Let's say this is thread_id_1.
3. We then create a Legacy STA object of coclass LegacyCOMObject1.
4. We call the Legacy STA object's TestMethod1() method.
5. The ID of the thread in which TestMethod1() is running is displayed.
6. You will find that this thread ID will be thread_id_1.

What happened in the above example is simple: spILegacyCOMObject1, a Legacy STA


object, gets instantiated inside the very first STA created in the process (which is
main()'s STA). main()'s STA is therefore designated a Legacy STA and
spILegacyCOMObject1 will live inside this Legacy STA. Note well: the first STA
created in a process is special because it is also the Legacy STA.

If we had switched the parameter to COINIT_MULTITHREADED, as in the following:


int main()
{
/* ::CoInitializeEx(NULL, COINIT_APARTMENTTHREADED); */
::CoInitializeEx(NULL, COINIT_MULTITHREADED);
DisplayCurrentThreadId();
if (1)
{
ILegacyCOMObject1Ptr spILegacyCOMObject1;
spILegacyCOMObject1.CreateInstance(__uuidof(LegacyCOMObject1));
spILegacyCOMObject1 -> TestMethod1();
}
::CoUninitialize();
return 0;
}

The following would be the outcome:

1. The thread running main() enters an MTA.


2. We display the ID of main()'s thread. Let's say this is thread_id_1.
3. We then create a Legacy STA object of coclass LegacyCOMObject1.
4. We call the Legacy STA object's TestMethod1() method.
5. The ID of the thread in which TestMethod1() is running is displayed.
6. You will find that this thread ID will not be thread_id_1.

What happened in the above example is also straightforward: spILegacyCOMObject1, a


Legacy STA object, gets instantiated inside an MTA. It cannot live inside this MTA and
so COM creates a default Legacy STA. spILegacyCOMObject1 will therefore live inside
this COM generated Legacy STA.

A Legacy STA object behaves very much like a standard STA object as the above two
examples show. However, there is a difference: all Legacy STA objects can only be
created inside the same STA thread. We will demonstrate this with yet another example
code.

The next example code also uses the same LegacyCOMObject1 object which was
demonstrated in the last example. This current test program also uses the same utility
function DisplayCurrentThreadId() that shows a message box displaying the ID of the
thread currently running when this function is invoked. The example code is listed in
"Test Programs\VCTests\DemonstrateLegacySTA\VCTest02".

A new utility function named ThreadMsgWaitForSingleObject() makes its debut here.


It is a cool function which is useful in many applications. I shall document this function
in part two of this article as it deserves close attention on its own. For now, simply note
that ThreadMsgWaitForSingleObject() will allow a thread to wait on a handle while at
the same time service any messages that comes its way. It encapsulates the functionality
of a message loop as well as that of WaitForSingleObject(). This function will prove
very useful for us as you will see in the example code.
Let us take a look at the code of the test program:

int main()
{
HANDLE hThread = NULL;
DWORD dwThreadId = 0;
::CoInitializeEx(NULL, COINIT_APARTMENTTHREADED);
DisplayCurrentThreadId();
if (1)
{
ILegacyCOMObject1Ptr spILegacyCOMObject1;
spILegacyCOMObject1.CreateInstance(__uuidof(LegacyCOMObject1));
spILegacyCOMObject1 -> TestMethod1();
hThread = CreateThread
(
(LPSECURITY_ATTRIBUTES)NULL,
(SIZE_T)0,
(LPTHREAD_START_ROUTINE)ThreadFunc,
(LPVOID)NULL,
(DWORD)0,
(LPDWORD)&dwThreadId
);
ThreadMsgWaitForSingleObject(hThread, INFINITE);
spILegacyCOMObject1 -> TestMethod1();
}
::CoUninitialize();
return 0;
}
DWORD WINAPI ThreadFunc(LPVOID lpvParameter)
{
::CoInitializeEx(NULL, COINIT_MULTITHREADED);
DisplayCurrentThreadId();
if (1)
{
ILegacyCOMObject1Ptr spILegacyCOMObject1A;
ILegacyCOMObject1Ptr spILegacyCOMObject1B;
spILegacyCOMObject1A.CreateInstance(__uuidof(LegacyCOMObject1));
spILegacyCOMObject1B.CreateInstance(__uuidof(LegacyCOMObject1));
spILegacyCOMObject1A -> TestMethod1();
spILegacyCOMObject1B -> TestMethod1();
}
::CoUninitialize();
return 0;
}

Let us go through the program carefully:

1. The thread executing main() enters an STA.


2. We display the ID of the main() thread. Let's say this thread ID is
thread_id_1.
3. We create an instance of coclass LegacyCOMObject1 (spILegacyCOMObject1).
4. We invoke TestMethod1() of spILegacyCOMObject1.
5. The ID of the thread executing TestMethod1() is displayed. You will note that
this is thread_id_1.
6. We then start a thread headed by ThreadFunc(). Thereafter, we wait for this
thread to finish by calling ThreadMsgWaitForSingleObject().
7. The ThreadFunc() thread is initialized as a non-STA thread.
8. In the ThreadFunc() thread, we instantiate two instances of coclass
LegacyCOMObject1 (spILegacyCOMObject1A and spILegacyCOMObject1B).
9. We call TestMethod1() on spILegacyCOMObject1A and
spILegacyCOMObject1B.
10. The ID of the thread executing each call to TestMethod1() is revealed. You
will note that this is thread_id_1.
11. The ThreadFunc() thread will then complete and we return to main()'s
thread.
12. We call TestMethod1() of spILegacyCOMObject1 and note that the ID of the
thread executing TestMethod1() of spILegacyCOMObject1 has not changed.
It is still thread_id_1.

Let us analyze this latest test program. The thread executing main() enters a standard
STA. This STA is the first STA created in the process. Recall that the first STA created in
a process is also the Legacy STA, hence the main()'s STA is the Legacy STA. Now,
spILegacyCOMObject1 (in main()) is created as a normal STA object and it resides in
the same STA as the one just created in main().

When the second thread (headed by ThreadFunc()) starts up, it is started as an MTA.
Hence any STA object created inside this thread cannot live in this MTA (it cannot use
ThreadFunc()'s thread). Both spILegacyCOMObject1A and spILegacyCOMObject1B are
STA objects and hence they cannot live inside ThreadFunc()'s MTA. Now, if
spILegacyCOMObject1A and spILegacyCOMObject1B are normal STAs, a new STA will
be created for them to live in. However, they are Legacy STAs and so they must live in
the legacy STA (if one already exists, and one already does exist).

The end result is that they will be accommodated in the Legacy STA created in main()'s
thread. This is why, when you invoke TestMethod1() from ThreadFunc(), the call is
actually marshaled to main()'s thread. There is actually inter-apartment marshalling
between ThreadFunc()'s MTA apartment (where the TestMethod1() call originates) and
main()'s STA apartment (where the TestMethod1() call is executed).

This is illustrated by the following diagram where spILegacyCOMObject1A is created in


ThreadFunc():
Note point 3 in the diagram: "The creation call is marshaled by COM into the Legacy
STA". In order for the creation call to be successful, COM has to communicate with the
Legacy STA and tell it to create spILegacyCOMObject1A. This communication requires a
message loop to exist in the target Legacy STA. Hence the need for the services of
ThreadMsgWaitForSingleObject().

EXE COM Servers And Apartments

Thus far, we have discussed COM servers implemented inside DLLs. However, this
article will not be complete without touching on COM servers implemented in EXEs. My
aim is to show how Apartments, the STA in particular, are implemented inside an EXE
server. Let us start with examining two of the main differences between a DLL server and
an EXE server.

Difference 1: The Way Objects Are Created

When COM wishes to create a COM object which is implemented inside a DLL, it loads
the DLL, connects with its exported DllGetClassObject() function, calls it, and obtains
a pointer to the IClassFactory interface of the class factory object of the COM object. It
is from this IClassFactory interface pointer that the COM object is created.

The story with EXE Servers has the same eventuality: obtaining the IClassFactory
interface pointer of the class factory object of the COM object to be created and then
creating the COM object through it. What happens before that is the difference between a
DLL server and an EXE Server.
A DLL server exports the DllGetClassObject() function for COM to extract the class
factory but an EXE server cannot export any function. An EXE server instead has to
register its class factory in the COM sub-system when it starts up, and then revoke the
class factory when it shuts down. This registration is done via the API
CoRegisterClassObject().

Difference 2: The Way The Apartment Model Of Objects Are Indicated

As mentioned earlier in this article, objects implemented in DLLs indicate their


Apartment Models by appropriately setting the "ThreadingModel" registry string value
which is located in the object's "InProcServer32" registry entry.

Objects implemented in an EXE server do not set this registry value. Instead, the
Apartment Model of the thread which registers the object's class factory determines the
object's Apartment Model:

::CoInitializeEx(NULL, COINIT_APARTMENTTHREADED);
...
...
...
IUnknown* pIUnknown = NULL;
DWORD dwCookie = 0;
pCExeObj02_Factory -> QueryInterface(IID_IUnknown,
(void**)&pIUnknown);
if (pIUnknown)
{
hr = ::CoRegisterClassObject
(
CLSID_ExeObj02,
pIUnknown,
CLSCTX_LOCAL_SERVER,
REGCLS_MULTIPLEUSE | REGCLS_SUSPENDED,
&dwCookie
);
pIUnknown -> Release();
pIUnknown = NULL;
}

In the above code snippet, we are attempting to register a class factory for the
CLSID_ExeObj02 COM object inside a thread. Note the call to
::CoInitializeEx(NULL, COINIT_APARTMENTTHREADED) at the beginning. This call
indicates to COM that CLSID_ExeObj02 COM objects will live in an STA. The thread
which called CoRegisterClassObject() is the lone thread inside this STA. This implies
that there will be a message loop inside this thread and that all access to any
CLSID_ExeObj02 object created by any client are serialized by this message loop.

If the call to CoInitializeEx() used COINIT_MULTITHREADED instead,


CLSID_ExeObj02 COM objects will live in an MTA. This means that CLSID_ExeObj02
COM objects and its class factory object can be accessed from any thread. Such threads
can be those which are implemented internally in the EXE Server (as part of the logic of
the implementation) or those from the RPC thread pool the purpose of which is to serve
external clients' method calls. The implementation of the CLSID_ExeObj02 COM object
must therefore ensure internal serialization to whatever extent required. In many ways,
this is much more efficient as compared with STAs.

Aside from the above two differences, take note that while it is possible that STA objects
inside a DLL server receive method calls only from inside its owning STA thread, all
method calls from a client to an STA object inside an EXE COM server will invariably be
invoked from an external thread. This implies the use of marshalling proxies and stubs
and, of course, a message loop inside the object's owning STA thread.

Demonstrating The STA Inside A COM EXE Server

As usual, we shall attempt to demonstrate STAs inside COM EXE Servers via an
example code. The example code for this section is rather elaborate. It can be found in the
following folder: "Test Programs\VCTests\DemonstrateExeServerSTA" in the sample
code that accompanies this article. There are three parts to this set of sample code:

1. Interface ("Interface\ExeServerInterfaces" subfolder).


2. Implementation ("Implementation\ExeServerImpl" subfolder).
3. Client ("Client\VCTest01" subfolder).

Please note that in order to use the ExeServerImpl COM server, you will need to
compile code in "Implementation\ExeServerImpl" and then register the resultant
ExeServerImpl.exe by typing the following command in a command prompt window:

ExeServerImpl RegServer

Do not type any "-" or "\" before "RegServer".

The Interface

The code in the Interface part is actually an ATL project ("ExeServerInterfaces.dsw")


which I use to define three interfaces: IExeObj01, IExeObj02 and IExeObj03. The three
interfaces each contain only one single method (of the same name): TestMethod1(). This
ATL project also specifies three coclasses which are identified by CLSID_ExeObj01
(specified to contain an implementation of interface IExeObj01), CLSID_ExeObj02
(specified to contain an implementation of interface IExeObj02) and CLSID_ExeObj03
(specified to contain an implementation of interface IExeObj03).

There is no meaningful implementation of these interfaces and coclass's in this project. I


created this project in order to use the ATL wizards to help me manage the IDL file and to
automatically generate the appropriate "ExeServerInterfaces.h" and
"ExeServerInterfaces_i.c" files. These generated files are used by both the
Implementation and Client code.
I used a separate ATL project to generate the above-mentioned files because I wanted my
implementation code to be non-ATL based. I wanted a COM EXE implementation based
on a simple Windows application so that I could put in various customized constructs that
can help me illustrate STAs clearer. With the ATL wizards, things can be a little more
inflexible.

The Implementation

The code in the Implementation part provides an implementation of the interfaces and
coclass's described in the Interface part. Except for CExeObj02, each of the
implementation of TestMethod1() contains only a message box display:

STDMETHODIMP CExeObj01::TestMethod1()
{
TCHAR szMessage[256];
sprintf (szMessage, "0x%X", GetCurrentThreadId());
::MessageBox(NULL, szMessage, "CExeObj01::TestMethod1()", MB_OK);
return S_OK;
}
STDMETHODIMP CExeObj03::TestMethod1()
{
TCHAR szMessage[256];
sprintf (szMessage, "0x%X", GetCurrentThreadId());
::MessageBox(NULL, szMessage, "CExeObj03::TestMethod1()", MB_OK);
return S_OK;
}

The purpose of doing this is to show the ID of the thread which is executing when each of
the methods is invoked. This should match with the ID of their containing STA thread. I
have made CExeObj02 a little special. This C++ class provides an implementation of
IExeObj02. It also contains a pointer to an IExeObj01 object:

class CExeObj02 : public CReferenceCountedObject, public IExeObj02


{
public :
CExeObj02();
~CExeObj02();
...
...
...
protected :
IExeObj01* m_pIExeObj01;
};

During the construction of CExeObj02, we will instantiate m_pIExeObj01:

CExeObj02::CExeObj02()
{
::CoCreateInstance
(
CLSID_ExeObj01,
NULL,
CLSCTX_LOCAL_SERVER,
IID_IExeObj01,
(LPVOID*)&m_pIExeObj01
);
}

The purpose of doing this is to show later that CExeObj02 and the object behind
m_pIExeObj01 will run in separate STAs. Take a look at this class' TestMethod1()
implementation:

STDMETHODIMP CExeObj02::TestMethod1()
{
TCHAR szMessage[256];
sprintf (szMessage, "0x%X", GetCurrentThreadId());
::MessageBox(NULL, szMessage, "CExeObj02::TestMethod1()", MB_OK);
return m_pIExeObj01 -> TestMethod1();
}

Two message boxes will be displayed: the first one showing CExeObj02's thread ID and
the second will show m_pIExeObj01's thread ID. These IDs will be different as will be
seen later on when we run the client code.

In addition to providing implementations to the interfaces, the implementation code also


provide class factories for each of the coclass's. These are CExeObj01_Factory,
CExeObj2_Factory and CExeObj03_Factory.

Let us now focus our attention on the WinMain() function:

int APIENTRY WinMain


(
HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPSTR lpCmdLine,
int nCmdShow
)
{
MSG msg;
HRESULT hr = S_OK;
bool bRun = true;
DisplayCurrentThreadId();
hr = ::CoInitializeEx(NULL, COINIT_APARTMENTTHREADED);
...
...
...
if (bRun)
{
DWORD dwCookie_ExeObj01 = 0;
DWORD dwCookie_ExeObj02 = 0;
DWORD dwCookie_ExeObj03 = 0;
DWORD dwThreadId_RegisterExeObj02Factory = 0;
DWORD dwThreadId_RegisterExeObj03Factory = 0;
g_dwMainThreadID = GetCurrentThreadId();
RegisterClassObject<CExeObj01_Factory>(CLSID_ExeObj01,&dwCookie_ExeO
bj01);
dwThreadId_RegisterExeObj02Factory
= RegisterClassObject_ViaThread
(ThreadFunc_RegisterExeObj02Factory, &dwCookie_ExeObj02);
dwThreadId_RegisterExeObj03Factory
= RegisterClassObject_ViaThread
(ThreadFunc_RegisterExeObj03Factory, &dwCookie_ExeObj03);
::CoResumeClassObjects();
// Main message loop:
while (GetMessage(&msg, NULL, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
StopThread(dwThreadId_RegisterExeObj02Factory);
StopThread(dwThreadId_RegisterExeObj03Factory);
::CoRevokeClassObject(dwCookie_ExeObj01);
::CoRevokeClassObject(dwCookie_ExeObj02);
::CoRevokeClassObject(dwCookie_ExeObj03);
}
::CoUninitialize();
return msg.wParam;
}

I have left out some code in WinMain() that pertains to EXE server registration and
unregistration which are not relevant to our discussion here. I have narrowed down the
code to show only the runtime class factory registration process.

I created two helper functions RegisterClassObject() and


RegisterClassObject_ViaThread() to help me with simplifying the call to
CoRegisterClassObject().

These are simple helper functions and, to avoid digression, I will not discuss them in this
article but to provide only a summary of what these functions do:

• RegisterClassObject() - instantiates a class factory based on the class


name (supplied as a template parameter) and then registers this class factory
to COM as the class factory for a COM object the CLSID of which is supplied
as a parameter to the RegisterClassObject() function.
• RegisterClassObject_ViaThread() - starts a thread whose job is to register
a class factory using the RegisterClassObject() function.

Whenever the EXE COM Server starts up, it registers all three class factories (albeit not
all of them are performed in WinMain()'s thread).

Notice the call to CoInitializeEx(NULL, COINIT_APARTMENTTHREADED) at the


beginning of the function. This is important and it makes the COM objects created by the
class factory registered in WinMain()'s thread belong to an STA (the one in which
WinMain()'s thread is currently running in). This class factory is CExeObj01_Factory
and the CLSID of the objects it creates is CLSID_ExeObj01.
After performing class factories registration, WinMain() enters a message loop. This
message loop services all method calls to CLSID_ExeObj01 COM objects created by
clients.

Let us now observe the other threads in action:

DWORD WINAPI ThreadFunc_RegisterExeObj02Factory(LPVOID lpvParameter)


{
MSG msg;
PStructRegisterViaThread pStructRegisterViaThread
= (PStructRegisterViaThread)lpvParameter;
::CoInitializeEx(NULL, COINIT_APARTMENTTHREADED);
DisplayCurrentThreadId();
pStructRegisterViaThread -> dwThreadId = GetCurrentThreadId();
RegisterClassObject<CExeObj02_Factory>
(CLSID_ExeObj02, &(pStructRegisterViaThread -> dwCookie));
SetEvent(pStructRegisterViaThread -> hEventRegistered);
// Main message loop:
while (GetMessage(&msg, NULL, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
::CoUninitialize();
return 0;
}
DWORD
WINAPI
ThreadFunc_RegisterExeObj03Factory(LPVOID lpvParameter) {
MSG msg;
PStructRegisterViaThread pStructRegisterViaThread
= (PStructRegisterViaThread)lpvParameter;
::CoInitializeEx(NULL, COINIT_APARTMENTTHREADED);
DisplayCurrentThreadId();
pStructRegisterViaThread -> dwThreadId = GetCurrentThreadId();
RegisterClassObject<CExeObj03_Factory>
(CLSID_ExeObj03, &(pStructRegisterViaThread -> dwCookie));
SetEvent(pStructRegisterViaThread -> hEventRegistered);
// Main message loop:
while (GetMessage(&msg, NULL, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
::CoUninitialize();
return 0;
}

Each of the threads perform the same actions:

1. Each initializes itself as an STA thread by calling CoInitializeEx(NULL,


COINIT_APARTMENTTHREADED).
2. Each displays the ID of the thread in which it is running
(DisplayCurrentThreadId()).
3. Each registers a class factory for CExeObj02_Factory and
CExeObj03_Factory respectively.
4. Each enters a message loop.

What we obtain eventually can be summarized in the following table:

S/No Thread Function Class Factory COM coclass Apartment


1 WinMain() CExeObj01_Factory CLSID_ExeObj01 STA
ThreadFunc_
2 RegisterExeObj02Factory()
CExeObj02_Factory CLSID_ExeObj02 STA

ThreadFunc_
3 RegisterExeObj03Factory()
CExeObj03_Factory CLSID_ExeObj03 STA

The Client

Let us move on now to the Client. The Client code is simple. It consists of a main()
function that instantiates two instances each of coclass'es CLSID_ExeObj01,
CLSID_ExeObj02 and CLSID_ExeObj03. Each instantiation is referenced by a pointer to
interfaces IExeObj01, IExeObj02 and IExeObj03 respectively:

int main()
{
IExeObj01* pIExeObj01A = NULL;
IExeObj01* pIExeObj01B = NULL;
IExeObj02* pIExeObj02A = NULL;
IExeObj02* pIExeObj02B = NULL;
IExeObj03* pIExeObj03A = NULL;
IExeObj03* pIExeObj03B = NULL;
HRESULT hr = ::CoInitializeEx(NULL, COINIT_MULTITHREADED);
::CoCreateInstance
(
CLSID_ExeObj01,
NULL,
CLSCTX_LOCAL_SERVER,
IID_IExeObj01,
(LPVOID*)&pIExeObj01A
);
::CoCreateInstance
(
CLSID_ExeObj01,
NULL,
CLSCTX_LOCAL_SERVER,
IID_IExeObj01,
(LPVOID*)&pIExeObj01B
);
::CoCreateInstance
(
CLSID_ExeObj02,
NULL,
CLSCTX_LOCAL_SERVER,
IID_IExeObj02,
(LPVOID*)&pIExeObj02A
);
::CoCreateInstance
(
CLSID_ExeObj02,
NULL,
CLSCTX_LOCAL_SERVER,
IID_IExeObj02,
(LPVOID*)&pIExeObj02B
);
::CoCreateInstance
(
CLSID_ExeObj03,
NULL,
CLSCTX_LOCAL_SERVER,
IID_IExeObj03,
(LPVOID*)&pIExeObj03A
);
::CoCreateInstance
(
CLSID_ExeObj03,
NULL,
CLSCTX_LOCAL_SERVER,
IID_IExeObj03,
(LPVOID*)&pIExeObj03B
);
...
...
...
}

Note our call to CoInitializeEx(NULL, COINIT_MULTITHREADED) at the beginning of


main(). Unlike the case with using DLL servers, this call will have no effect on the
Apartment Model used by the COM objects that we create.

The Client code then proceeds to call each interface pointer's TestMethod1() method
before releasing all interface pointers:

if (pIExeObj01A)
{
pIExeObj01A -> TestMethod1();
}
if (pIExeObj01B)
{
pIExeObj01B -> TestMethod1();
}
if (pIExeObj02A)
{
pIExeObj02A -> TestMethod1();
}
if (pIExeObj02B)
{
pIExeObj02B -> TestMethod1();
}
if (pIExeObj03A)
{
pIExeObj03A -> TestMethod1();
}
if (pIExeObj03B)
{
pIExeObj03B -> TestMethod1();
}

Let us observe what will happen when the Client application runs:

1. When the first call to ::CoCreateInstance() is made, COM will launch our
EXE COM server.
2. Our COM server will then run its WinMain() function. The first visible thing it
will do is to display WinMain()'s thread ID in a message box. Let's say this is
thread_id_1.
3. Next, the class factory for the CLSID_ExeObj01 COM object is registered
within WinMain()'s thread. Hence CLSID_ExeObj01 COM objects will live in
the STA headed by WinMain()'s thread.
4. Our COM server will then launch the thread headed by
ThreadFunc_RegisterExeObj02Factory() which will register the class
factory for the CLSID_ExeObj02 COM object. The ID for this thread is
displayed by a message box at the start of the thread. Let's say this is
thread_id_2.
5. CLSID_ExeObj02 COM objects will live in the STA headed by the thread with
ID thread_id_2.
6. Our COM server will then launch the thread headed by
ThreadFunc_RegisterExeObj03Factory() which will register the class
factory for the CLSID_ExeObj03 COM object. The ID for this thread is
displayed by a message box at the start of the thread. Let's say this is
thread_id_3.
7. CLSID_ExeObj03 COM objects will live in the STA headed by the thread with
ID thread_id_3.
8. Back to the client code. When TestMethod1() is invoked on pIExeObj01A, the
ID of the thread which is executing is displayed. You will note that this is
thread_id_1 which is consistent with point 3 above.
9. The same ID will be displayed when TestMethod1() is invoked on
pIExeObj01B.
10. When TestMethod1() is invoked on pIExeObj02A, two thread IDs will be
displayed one after the other. The first one is the ID of the thread executing
when pIExeObj02A -> TestMethod1() is invoked, and this is thread_id_2
which is consistent with point 5 above.
11. When the second message box is displayed, we will see the ID of the thread
running when the CLSID_ExeObj01 COM object contained inside pIExeObj02A
is invoked. This is not thread_id_2 but thread_id_1! This is perfectly in line
with point 3 above.
12. The same pair of IDs will be displayed when TestMethod1() is invoked on
pIExeObj02B.
13. When pIExeObj03A -> TestMethod1() and pIExeObj03B -> TestMethod1()
are invoked in the statements that follow, we will see that the ID of the
thread executing them is thread_id_3. This is again consistent with point 7
above.

If you were to put breakpoints in CExeObj01::TestMethod1() and


CExeObj02::TestMethod1(), you will observe from the call stack that calls between
them are actually marshaled.

We have thus demonstrated STAs as used inside a COM EXE Server. I strongly
encourage the reader to experiment with the code and see the effects of changing one or
more threads from STAs to MTAs. It's a fun way to learn.

Before I conclude this last major section, please allow me to present two short variations
to the Implementation code. The first shows the dramatic effects of not providing the
appropriate message loop inside a class registration thread. The second shows the
completely harmless effects of not providing one!

Variation 1

Let us examine the first case. In the EXE COM server code's main.cpp file, we modify
the ThreadFunc_RegisterExeObj02Factory() function as follows:

DWORD WINAPI ThreadFunc_RegisterExeObj02Factory(LPVOID lpvParameter)


{
MSG msg;
PStructRegisterViaThread pStructRegisterViaThread
= (PStructRegisterViaThread)lpvParameter;
::CoInitializeEx(NULL, COINIT_APARTMENTTHREADED);
DisplayCurrentThreadId();
pStructRegisterViaThread -> dwThreadId = GetCurrentThreadId();
RegisterClassObject<CExeObj02_Factory>
(CLSID_ExeObj02, &(pStructRegisterViaThread -> dwCookie));
SetEvent(pStructRegisterViaThread -> hEventRegistered);
Sleep(20000); /* Add Sleep() statement here. */
/* Main message loop: */
while (GetMessage(&msg, NULL, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
::CoUninitialize();
return 0;
}

We simply add a Sleep() statement right above the message loop. Compile the EXE
COM server again. Run the client in debug mode (so that you can observe what happens
when coclass CLSID_ExeObj02 is instantiated as in the following call to
CoCreateInstance()):

::CoCreateInstance
(
CLSID_ExeObj02,
NULL,
CLSCTX_LOCAL_SERVER,
IID_IExeObj02,
(LPVOID*)&pIExeObj02A
);

You will note that this call will appear to hang. But hold on, if you had patiently waited
for about 20 seconds, the call will go through. What happened? Well, turns out that
because CLSID_ExeObj02 is an STA object, the call to CoCreateInstance() resulted in
a need to communicate with the message loop of the thread that registered the
CLSID_ExeObj02 class factory.

By blocking the thread with a Sleep() statement, the thread's message loop does not get
serviced. The call to create instance will not return in this case. But once the Sleep()
statement returns, the message loop is started and the create instance call is serviced and
will return in time.

Note therefore the importance of the message loop in a COM EXE Server STA thread.

Variation 2

This time, let us modify the ThreadFunc_RegisterExeObj02Factory() function as


follows:

DWORD WINAPI ThreadFunc_RegisterExeObj02Factory(LPVOID lpvParameter)


{
MSG msg;
PStructRegisterViaThread pStructRegisterViaThread
= (PStructRegisterViaThread)lpvParameter;
::CoInitializeEx(NULL, COINIT_MULTITHREADED);/*1.Make this an MTA
thread.*/
DisplayCurrentThreadId();
pStructRegisterViaThread -> dwThreadId = GetCurrentThreadId();
RegisterClassObject<CExeObj02_Factory>
(CLSID_ExeObj02, &(pStructRegisterViaThread -> dwCookie));
SetEvent(pStructRegisterViaThread -> hEventRegistered);
Sleep(INFINITE); /* 2. Set to Sleep() infinitely. */
/* 3. Comment out Main message loop. */
/* while (GetMessage(&msg, NULL, 0, 0)) */
/* { */
/* TranslateMessage(&msg); */
/* DispatchMessage(&msg); */
/* } */
::CoUninitialize();
return 0;
}

This time, we change the thread into an MTA thread, set Sleep()'s parameter to
INFINITE, and comment out the message loop altogether.
You will find that the call to ::CoCreateInstance() on coclass CLSID_ExeObj02 in the
client will go through successfully albeit CLSID_ExeObj02 is now an MTA object and the
calls to its TestMethod1() method may display different thread IDs.

What we have shown clearly here is that as long as the MTA thread that registers a class
factory remains alive (via Sleep(INFINITE)), calls to the class factory goes through
(without the need for any message loop, by the way).

Note that regardless of whether ThreadFunc_RegisterExeObj02Factory() is an STA or


MTA thread, if it had fallen through and exited after registering its class factory,
unpredictable results will occur when coclass CLSID_ExeObj02 is instantiated in the
client.

In Conclusion

I certainly hope that you have benefited from the explanatory text as well as the example
code of this long article. I have done my level best to be as thorough and exhaustive as
possible to lay a strong foundation on the concepts of Single-Threaded Apartments.

In this part one, I have demonstrated a few inter-apartment method calls for which COM
has already paved the way. We have also seen how COM automatically arranges for
objects to be created in the appropriate apartment threads. Proxies and stubs are generated
internally and the marshalling of proxies are performed transparently without the
developers' knowledge.

In part two, we will touch on more advanced features of COM that pertain to STAs. We
shall show how to perform explicit marshalling of COM object pointers from one
apartment to another. We will also show how an object can fire events from an external
thread. Lower-level codes will be explored.

Change of Article Title

Please note that I have changed the title of this article from "TEventHandler - A C++
COM Event Handler For IDispatch-Based Events" to the current title "Understanding
COM Event Handling". The latter, I believe, is a better title which gives a more accurate
picture of the intended theme of this article - i.e., to expound carefully the internal
mechanisms behind COM event handling.

Introduction

If you have ever done any development work involving the use of COM objects, chances
are you would have encountered the need for COM object event handling. Visual Basic
users will know how simple it is to connect with the event interface(s) of the COM (or
ActiveX) objects. The VB IDE lays out the event handling function codes nicely for the
user. All the user has to do is to fill in the details of the event handling functions.
For Visual C++ users, this is not always so straight forward. If your COM object happens
to be an ActiveX control and you are using MFC, then yes, the Visual C++ IDE provides
Wizards that can help you generate event handler function stubs. All the necessary codes
(e.g., inserting the event sink map and event entry macros) are done automatically for
you.

But what if your COM object is not an ActiveX control? What if you are using straight
COM objects which also fire events and you need to handle those events?

If you are an MFC user, you may want to tinkle with the various MFC macros to see if
you can fit in an event handler function into your code either manually or via the Wizard.
I personally believe this is possible. But you need to be armed with an intimate
knowledge of MFC and its generated macros.

If you do not use MFC, you may want to experiment with ATL codes (e.g.,
IDispEventImpl, BEGIN_SINK_MAP, SINK_ENTRY_EX, etc.) to perform event handling.
The ATL macro codes are certainly not simple but they are well-documented in MSDN
and they do provide standard handling mechanisms.

In this article, I will go back to basics and seek to explain the fundamental principles of
how event handling is done in COM. I will also provide a C+ class which serves as a
basic and simple (at least in terms of code overhead) facilitator for COM Object Event
Handling.

I do this via a special custom-developed template class named TEventHandler which I


have used in many projects. This class uses COM first principles and primitives and
avoids the use of complicated macros. The sections following expound this class in detail.
I assume that the reader is suitably conversant with C++, ATL and the concepts of
template classes. However, before we start discussing the TEventHandler class, let us
explore the fundamental principles of Event Handling in COM.

Event Handling in COM.

Incoming Interfaces

When we develop our usual COM objects, we provide implementations for interfaces
(defined in an IDL file) which we write ourselves or have been supplied to us. Such
implementations facilitate what are known as "incoming" interfaces. By "incoming", we
imply that the object "listens" to its client. That is, the client calls methods of the
interface and, in this way, "talks" to the object.

Referring to the diagram below, we can say that ISomeInterface is an "incoming"


interface provided by the COM object on the right.
Outgoing Interfaces

As Kraig Brockschmidt puts it so well in his book "Inside OLE", many COM objects
themselves have useful things to say to their clients. And clients may want to listen to
COM objects too. If such a two-way dialog is desired, something known as an
"outgoing" interface is required.

The term "outgoing" is used in the context of the COM object. It is outgoing in the
perspective of the COM object. Imagine a situation in which the role of "talker" and
"listener" is reversed as shown in the diagram below:

Referring to the diagram above, we can say that ITalkBackInterface is an "outgoing"


interface supported by the COM object on the right. The COM object invokes the
methods of ITalkBackInterface and it is the Client that implements the
ITalkBackInterface methods.

A COM object that supports one or more outgoing interfaces is known as a Connectable
Object or a Source. A connectable object can support as many outgoing interfaces as it
likes. Each method of the outgoing interface represents a single event or request.

All COM objects, regardless of whether they are non-visual COM objects or ActiveX
controls (generated manually or via MFC or ATL), use the same mechanism
(connectability) for firing events to their clients.

Events and Requests

Events are used to tell a client that something of interest has occurred in the object - a
property has changed or the user has clicked a button. Events are particularly important
for COM controls. Events are fired by COM objects and no response from the client is
expected. In other words, they are simple notifications.

Requests, on the other hand, is how a COM object asks the client a question and expects
a response in return.

Events and requests are similar to Windows messages, some of which inform a window
of an event (e.g., WM_MOVE) and some will ask for information from the window (e.g.,
WM_QUERYENDSESSION).

Sinks

In both cases, the client of the COM object must listen to what the object has to say and
then use that information appropriately. It is the client, therefore, that implements the
outgoing interfaces which are also known as sinks (I really dislike this name but it has
become common and ubiquitous in the world of COM and .NET).

From a sink's perspective, this outgoing interface is actually incoming. The sink listens to
the COM object through it. In this context, the connectable COM object plays the role of
a client.

How Things Are Tied Up Together

Let us take a helicopter view of the entire communications situation. There are three
participants:

• The COM object itself.


• The Client of the COM object.
• The Sink.
The Client communicates with the COM object as usual via the object's incoming
interface(s).

In order for the COM object to communicate back to the client in the other direction, the
COM object must somehow obtain a pointer to an outgoing interface implemented
somewhere in the client. Through this pointer, the COM object will send events and
requests to the client.

This somewhere is the Sink. Let us illustrate the above with a simple diagram:

Note that the Sink is an object by itself. The Sink provides the implementation for one or
more outgoing interfaces. It is also usually strongly tied to the other parts of the client's
code because the whole idea of implementing a sink is for the client code to be able to
react to an event and/or to respond to some request from the COM object.

How Does A COM Object Connect To A Sink?

But how does a COM object connect to a Sink in the first place? This is where the notion
of Connection Points and Connection Point Containers come in. We will explore this in
detail in the next section.
Connection Points And Connection Point Containers

For each outgoing interface that a COM object supports (note the use of the word
support, the COM object itself does not implement this interface, it invokes it), the COM
object exposes a small object called a connection point. This connection point object
implements the IConnectionPoint interface.

It is through this IConnectionPoint interface that the client passes its Sink's outgoing
interface implementation to the COM object. Reference counts of these
IConnectionPoint objects are kept by both the client and the COM object itself to
ensure the lifespan of the two-way communications.

The method to call (from the client side) to establish event communication with the COM
object is the IConnectionPoint::Advise() method. The converse of Advise() is
IConnectionPoint::Unadvise() which terminates a connection. Please refer to MSDN
documentation for more details of these methods.

Hence, via IConnectionPoint interface method calls, a client can start listening to a set
of events from the COM object. Note also that because a COM object maintains a
separate IConnectionPoint interface for every outgoing interface it supports, a client
must be able to use the correct connection point object for every sink it implements.

How then, does the Client choose the appropriate connection point for a sink? In comes
Connection Point Containers. An object which is connectable must also be a Connection
Point Container. That is, it must implement the IConnectionPointContainer interface.
Through this interface, a Client requests for the appropriate Connection Point object of an
outgoing interface.

When a client wants to connect a Sink to a Connection Point, it asks the Connection Point
Container for the Connection Point object for the outgoing interface implemented by that
Sink. When it receives the appropriate connection point object, the Client passes the
Sink's interface pointer to that connection point.

The IConnectionPointContainer interface pointer itself can be obtained easily via


QueryInterface() on the COM object itself. Nothing speaks better than an example
code which is listed below:

void Sink::SetupConnectionPoint(ISomeInterface* pISomeInterface)


{
IConnectionPointContainer* pIConnectionPointContainerTemp = NULL;
IUnknown* pIUnknown = NULL;
/*QI this object itself for its IUnknown pointer which will be used
*/
/*later to connect to the Connection Point of the ISomeInterface
object.*/
this -> QueryInterface(IID_IUnknown, (void**)&pIUnknown);
if (pIUnknown)
{
/* QI pISomeInterface for its connection point.*/
pISomeInterface -> QueryInterface (IID_IConnectionPointContainer,
(void**)&pIConnectionPointContainerTemp);

if (pIConnectionPointContainerTemp)
{
pIConnectionPointContainerTemp ->
FindConnectionPoint(__uuidof(ISomeEventInterface),
&m_pIConnectionPoint);
pIConnectionPointContainerTemp -> Release();
pIConnectionPointContainerTemp = NULL;
}

if (m_pIConnectionPoint)
{
m_pIConnectionPoint -> Advise(pIUnknown, &m_dwEventCookie);
}

pIUnknown -> Release();


pIUnknown = NULL;
}
}

The sample function above describes the SetupConnectionPoint() method of a Sink


class. The Sink class implements the methods of the outgoing interface
ISomeEventInterface. The SetupConnectionPoint() method takes a parameter which
is pointer to an interface named ISomeInterface. The COM object behind
ISomeInterface is assumed to be a Connection Point Container. The following is an
outline of the function's logic:

1. We first QueryInterface() the Sink object itself for its IUnknown interface
pointer. This IUnknown pointer will be used later in the call to
IConnectionPoint::Advise().
2. Having successfully obtained the IUnknown pointer, we next
QueryInterface() the object behind pISomeInterface for its
IConnectionPointContainer interface.
3. Having successfully obtained the IConnectionPointContainer interface
pointer, we use it to find the appropriate Connection Point object for the
outgoing interface ISomeEventInterface.
4. If we are able to obtain this Connection Point object (it will be represented by
m_pIConnectionPoint), we will proceed to call its Advise() method.
5. From here onwards, whenever the COM object behind pISomeInterface fires
an event to the sink (by calling one of the methods of
ISomeEventInterface), the corresponding method implementation in the
Sink object will be invoked.

I certainly hope that the above introductory sections on Event Handling in COM,
Connection Points and Connection Point Containers will have served to provide the
reader with a clear understanding of the basics of event handling. It is worth re-
mentioning that the above principles are used whatever the type of COM object is
involved (e.g., straight COM object, ActiveX controls, etc.).
With the basics explained thoroughly, we shall proceed to expound on the sample source
codes, especially the TEventHandler template class.

The Sample Source Codes

I will attempt to explain TEventHandler by running through some example codes. Please
refer to the source files which are contained in the TEventHandler_src.zip file. In the set
of sample codes, I have provided two sets of projects:

• EventFiringObject
• TestClient

EventFiringObject

The EventFiringObject project contains the code for a simple COM object which
implements an interface named IEventFiringObject. This COM object is also a
Connection Point Container which recognizes the _IEventFiringObjectEvents
connection point.

The relevant IDL constructs for this COM object is listed below for discussion purposes:

[
object,
uuid(8E396CC0-A266-481E-B6B4-0CB564DAA3BC),
dual,
helpstring("IEventFiringObject Interface"),
pointer_default(unique)
]
interface IEventFiringObject : IDispatch
{
[id(1), helpstring("method TestFunction")]
HRESULT TestFunction([in] long lValue);
};
[
uuid(32F2B52C-1C07-43BC-879B-04C70A7FA148),
helpstring("_IEventFiringObjectEvents Interface")
]
dispinterface _IEventFiringObjectEvents
{
properties:
methods:
[id(1), helpstring("method Event1")] HRESULT Event1([in] long lValue);
};
[
uuid(A17BC235-A924-4FFE-8D96-22068CEA9959),
helpstring("EventFiringObject Class")
]
coclass EventFiringObject
{
[default] interface IEventFiringObject;
[default, source] dispinterface _IEventFiringObjectEvents;
};
In the EventFiringObject project, we implement a C++ ATL class named
CEventFiringObject which implements the specifications of coclass
EventFiringObject. CEventFiringObject provides a simple implementation of the
TestFunction() method. It simply fires Event1 which is specified in
_IEventFiringObjectEvents.

STDMETHODIMP CEventFiringObject::TestFunction(long lValue)


{
/* TODO: Add your implementation code here */
Fire_Event1(lValue);
return S_OK;
}

TestClient

TestClient is a simple test application: an MFC dialog-based application which


instantiates the EventFiringObject COM object. It also attempts to handle the Event1
event fired from EventFiringObject. I will walk through the TestClient code more
thoroughly to explain the process of event handling.

The client code centers around the CTestClientDlg class which is derived from
CDialog. In TestClientDlg.h, notice that we declare an instance of a smart pointer object
which will be tied to the COM object which implements IEventFiringObject:

/* ***** Declare an instance of a IEventFiringObject smart pointer.


***** */
IEventFiringObjectPtr m_spIEventFiringObject;

Then, in the CTestClientDlg::OnInitDialog() function, we instantiate


m_spIEventFiringObject:

/* ***** Create an instance of an object


which implements IEventFiringObject. ***** */
m_spIEventFiringObject.CreateInstance(__uuidof(EventFiringObject));

We also create a button in our simple dialog box labeled "Call Test Function". In the click
handler for this button, we invoke the TestFunction() of our
m_spIEventFiringObject:

/* ***** Call the IEventFiringObject.TestFunction(). ***** */


/* ***** This will cause the object which implements ***** */
/* ***** IEventFiringObject to fire Event1. ***** */
m_spIEventFiringObject -> TestFunction(456);

Thus far, we have dealt with mostly typical COM client code. The fun begins when we
invoke TestFunction(). We know that TestFunction() will cause the
m_spIEventFiringObject COM object to fire the Event1 event. This is where the real
action starts.
The TEventHandler Class

General Design Goals And Example Use Case

The TEventHandler class is supplied in TEventHandler.h. It works according to the


following design:

• It serves the role of a Sink for a client.


• It generically handles one event interface which must be dispinterface-based
(i.e., derived from IDispatch).
• After receiving an event fired from the COM object, TEventHandler will call a
predefined method of its client. This is how TEventHandler clients get notified
of events fired from the COM object.

The predefined method of the client must be defined using the following signature:

typedef HRESULT (event_handler_class::*parent_on_invoke)


(
TEventHandler<event_handler_class, device_interface,
device_event_interface>* pthis,
DISPID dispidMember,
REFIID riid,
LCID lcid,
WORD wFlags,
DISPPARAMS* pdispparams,
VARIANT* pvarResult,
EXCEPINFO* pexcepinfo,
UINT* puArgErr
);

Notice that the predefined method's parameters match those of IDispatch::Invoke()


except for the addition of a parameter (first in the list) which is a pointer to the
TEventHandler instance itself. This parameter is supplied as it may be useful to client
code.

In the context of our example code, our usage of TEventHandler can be represented by
the following diagram:
What TEventHandler Is Not Designed To Do

Why do we make an apparent rehash of the IDispatch::Invoke() method? What value-


add could TEventHandler have if your callback function still has to handle all the
parameters of the IDispatch::Invoke() call?

The answer is that the TEventHandler class is not primarily designed to simplify the
handling of the parameters of the event methods (although this might be possible, see my
comments on this later in this section).

It is designed to be a sink object. It is meant to readily make available (for a client) a sink
which can be hooked up to receive the dispinterface-based event of a COM object. And
then have the sink automatically call the mirror Invoke() method of the client.

Note that using C++ templates alone, it will not be possible to anticipate in advance the
return values and parameter lists of event methods. This would require the work of
Wizards which can read all these information from the type libraries associated with the
connectable COM objects and then generating function codes which match the signatures
of event methods.

TEventHandler is not a wizard. It is a C++ template (which makes it sort of a bona-fide


code generator, but it can't do everything, e.g., read a type library and generate code
according to information found therein...). It is also meant to be a Sink for one event
interface which must be derived from IDispatch, and hence TEventHandler implements
IDispatch (see the next section "Why Is TEventHandler dispinterface-based?" for
more information on the reasons behind this).

Why Is TEventHandler dispinterface-based?


In order for TEventHandler to be a Sink for ISomeEventInterface, it must be derived
from ISomeEventInterface and it must implement the methods of that interface. I have
chosen to make TEventHandler derive from IDispatch, thereby making it a Sink for an
event interface that also derives from IDispatch.

Interfaces that derive from IDispatch are also known as dispinterfaces (for dispatch
interfaces). Interfaces, in general (not just event interfaces), that are dispinterfaces are
very common. They are common because of the long-time need for COM to support
Visual Basic.

The IDispatch interface is the basis behind Visual Basic's achievement of something
known as late binding which means the act of programmatically constructing a function
call (together with the inclusion of parameters and the receipt of return values) at
runtime. It is perhaps the most generic and flexible of all COM interfaces.

Take note that I'm using the term late binding in a generic way (I'm not referring to the
C++ concept of vtables which is another implementation of late binding).

IDispatch can be used to define a virtual interface the methods of which are called via
the IDispatch::Invoke() method. This system of runtime function invocation is known
as automation (formerly OLE-automation).

To distinguish one IDispatch virtual interface from another, something known as a


Dispatch Interface ID is used (DIID). This is programmatically no different from a
normal Interface ID (IID). Take a look at the following code fragment:

pIConnectionPointContainerTemp -> FindConnectionPoint


(
__uuidof(ISomeEventInterface),
&m_pIConnectionPoint
);

Here, we are asking a connection point container to return to us a connection point that
supports the outgoing interface that is identified by the DIID which is equivalent to the
result of __uuidof(ISomeEventInterface).

Visual Basic applications can only handle ActiveX object events through sinks which are
dispinterface-based. This is natural both because of the fact that Visual Basic cannot
handle non-dispinterface-based events, and also because of the need to handle events
fired from any and all ActiveX objects.

The central point behind this is that while event interfaces need not be dispinterface-
based (their methods can be of any signature, the only mandate is that the event interface
must also derive from IUnknown), Visual Basic is not able to internally anticipate the
design of these custom event interfaces and to generate Sinks for them.
Furthermore, the types of method return values and parameters must be confined to those
that Visual Basic is able to understand and internally process.

Hence, the only way to standardize the handling of event interfaces is to require that they
be derived from IDispatch, and that the return and parameter types be from a wide but
limited ranged set. This set of types is known as the automation-compatible-types.

Like Visual Basic, it is not possible for us to design the TEventHandler template class to
be a Sink for custom event interfaces. Remember that we must actually implement the
methods of the event interface that TEventHandler is to be a Sink for. Hence, I decided
that TEventHandler should handle only dispinterface-based events. And there are plenty
of COM object sources which support them.

Code Details

The TEventHandler template class is defined (in summary) as follows:

template <class event_handler_class, typename device_interface,


typename device_event_interface>
class TEventHandler : IDispatch
{
...
...
...
}

Note that TEventHandler is derived from IDispatch. However, note, it need not
implement all of the methods of IDispatch. Only the basic AddRef(), Release(),
QueryInterface() and the Invoke() methods are required at minimum.

TEventHandler takes in three template parameters which are:

1. event_handler_class
2. device_interface
3. device_event_interface

The "event_handler_class" parameter indicates to TEventHandler the name of the


class which will contain the predefined method to be invoked when the COM object fires
an event of the outgoing interface.

In out example use case, this parameter will be the CTestClientDlg class. The
CTestClientDlg class will contain the invocation function:

HRESULT CTestClientDlg::OnEventFiringObjectInvoke
(
IEventFiringObjectEventHandler* pEventHandler,
DISPID dispidMember,
REFIID riid,
LCID lcid,
WORD wFlags,
DISPPARAMS* pdispparams,
VARIANT* pvarResult,
EXCEPINFO* pexcepinfo,
UINT* puArgErr
)

It is in this function that CTestClientDlg will handle events fired from the
EventFiringObject COM object.

The "device_interface" parameter refers to the interface type of the COM object
(whose event we are trying to receive). In our example use case, this will be
IEventFiringObject.

The last template parameter is "device_event_interface" and this indicates the


interface type of the outgoing interface supported by the COM object. This will be the
interface that must be implemented by the Sink object. In our example use case, this will
be _IEventFiringObjectEvents. And note that because _IEventFiringObjectEvents
is essentially derived from IDispatch, our Sink object (which is TEventHandler) is also
derived from IDispatch.

All the above template parameters are used in order that the VC++ compiler be able to
generate a C++ class which has been tailored to contain methods, properties and
parameter types which match those of CTestClientDlg, IEventFiringObject and
_IEventFiringObjectEvents.

Hence, a customized class will eventually be created for further use in the code.

Usage

Usage of the TEventHandler class is simple and straightforward. Let us walk through
some example codes from TestClient:

1. We need to define a specific class type based on TEventHandler:


2. // ***** Declare an event handling class
3. using the TEventHandler template. *****
4. typedef TEventHandler<CTestClientDlg, IEventFiringObject,
_IEventFiringObjectEvents> IEventFiringObjectEventHandler;

Note that we are not instantiating an object here! We are merely defining a C++
class via the use of TEventHandler. We will call this new C++ class
IEventFiringObjectEventHandler. IEventFiringObjectEventHandler is the
customized class that we mentioned earlier.

This IEventFiringObjectEventHandler is the Sink for the outgoing interface


_IEventFiringObjectEvents supported by the EventFiringObject COM
object.
5. In our CTestClientDlg class, we will now define a pointer to an instance of
the IEventFiringObjectEventHandler class:
6. /* Declare a pointer to a TEventHandler class which is specially
tailored */
7. /* to receiving events from the _IEventFiringObjectEvents events
of an */
8. /* IEventFiringObject object. */
IEventFiringObjectEventHandler*
m_pIEventFiringObjectEventHandler;

9. We need to define the invoke method which will be called by the


IEventFiringObjectEventHandler class object when the
EventFiringObject fires an event based on the
_IEventFiringObjectEvents outgoing interface.

We have seen this method before and it is defined as:

HRESULT CTestClientDlg::OnEventFiringObjectInvoke
(
IEventFiringObjectEventHandler* pEventHandler,
DISPID dispidMember,
REFIID riid,
LCID lcid,
WORD wFlags,
DISPPARAMS* pdispparams,
VARIANT* pvarResult,
EXCEPINFO* pexcepinfo,
UINT* puArgErr
);

Please note that the reader must be familiar with the IDispatch::Invoke()
method in order to be able to interpret the values contained in the various
parameters of this method. See the example code in
OnEventFiringObjectInvoke() itself and refer to MSDN documentation for
further details.

10. In the CTestClientDlg::OnInitDialog() function, we instantiate


m_pIEventFiringObjectEventHandler:
11. /* Instantiate an IEventFiringObjectEventHandler object. */
12. m_pIEventFiringObjectEventHandler = new
IEventFiringObjectEventHandler
13. (*this,
14. m_spIEventFiringObject,
15. &CTestClientDlg::OnEventFiringObjectInvoke
);

Here, we instantiate with constructor parameters according to those defined for


TEventHandler.

The first parameter is a reference to the CTestClientDlg object. The second is


the smart pointer object m_spIEventFiringObject. This will be cast to an
IEventFiringObject interface pointer and so the inner pointer contained inside
m_spIEventFiringObject will be supplied to the constructor.

The last parameter is a pointer to a method of the CTEstClientDlg class which


conforms to the parent_on_invoke method signature.

You will note in the TEventHandler constructor that once an instance of


IEventFiringObjectEventHandler is created, the SetupConnectionPoint()
method is called. This method will duly perform all the required connection point
protocols to establish event connectivity with the EventFiringObject COM
object.

16. When we no longer want to maintain event connection with the


EventFiringObject COM object, we shutdown the connection point as in the
CTestClientDlg::OnDestroy() method:
17.void CTestClientDlg::OnDestroy()
18.{
19. CDialog::OnDestroy();
20.
21. /* When the program is terminating, make sure that we instruct
our */
22. /* Event Handler to disconnect from the connection point of the
*/
23. /* object which implemented the IEventFiringObject interface. */
24. /* We also needs to Release() it (instead of deleting it). */
25. if (m_pIEventFiringObjectEventHandler)
26. {
27. m_pIEventFiringObjectEventHandler -> ShutdownConnectionPoint();
28. m_pIEventFiringObjectEventHandler -> Release();
29. m_pIEventFiringObjectEventHandler = NULL;
30. }
}

31. To invoke the event handling code, I have included a button in the
CTestClientDlg dialog box, and the handler to this button will call the
EventFiringObject's TestFunction() method which will internally fire
Event1.

This will lead to CTestClientDlg::OnEventFiringObjectInvoke() being


called by the IEventFiringObjectEventHandler class object:

HRESULT CTestClientDlg::OnEventFiringObjectInvoke
(
IEventFiringObjectEventHandler* pEventHandler,
DISPID dispidMember,
REFIID riid,
LCID lcid,
WORD wFlags,
DISPPARAMS* pdispparams,
VARIANT* pvarResult,
EXCEPINFO* pexcepinfo,
UINT* puArgErr
)
{
if (dispidMember == 0x01) // Event1 event.
{
// 1st param : [in] long lValue.
VARIANT varlValue;
long lValue = 0;
VariantInit(&varlValue);
VariantClear(&varlValue);
varlValue = (pdispparams -> rgvarg)[0];
lValue = V_I4(&varlValue);
TCHAR szMessage[256];
sprintf (szMessage, "Event 1 is fired with value : %d.",
lValue);
::MessageBox (NULL, szMessage, "Event", MB_OK);
}
return S_OK;
}

In Conclusion

I certainly do hope you will find the TEventHandler class useful. C++ templates are
really superb in generating generic code. There is already talk of C# providing template
features. I really can't wait to get my hands on this.

If you have any comments on TEventHandler, on how to improve it further, please drop
me an email anytime.


Introduction

This article is about COM and COM components. It explains some of the details behind
COM technology, through implementation of a simple COM component. The article
begins with some background information about COM and the reader is guided to
implement and improve a simple COM component in an example. The example starts
with implementation of a COM component and a COM client in a common file. The
improvement of the example is about the separation of the COM component form its
client. The client and the component are first separated to two different files, then the
component will be put on a DLL, which can be loaded into the address space of the client
and the final improvement is to register the component in Windows registry, such that the
client is no longer bound to the component, and is able to create it through the class
factory. In the following illustration, the relation between the component and its client is
shown with a chain, which will be broken totally when the component is created through
the class factory. In the last part, COM containment is explained by reusing the
implemented component in a new COM component. This article is only about part one
and the other parts are explained in two other articles. The demo application's code
(Client + 3 component Servers) is very similar to the example explained in the article and
only a window is used to visualize the component itself. The following image illustrates
part one and part two:
Assumption

You are familiar with basic C++ and Windows programming.

Part one-Background

A binary standard for making software components

In object oriented programming, software objects created by different vendors cannot


interact with each other if a common standard framework has not been used, and COM or
Component Object Model is a binary standard for making software component language
independent, and it's about breaking the applications into separate pieces. COM's
implementation is hidden for users (COM clients) and it means that COM components
are shipped in a binary form and are already compiled, linked and ready to use and they
are just a bunch of 1s and 0s, i.e. they are just machine code, which can be executed
whenever a client interact with them. Here are some advantages of COM:

1. Language independency (it is possible to make COM Components with


different languages).
2. Reusing application architectures.
3. Easy to extend functionalities of an application without rebuilding.
Functionality of COM components

An object or component in COM is any structure that exposes its functionality through
the interface mechanism. In C++ applications, interfaces are defined as abstract base
classes. An interface is a C++ class that contains nothing but pure virtual member
functions. This means that the interface carries no implementation and only prescribes the
function signatures for some other class to implement. The pure abstract base classes
define the specific memory structure that COM requires for an interface, and when we
define a pure abstract base class, we are actually defining the layout for a block of
memory. Memory is not allocated for the structure until the abstract base class is
implemented in a derived class. So in the following piece of code, IComponent is both an
interface (because its memory layout follows the COM specification) and a pure abstract
base class.

//----------------------------------------------------------------//
// Definition of component's functionallity through an interface //
//----------------------------------------------------------------//
// The keyword "interface" is just an alias for "struct"
interface IComponent
{
// Methods for the implementation of the component's
functionalities:
virtual void __stdcall Function1()=0;
virtual void __stdcall Function2()=0;
virtual void __stdcall Function3()=0;
};

As shown in the following image, there are two parts to the block of memory defined by
a pure abstract base class which makes the interface of a COM component in C++ (the
top figure illustrates Component2 from the demo application):

1. The virtual function table or vtbl, which is an array of pointers that point to
the implementation of the virtual functions.
2. A pointer to the vtbl known as the vtbl-Pointer.
Conclusion: In COM, functionality of components is obtained through their interfaces,
which are addresses or entries to components' methods.

A common need for COM objects and interfaces (Basic operations)

In a component which supports variety of functionalities and interfaces, there is a need to


be able to access these interfaces easily. Another requirement is that the clients should be
able to manage the existence of components and free them once they have finished using
them. A COM component is a component, which supports these operations through a so-
called IUnknown interface. By inheriting and implementing of this interface, COM
objects allow clients to have access to two basic operations:

1. Navigating between multiple interfaces on an object through the


QueryInterface method.
2. Controlling the object’s lifetime through a reference counting mechanism
handled with methods called AddRef and Release.

These three methods make up the IUnknown interface from which all other interfaces
inherit. All COM interfaces must inherit from IUnknown. This means that the first three
entries in the vtbl are the same for all COM interfaces. They are the addresses for the
implementation of the three methods in IUnknown interface. So in the following code, the
IComponent interface becomes a COM interface by inheriting from IUnknown interface:

//------------------------------------------------------------//
// Being a COM interface by inheriting from IUnknown interface//
//------------------------------------------------------------//
interface IComponent :public IUnknown;
{
virtual void __stdcall Function1()=0;
virtual void __stdcall Function2()=0;
virtual void __stdcall Function3()=0;
};

Because every COM component inherits from IUnknown, a client which has an IUnknown
interface pointer does not know what kind of interface pointer it has and it can just query
for other interfaces using the QueryInterface method. That's why this interface is called
Unknown interface or IUnknown. The following image shows the IComponent interface
being as a COM interface after inheriting from IUnknown interface.

Conclusion: The IUnknown interface is the fundamental interface in COM, which


contains basic operations for all interfaces and objects.

The Class of COM components (Implementation)

When the component's functionality is defined through its methods (like Function1-
Function3) by a COM interface, the component's class can be defined by deriving a
class from this newly created COM interface. In the following example, this class is
called CComponent:

//----------------------------------//
//Definition of the Component's class
//----------------------------------//
class CComponent:public IComponent
{
public:
//---------------------------------------------------------------------
----//
//IUnknown methods,which are the basic oparation
//required for all COM components:
//---------------------------------------------------------------------
----//
virtual HRESULT __stdcall QueryInterface(const IID& iid,void** ppv);
virtual ULONG __stdcall AddRef();
virtual ULONG __stdcall Release();

//-------------------------------------------------------//
//The new methods, which the interface supports:
//-------------------------------------------------------//
virtual void __stdcall Function1();
virtual void __stdcall Function2();
virtual void __stdcall Function3();

private:
// Component's member data
long m_cRef;// The reference count variable
};

The following image illustrates the memory layout for the CComponent class:

As the image shows, an Interface pointer like the "this" pointer of the CComponent
class points to the vtbl pointer. In COM, a component is accessed just through methods
and never directly through variables, and pure abstract base classes have only pure virtual
functions and they do not have instance data. In order to call one of the component's
methods (like Function1), the IUnknown pointer can be used to query or ask for a pointer
that points to the IComponent interface, and by using that pointer, it will be possible to
call the desired method:

//---------------------------------------------//
//Getting a pointer to the IComponent interface:
//---------------------------------------------//
IComponent* pIComponent=NULL;
//The "IID_IComponent" is the interface ID for the IComponent interface
pIUnknown->QueryInterface(IID_IComponent,(void**) &pIComponent;
// Calling the component's method:
pIComponent->Function1();

As explained before, we can navigate between interfaces on a COM object through the
QueryInterface method. Interfaces encapsulate the implementation details of
components. Whenever we want to access a component, we want to obtain some desired
functionality and so it is obvious that we are aware of a component's functionality, before
we use it. If we do not know what functionality a component has, we cannot use it. It
means that a component's client should know what kind of functionalities or interfaces
the component supports. Every COM interface has an interface identifier, which can be
used by clients in order to query a particular interface. The interface identifiers are 128-
bit values, and in the previous piece of code, the IComponent interface is identified by
IID_IComponent, which is the interface identifier of the IComponent interface. So,
whenever a client wants to use a functionality of a component, it should know which
interface implements that functionality, and it's required that it delivers an interface
identifier when it queries that interface using the QueryInterface method. A component
is comparable with a window and its interfaces with the window's menu. A window's
menu operates like an interface; it is an entry to a variety of functionalities which can be
obtained by selecting its items with the mouse pointer. As a matter of fact, the interfaces
for the three components in the demo application are like menus, and the components
methods are accessible through the menu items. The following image shows one of these
components.

A window object has been used as a member data of the component in order to visualize
the component, and as the image shows, the component is a window with a menu which
operates as its interface, and the component's methods are accessible through menu items
using the mouse pointer like an interface pointer.

Conclusion: A COM class is a particular implementation of certain interfaces.

Instantiating of COM Objects

As explained before, the only way of getting access to a component's functionality is just
through its interfaces, so, once we get a pointer to a component's IUnknown interface, we
can actually access all the other interfaces (using the QueryInterface method)
supported by the component, and because all COM interfaces inherit from IUnknow
interface, every interface pointer is also an IUnknown interface pointer. A COM object is
created by using the new operator, and an interface pointer of the created object can be
obtained by converting the pointer which points to the object to an interface pointer or an
IUnknown pointer as shown below:

//When new operator is used to allocate a single object,


//it gives a pointer to that object

IUnknown* pIUnknown = static_cast<IComponent*>(new CComponent);

Interface Identifier

An interface identifier is a structure of type GUID (Globally Unique Identifier). GUIDs


exist in two formats: string and numeric. In Windows registry, the string format of the
GUIDs appears in various locations, however, the numeric representation of GUIDs are
necessary when they are used within client applications or within the actual COM object
implementation. The _GUID structure is defined in the basetyps.h header file as shown in
the following:

//-------------------------------------------//
// GUID definition:
//------------------------------------------//
typedef struct _GUID
{
unsigned long Data1; // 32 bits
unsigned short Data2; // 16 bits
unsigned short Data3; // 16 bits
unsigned char Data4[8];// 8*8 = 64 bits
//-----------------------------------------//
// Total bits = 128 bits
} GUID;

Your development environment will include a tool called UUIDGEN.EXE or


GUIDGEN.EXE, which will give you one or more GUIDs that you can incorporate into
source code. I have used GUIDGEN.EXE to create GUIDs in all the examples in this
article.

An example from scratch

By now, you are able to make a simple COM object and let a client to use its
functionality. Using the following steps, you may make the example, which is shown in
the illustration at the beginning of the article.

You may download the code in each part and copy-paste some parts in order to make
your application quickly.

Step 1:
Using the AppWizard, create a simple Win32 Console application and choose an empty
project. This application will act as the COM client and will also hold the component
itself.

Step 2:

Create a source file with the extension of "cpp" and give it a name. I used the name
ClientAndComponent.cpp.

Step 3:

Let's decide a primitive functionality for the component, for example, the component
should be able to print the phrase "COM from scratch" on the screen. As mentioned
before, a COM component exposes its functionality through the interface mechanism, so
the need is to make an interface, and this interface should be derived from the IUnknown
interface if the component is going to be a COM component. So, let's call the interface
IComponent, derive it from the IUnknown interface and define the desired functionality in
a method called Print:

//-------------------------//
//Interface definition:
//-------------------------//
interface IComponent:IUnknown
{
//The new method for assigning component's functionality
virtual void __stdcall Print(const char* msg)=0;
};

Step 4:

In order to identify the IComponent interface, make an interface identifier for it by using
the tools UUIDGEN.EXE or GUIDGEN.EXE from your development environment:

//-----------------------------------------------//
//Interface identifier, which is a 128-bit value.//
//-----------------------------------------------//
// {853B4626-393A-44df-B13E-64CABE535DBF} string format
static const IID IID_IComponent =
{ 0x853b4626, 0x393a, 0x44df, //Data1,Data2,Data3
{ 0xb1, 0x3e, 0x64, 0xca, 0xbe, 0x53, 0x5d, 0xbf } }; //Data4

Step 5:

The component's class can be defined by deriving it from the defined interface
(IComponent):

//----------------------------------//
//Definition of the Component's class
//----------------------------------//
class CComponent:public IComponent
{
public:

//---------------------------------------------------------------------
------//
//IUnknown methods,which are the basic oparation
//required for all COM components:
//---------------------------------------------------------------------
------//
virtual HRESULT __stdcall QueryInterface(const IID& iid,void** ppv);
virtual ULONG __stdcall AddRef(){return 0;}
virtual ULONG __stdcall Release(){return 0;}

//-------------------------------------------------------//
//The new method, which the interface should support.
//-------------------------------------------------------//
virtual void __stdcall Print(const char* msg);
};

Step 6:

Now it's necessary to implement the methods:


Step 7:

Create a function in order to instantiate an object from the component class:

Step 8:

The final step is just to make the client, which uses the component's functionality through
the component's interface:

//----------------//
// Client
//----------------//
void main()
{
IUnknown* pIUnknown=CreateInstance();
IComponent* pIComponent=NULL;
pIUnknown->QueryInterface(IID_IComponent,(void**)&pIComponent);
pIComponent->Print("COM from scratch.");
}

A better implementation of the IUnknown interface's methods

• The AddRef method:

In order to control the life time of the object instantiated from the component's
class, it's necessary to have a variable which could be used as a reference count.
This variable should be incremented when the component is in use and
decremented when the client no longer uses the component. The AddRef() and
Release() methods can be used to increment and decrement this variable. So by
adding a private member variable to the component's class and calling the
AddRef() and Release() methods, it's possible to control the life time of the
component and free the memory which has been allocated for the component,
whenever this variable reaches the zero value. So in order to control the life time
of the component, redefine the component's class:

//
// Component
//
class CComponent : public IComponent
{
//IUnknown interface's methods:
virtual HRESULT __stdcall QueryInterface(const IID& iid,
void** ppv) ;
// method for incrementing the reference count variable
"m_cRef"
virtual ULONG __stdcall AddRef();
// method for decrementing the reference count variable
"m_cRef"
virtual ULONG __stdcall Release();

//IComponent interface's method:


virtual void __stdcall Print(const char* msg);

public:
CComponent() ;// Constructor
~CComponent();// Destructor

private:

long m_cRef ;// The reference count variable

};

Initialize the reference count variable to zero in the constructor:

/////////////////////////////////
CComponent::CComponent()
{
Print("Constructing the component...") ;
m_cRef=0;
}

////////////////////////////////////////
CComponent::~CComponent()
{
Print("Destructing the component...") ;
}

Implement the AddRef() method:

//////////////////////////////////////
ULONG __stdcall CComponent::AddRef()
{
Print("Incrementing the reference count variable...");
return InterlockedIncrement(&m_cRef);
}

• The Release() method:

As mentioned, the Release() method will be used to decrement the reference


count, and in this method, if the reference count reaches zero, the component's
object can be destroyed by deleting the this pointer:
//////////////////////////////////////
ULONG __stdcall CComponent::Release()
{
Print("Decrementing the reference count variable...");
if(InterlockedDecrement(&m_cRef) == 0)
{
delete this ;
return 0 ;
}
return m_cRef ;
}

• QueryInterface method:

One of the limits of the QueryInterface method in the program is that it doesn't
inform their clients if an unsupported interface is queried. The HRESULT is the key
type involved in COM error reporting and is a simple 32-bit value. COM
components use HRESULT to report conditions to their clients. Like other interfaces
in COM, the QueryInterface method returns a HRESULT. The most significant
bit (severity field) of a HRESULT reports whether the function call succeeded or
failed. The last 16 bits contain the code that the function is returning. Two bits are
reserved for future use and the remaining 13 bits provide more information about
the type and the origin of the return code. The following image illustrates this.

So using the following implementation, the component will be able to inform its
clients whether a particular interface is not supported by the component.

/////////////////////////////////////////////////////////////////
//////////
HRESULT __stdcall CComponent::QueryInterface(const IID& iid,
void** ppv)
{
if (iid == IID_IUnknown)
{
Print("Returning pointer to IUnknown...") ;
*ppv = static_cast<IComponent*>(this) ;
}
else if (iid == IID_IComponent)
{
Print("Returning pointer to IComponent interface...") ;
*ppv = static_cast<IComponent*>(this) ;
}
else
{
Print("Interface is not supported!.") ;
*ppv = NULL ;
return E_NOINTERFACE ;
}

//The reinterpret_cast operator allows any pointer to be


converted into
//any other pointer type.
reinterpret_cast<IUnknown*>(*ppv)->AddRef() ;
// Incrementing the Reference count variable

return S_OK ;
}

Now the client is able to release the component by calling the component's
Release() method:

//----------------//
// Client
//----------------//
void main()
{
IUnknown* pIUnknown=CreateInstance();
IComponent* pIComponent=NULL;
pIUnknown->QueryInterface(IID_IComponent,(void**)&pIComponent);
pIComponent->Print("COM from scratch.");
pIComponent->Release();// Releasing the Component
}

The following screen shot is made from the example in part one:

Summary
1. There are many advantages in making applications by components.
2. A common standard should be used for making software components such
that they become language independent, and COM is a binary standard for
making software components.
3. Like hardware components, some sort of functionality are assigned to
software components.
4. The functionality of COM components are obtained through the interface
mechanism.
5. In C++ applications, interfaces are defined as abstract base classes.
6. The memory layout generated by the C++ compiler for a pure abstract base
class is the same as the memory layout required by COM for an interface.
7. As pure abstract base classes have only pure virtual functions and they do not
have instance data, COM components are accessed just through their
methods and never directly through member variables.
8. An interface becomes a COM interface by inheriting from the IUnknown
interface, which has three methods:
o QueryInterface, which is used in order to navigate between multiple
interfaces on an object and returns a pointer to a queried interface.
o AddRef, which is used in order to control the object’s lifetime.
o Release, which is used in order to control the object’s lifetime.
9. In COM, every interface pointer is also an IUnknown interface pointer.
10. A COM component is created by using the new operator.
11. COM components use HRESULT type in order to report conditions to their
clients.
12. Every interface is identified with an interface identifier, which is a 128 bits
value with type of GUID.
13. There is an important difference between a C++ class and a COM class. In
C++, a class is a type, but a COM class is simply a definition of the object,
and carries no type, although a C++ programmer might implement it using a
C++ class.

Introduction

In part one, some background information regarding COM technology was explained,
and a simple example showed how a client can use a component's functionality through
its interface. In this part, I'll guide the reader to separate the implementation of the
component from its client such that the client is no longer bound to the component, and is
able to create it through the class factory.

Part two-Breaking the chain

Serving components (Distribution)

Software component servers provide a way so that functionality can be reused more
easily, besides they reduce memory overhead when several applications use the same
functionality at the same time, because although each application gets its own copy of the
data, they can share the code. By putting a component into a DLL, it's possible to make a
kind of component distribution and the DLL becomes the component's server and will
contain the implementations of interfaces supported by the component.
Building of the component's Server (DLL)

In the example, the client and the component were both in the same file, now they should
be separated, the client will be in a .exe file, which loads the component into its address
space in order to use it, and the component will be served by a DLL. The client should
load the DLL into its process and create the component before it can get an interface
pointer. If the client links to the CreateInstance() function in the DLL, all other
functions of the component are accessible through an interface pointer. So, the solution is
just to export the CreateInstance() function from the DLL such that the client can link
explicitly to it on the fly. The DLL will be built from the command prompt using one of
Microsoft’s command-line tools.

The following figure shows the Server files which are going to be used in making the
DLL:

Step 1:

Create a source file (Component.cpp) and put the definition and the implementation of
the component's class into it.

Step 2:

Export the CreateInstance() function by adding the following piece of code at the end
of the file:
Step 3:

The linker should be informed that the CreateInstance function will be exported, and
that can be done by using a module-definition file. A module-definition file is a file with
the "def" extension, which contains information about exports, attributes and other
information for linking an .EXE file (which has exports) or DLL. In the .def file, the
CreateInstance function's export ordinal is chosen to be 1. The following part shows
the contents of this file.

;component.def
; Component module-definition file
; LIBRARY Component.dll
DESCRIPTION 'Components windows dynamik library'
EXPORTS ; Explicit exports can go here
CreateInstance @1 PRIVATE

Step 4:

The interface identifier and the interface definition should be known for both the client
and the component, and they can be put into two separate files, which are shared between
the client and the component. Create another source file (GUID.cpp), which can hold the
interface ID:

//
// GUID.cpp - Interface ID
//
#include "objbase.h"
extern "C"
{

extern const IID IID_IComponent =


{ 0x853b4626, 0x393a, 0x44df, //Data1,Data2,Data3
{ 0xb1, 0x3e, 0x64, 0xca, 0xbe, 0x53, 0x5d, 0xbf } }; //Data4

// The extern is required to allocate memory for C++ constants.


}

Step 5:

Create a header file (interface.h) with the following content:

//
// Interface.h
//
interface IComponent : IUnknown
{
virtual void __stdcall Print(const char* msg) = 0 ;
} ;

// Forward references for GUID


extern "C"
{
extern const IID IID_IComponent ;

Step 6:

Create a "make" file containing the options for making the DLL with the following
content:

#Makefile

#######################################################################
#########
# Compiler options:
# /c compile without linking
# CL cl.exe is a 32-bit tool that controls the Microsoft C
# and C++ compilers and linker.
# The compilers produce Common Object File Format
# (COFF) object (.obj) files.
# The linker produces executable (.exe) files
# or dynamic-link libraries (DLLs).
#
##################################
# Linker options:
#
# /DEF Passes a module-definition (.def) file to the linker
# /DEBUG Creates debugging information
# /DLL Builds a DLL

CPP_FLAGS=/c /MTd /Zi /Od /D_DEBUG


EXE_LINK_FLAGS=/DEBUG
DLL_LINK_FLAGS=/DLL /DEBUG

LIBS=UUID.lib

#############################################
# Targets:
# CodeProject is just a pseudotarget
#
CodeProject : component

component : Component.dll
#########################################
# Shared source files:
#

GUID.obj : GUID.cpp
Cl $(CPP_FLAGS) GUID.cpp

##########################################
# Component source files:
#

Component.obj : Component.cpp Interface.h


Cl $(CPP_FLAGS) Component.cpp

########################################
# Link component:
#

Component.dll : Component.obj GUID.obj Component.def


link $(DLL_LINK_FLAGS) Component.obj GUID.obj $(LIBS)
/DEF:Component.def

Step 7:

Make the DLL from the Command line by using Microsoft Program Maintenance Utility
(NMAKE.EXE). This program is a tool that can build projects based on commands
contained in a description file.

1. Open the Command window (Click Start choose Run menu item and then
write cmd in the dialog box).
2. From the Command line, change to the directory which contains the server
files.
3. From the Command line, enter: nmake /f makefile.

The NMAKE utility will create the DLL in the same folder:
Building the Client

The following figure shows the files which are used in making the Client. The Client will
be built within the Visual C++ development environment.

Step 1:

Using the AppWizard, create a simple Win32 Console application and choose an empty
project.

Step 2:
Create a new source file (Create.cpp) and make a function which takes the DLL's name
as parameter, loads the "DLL" and then calls the exported function CreateInstance().
The function's return value would be the return value of the CreateInstance() function,
which is an IUnknown interface pointer. In order to link explicitly to the "DLL", the
function calls the GetProcAddress function to get the address of the exported function.
The GetProcAddress function takes two parameters. The first parameter is a handle to
the "DLL" module and the second parameter is the "DLL" name. By calling the
LoadLibrary function, it would be possible to obtain the module handle.

// Create.cpp
#include "iostream.h"
#include "unknwn.h"//IUnknown definition file.
#include "Create.h"

typedef IUnknown* (*CREATEFUNCPTR)();


//////////////////////////////////////////
IUnknown* CallCreateInstance(char* dllname)
{
//-----------------------------------------------------------------//
// Load dynamic link library into client's process.
//Loadlibrary maps a DLL module and return a handle
//that can be used in GetProcAddress
//to get the address of a DLL function
//-----------------------------------------------------------------//
HMODULE hm = ::LoadLibrary(dllname);
if (hm ==NULL)
return NULL;
// Get the address of CreateInstance function.
CREATEFUNCPTR Function =
(CREATEFUNCPTR)::GetProcAddress(hm, "CreateInstance");
if (Function == NULL)
return NULL;
return Function();
}

Step 3:

Create a new header file (Create.h) with the following contents:

// Create.h
IUnknown* CallCreateInstance(char* dllname) ;

Step 4:

Create a new header file (interface.h) with the following contents:

//
// Interface.h
//
interface IComponent : IUnknown
{
virtual void __stdcall Print(const char* msg) = 0 ;
} ;

// Forward references for GUID


extern "C"
{
extern const IID IID_IComponent ;

Step 5:

Create another source file (GUID.cpp), which can hold the interface ID:

// GUID.cpp - Interface ID
#include "objbase.h"
extern "C"
{
extern const IID IID_IComponent =
{ 0x853b4626, 0x393a, 0x44df, //Data1,Data2,Data3
{ 0xb1, 0x3e, 0x64, 0xca, 0xbe, 0x53, 0x5d, 0xbf } }; //Data4
// The extern is required to allocate memory for C++ constants.
}

Step 6:

Create a source file (Client.cpp) and implement the main function. Call the function made
in step 2, in order to instantiate the component and use its methods:

//--------//
// Client
//--------//
int main()
{
HRESULT hr ;

// Get the name of the component to use.


char dllname[20];
cout << "Enter the filename of component's server [component.dll]:";
cin >> dllname;
...

// calling the CreateInstance function in the


// DLL in order to create the component.
TRACE("Getting an IUnknown interface pointer...") ;
IUnknown* pIUnknown = CallCreateInstance(dllname) ;

...

IComponent* pIComponent ;
hr = pIUnknown->QueryInterface(IID_IComponent,
(void**)&pIComponent);

if (SUCCEEDED(hr))
{
...
pIComponent->Print("COM from scratch.") ;
//using the component's functionality

pIComponent->Release() ;
...
}
...

return 0 ;
}

Step 7:

Put the component's server (Component.dll) into the same directory of the client. Now the
client is able to load the DLL into its address space and get the address of the
CreateInstance function by using LoadLibrary and GetProcAddress functions. Build
and run the client program.

The following screen shot shows the client application, after loading the DLL and calling
the component's Print method:

Conclusion: Distribution of COM components by servers makes it easy for clients to


reuse components' functionality.

Extending component's functionality without rebuilding Clients

One of the advantages of COM components is that it is easy to extend functionalities of


an application without rebuilding. As long as an interface is not changed, the client
application can still use the component, although its functionality is extended by new
changes to its methods. In order to show this advantage of COM components, it's better
to view the problem of rebuilding of client applications by a simple example. In the
following, a DLL is linked to a client application, you may notice that whenever changes
are made to the DLL (for example, by adding a new member variable to a class in the
DLL and modifying a member function), the client application will fail to run if it is not
rebuild.

Step 1: Make the DLL


1. Using the AppWizard, create a new project with type Win32 Dynamic Link
Library:

2. In step two from the wizard, choose 'A simple DLL project' and click Finish:
3. Create a new header file and define a class with a member variable and a
member function which can be exported from the DLL:
4. //myclass.h
5.
6. class CMyclass
7. {
8. long m_cRef;
9. public:
10. _declspec(dllexport) void Print(const char*msg);
};

11. Create a source file (myclass.cpp) and implement the member function:

12. Build the DLL.


Step 2: Make the Client and load the DLL
1. Make a new empty project of type Win32 Console Application.
2. Create a new source file in order to load and test the DLL (client.cpp).
3. Include the header, which contains the class definition in the DLL:
4. //client.cpp
5.
6. #include"iostream.h"
7. #include"..\DLL\myclass.h"
8.
9. /////////////////////////////////
10.void main()
11.{
12. CMyclass classObj;
13. classObj.Print("COM from scratch.");
}

14. Add the DLL.lib file from the DLL project to the Client project (Project->Add to
Project->Files, and then choose Library Files (.lib) as the file type):

15. Copy the DLL into the same folder of the client's executive file. If you build
and run the client application, "COM from scratch." will be written on the
screen, and the DLL will be loaded without any problem.

Step 3: Viewing the Rebuild problem

1. Return to the implementation of the CMyclass class and add a new member
variable:
2. //myclass.h
3. class CMyclass
4. {
5. long m_cRef;
6. int m_i; // a new member variable
7. public:
8. _declspec(dllexport) void Print(const char* msg);
};

9. Modify the implementation of the Print member function:

10. Rebuild the DLL and copy it into the same folder of the client's executive file.
11. Execute the client application with the new version of the DLL without
rebuilding, and if you run the client application, you will confront with a
problem, and rebuilding of the client application solves this problem.
Rebuilding of client applications is a big problem, because it requires source
codes. Now if you make the same changes to the component's class from the
example in part one and rebuild the DLL and test it with the client application
(without rebuilding it), you will notice that the client runs without any
problem, although a new member variable is added to the component's class
and the implementation of the Print method has been changed. Since
method calls in COM components are indirectly and through their interfaces,
there will not be any problem if the methods are modified.

Conclusion: COM extends functionalities of applications without rebuilding.

Improvement of the example

In the example, although the client and the component have been separated, the client is
closely related to the component's implementation and should know about the DLL's
name, and changing the DLL's name will affect the client. An improvement is that we can
move the component from one DLL to another or to other directories. The solution is to
replace the CallCreateInstance function with a COM Library function called
CoCreateInstance. COM Runtime Library is an integral component of the Windows
operating system, which provides the means for clients to locate and instantiate COM
objects. COM class objects can be identified by CLSIDs (globally unique identifiers),
which are used in order to locate and create an instance of an object. Once the CLSID is
obtained, a client application submits the CLSID to the COM run-time library to load the
COM object and retrieve an interface pointer. Using the CLSID and the registry,
CoCreateInstance locates the specified object, creates an instance of that object, and
returns an interface pointer to that object. In order to use CoCreateInstance to create an
object, the object must be registered with the system.

CoCreateInstance
The COM Library contains this function. The easiest way of creating a component is by
the use of CoCreateInstance function. CoCreateInstance uses a class factory when it
creates a component. It takes a CLSID, creates an instance of the corresponding
component, and returns an interface for this instance of the component.
CoCreateInstance takes 4 in parameters and 1 out parameter (IUnknown*). By passing
an IID to CoCreateInstance, the client doesn't need to call QueryInterface on the
component after creating it.

CoCreateInstance's parameters:

• The first parameter is the CLSID of the object.


• The second parameter is used to aggregate the object as part of another
object.
• The third parameter specifies the execution context of the object.
• The 4th parameter is the IID (Interface ID) of the requested interface.
• The last parameter which is an out parameter is an interface pointer to the
created object.

Registration of components

Objects that can be created with CoCreateInstance must also be registered with the
system. Registration maps a CLSID to the automation component file (.dll or .exe) in
which the object resides. If clients will want to obtain CLSIDs at run-time, there must be
a way to dynamically locate and load CLSIDs for accessible objects. Furthermore, there
has to be some system-wide method for the COM Library to associate a given CLSID
(regardless of how the client obtained it) to the server code that implements that class. In
other words, the COM Library requires some persistent store of CLSID-to-server
mappings that it uses to implement its locator services. The COM implementation on
Microsoft Windows uses the Windows system registry as a store for such information. In
that registry, there is a root key called "CLSID" under which servers are responsible to
create entries that point to their modules. Usually, these entries are created at installation
time by the application's setup code, but can be done at run-time if desired. When a server
is installed under Windows, the installation program will create a sub-key under "CLSID"
for each class the server supports, using the standard string representation of the CLSID
as the key name. So the primary entry for a CLSID is a sub-key under CLSID key, which
is the CLSID spelled in hex digits within braces. We may also want to associate a CLSID
with what is called a programmatic identifier or ProgID, which effectively identifies the
same class. A ProgID is a text string without spaces that can be used instead of the
CLSID string. The standard ProgID format is <Vendor>.<Component>.<Version>, such
as Codeproject.Cmpnt1.1. This format is reasonably unique, and if everyone follows it,
there will generally not be a collision. There is also the "VersionIndependentProgID",
which has the same format without the version number. Both the ProgID and the
VersionIndependentProgID can be registered, with a human-readable name as a value,
below the root key. The VersionIndependentProgID is mapped to the ProgID, which is
mapped to the CLSID. To create registry entries, you can either write code or create a
REG file and simply run it to merge its entries with the registry. The following image
shows the registry entries for the Component1 from the Demo Application.
The Class Factory

The CoCreateInstance function does not create COM components directly. Instead, it
creates a component called class factory, which then creates the desired component. A
class factory is a component that creates other components. A particular class factory
creates components that correspond only to a single, specific CLSID. The client uses
interfaces supported by the class factory for controlling how the class factory creates each
component. The standard interface for creating components is IClassFactory interface.
IClassFactory like other COM interfaces is derived from IUnknown interface and has
two methods:

• CreateInstance, which creates an un-initialized object of a specified CLSID.


• LockServer, which locks the object's server in memory, allowing new objects
to be created more quickly.

In the following, a class factory is defined in order to create the COM component in the
example:

///////////////////////////////////////////////////////////
//
// Class factory
//
class CFactory : public IClassFactory
{
public:
// IUnknown
virtual HRESULT __stdcall QueryInterface(const IID& iid,void** ppv)
;
virtual ULONG __stdcall AddRef() ;
virtual ULONG __stdcall Release() ;

// IClassFactory
virtual HRESULT __stdcall CreateInstance(IUnknown* pUnkOuter,
const IID& iid,
void** ppv) ;
virtual HRESULT __stdcall LockServer(BOOL bLock) ;

// Constructor
CFactory() : m_cRef(1) {}
// Destructor
~CFactory() {}

private:
long m_cRef ;
} ;

//
// Class factory IUnknown implementation
//////////////////////////////////////////////////////////////////////
HRESULT __stdcall CFactory::QueryInterface(const IID& iid,LPVOID* ppv)
{
if ((iid == IID_IUnknown) || (iid == IID_IClassFactory))
*ppv = static_cast<IClassFactory*>(this) ;
else
{
*ppv = NULL ;
return E_NOINTERFACE ;
}
reinterpret_cast<IUnknown*>(*ppv)->AddRef() ;
return S_OK ;
}

///////////////////////////////////
ULONG __stdcall CFactory::AddRef()
{
return ::InterlockedIncrement(&m_cRef) ;
}

////////////////////////////////////
ULONG __stdcall CFactory::Release()
{

if (::InterlockedDecrement(&m_cRef) == 0)
{
delete this ;
return 0 ;
}
return m_cRef ;
}

//
// IClassFactory implementation
///////////////////////////////////////////////////////////////
HRESULT __stdcall CFactory::CreateInstance(IUnknown* pUnkOuter,
const IID& iid,void** ppv)
{

HRESULT hr;
if (pUnkOuter != NULL)
{
return CLASS_E_NOAGGREGATION ;
}
CComponent* pComponent = new CComponent ;
if (pComponent == NULL)
{

return E_OUTOFMEMORY ;
}
// Get the requested interface.
hr = pComponent->QueryInterface(iid,(void**) ppv) ;

if(FAILED(hr))
pComponent->Release() ;

return hr ;
}
//---------------------------------------------------------------------
--//
// LockServer
// Called by the client of a class object to keep a server open in
memory,
// allowing instances to be created more quickly.
//---------------------------------------------------------------------
--//
///////////////////////////////////////////////////
HRESULT __stdcall CFactory::LockServer(BOOL bLock)
{
return S_OK ;
}

Before going to more details, it's better to have an overview about creation of the
component via COM Library:

1. The client calls CoCreateInstance, which is implemented in COM Library.


2. CoCreateInstance is implemented using CoGetClassObject function.
3. CoGetClassObject calls DllGetClassObject, which is implemented in DLL
server and its job is to create the class factory for the component.
4. DllGetClassObject queries the class factory for IClassFactory interface,
which is returned to CoCreateInstance function.
5. CoCreateInstance uses IClassFactory interface to call its CreateInstance
method.
6. The IClassFactory::CreateInstance(...) uses the new operator to create
the component and it queries the component for its interface.
7. After getting the component's interface, CoCreateInstance releases the class
factory and returns an interface pointer to the client.
8. The client uses the interface pointer in order to call the Print method of the
component and uses its functionality.

The following image illustrates these steps:


So, in order to improve the example, we need to:

1. Implement the CFactory methods.


2. Implement the DllGetClassObject in the component server or the DLL
instead of CreateInstance function.
3. Write the necessary code (or use a registration file) in order to register the
component in the Windows registry system.

It's also easier to make the DLL within the Visual C++ development environment. In the
following, these steps will be implemented:

Step 1:

• Using the AppWizard, create a new project (with the name "Component") for
the DLL, and select MFC AppWizard (DLL). Consider that the DLL now will
reside in a directory (C:\CodeProject) different form its client:
• In step one, select "MFC Extension DLL" and click the "Finish" button:
• Open the Component.cpp file and replace its content with the following part:
• // Component.cpp : Defines the initialization routines for the
DLL.
• //

• #include "stdafx.h"
• #include <afxdllx.h>
• #include "interface.h"
• #include <objbase.h>
• #include "iostream.h"
• #ifdef _DEBUG
• #define new DEBUG_NEW
• #undef THIS_FILE
• static char THIS_FILE[] = __FILE__;
• #endif
• //
• // Component.cpp


• /////////////////////////////////////////////
• BOOL APIENTRY DllMain(HINSTANCE InsModule,
• DWORD dwReason,
• void* lpReserved)
• {

• return TRUE;
}

• Copy and paste the definitions of the component's class and the class factory
and their implementations in the source file Component.cpp, and ignore the
export of the CreateInstance function, because the class factory is going to
create the component.

Step 2: Getting the Class Factory - DllGetClassObject

The DllGetClassObject function will be called from within the CoGetClassObject


function, when the class context is a DLL, and as mentioned before, its job is to create the
class factory for the component. Implement this function in the Component.cpp file:

///////////////////////////////////////////////////////////////////////
//
STDAPI DllGetClassObject(const CLSID& clsid, const IID& iid, void** ppv)

if (clsid != CLSID_Component)
return CLASS_E_CLASSNOTAVAILABLE;
// Create class factory.
CFactory* pFactory = new CFactory ;
if (pFactory == NULL)
return E_OUTOFMEMORY;
// Get requested interface.
HRESULT hr = pFactory->QueryInterface(iid, ppv);
pFactory->Release();
return hr;

Compile and build the DLL (Component.dll).

Step 3: Registration

Using the GUIDGEN.EXE, create a CLSID for the Component's class:

{49BF12F1-5041-48da-9B44-AA2FAA63AEFB}
static const GUID CLSID_Component =
{ 0x49bf12f1, 0x5041, 0x48da,
{ 0x9b, 0x44, 0xaa, 0x2f, 0xaa, 0x63, 0xae, 0xfb } };

Create a file with ".reg" extension (component.reg) in order to create registry entries for
the component (using the CLSID):

REGEDIT
HKEY_CLASSES_ROOT\Codeproject.Component.1 =
Codeproject Component Version 1.0
HKEY_CLASSES_ROOT\Codeproject.Component.1\CLSID =
{49BF12F1-5041-48da-9B44-AA2FAA63AEFB}
HKEY_CLASSES_ROOT\Codeproject.Component = Codeproject Component
HKEY_CLASSES_ROOT\Codeproject.Component\CurVer =
Codeproject.Component.1
HKEY_CLASSES_ROOT\CLSID\{49BF12F1-5041-48da-9B44-AA2FAA63AEFB} =
Codeproject Component 1.0
HKEY_CLASSES_ROOT\CLSID\{49BF12F1-5041-48da-9B44-
AA2FAA63AEFB}\InprocServer32 =
c:\codeproject\component.dll
HKEY_CLASSES_ROOT\CLSID\{49BF12F1-5041-48da-9B44-AA2FAA63AEFB}\ProgID
=
Codeproject.Component.1
HKEY_CLASSES_ROOT\CLSID\{49BF12F1-5041-48da-9B44-AA2FAA63AEFB}\
VersionIndependentProgID = Codeproject.Component

Activate the registry file by clicking on that. After running the registry file, the entries
will be stored in Windows registry system. The following image shows these entries:
That's it; the chain now is totally broken to pieces. Check it out with the client.

The Client

Now, although the component's server (component.dll) resides in the directory


C:\codeproject, the client can easily load it and use its functionality through the class
factory and COM Library, and this is the way COM components usually are created and
used by their clients. The following shows how the client uses the component through the
COM Library:

//-----------//
// Client
//-----------//
void main()

{
HRESULT hr;
IUnknown* pIUnknown;
IComponent* pIComponent;
IClassFactory* pIClassFactory;

::CoInitialize(NULL);
/*
//Once the CoCreateInstance is called, the component
//will be created and the client can not
//control it, that's why CoCreateInstance is inflexible
//and the solution is to call CoGetClassObject function
hr = ::CoCreateInstance(CLSID_Component,NULL,
CLSCTX_INPROC_SERVER,IID_IUnknown,(void**)&pIUnknown) ;
if (SUCCEEDED(hr))
{
hr=pIUnknown-
>QueryInterface(IID_IComponent,(void**)&pIComponent);
if(SUCCEEDED(hr))
pIComponent->Print("COM from scratch.");
}

*/
//-------------------------------//
// improvement of the client code
//------------------------------//
// By calling the CoGetClassObject function, the client can control
// creation of the component
hr=CoGetClassObject(CLSID_Component,CLSCTX_INPROC_SERVER,
NULL,IID_IClassFactory,(void**)&pIClassFactory);
if (SUCCEEDED(hr))
{
hr=pIClassFactory->CreateInstance(NULL,
IID_IComponent,(void**)&pIComponent);
if(SUCCEEDED(hr))
pIComponent->Print("COM from scratch.");
}

::CoUninitialize ();
}

Part three is explained in the next article.

Introduction

In part one, some background information regarding to the COM technology was
explained, and a COM component was made by a simple example. In part two, the code
in the example was optimized, such that the component was no longer bound to its client,
and it could be created via COM Library. This part is about the containment mechanism,
and I'll guide the reader to reuse the component made in part two in another component.

Part three - COM Containment

COM provides two mechanisms for code reuse. These mechanisms are called
containment or delegation and aggregation. In the containment mechanism, one object
(the “outer” object) becomes the client of another, internally using the second object (the
“inner” object) as a provider of services that the outer object finds useful in its own
implementation. COM containment is similar to C++ containment; however, it is at the
interface level. The outer component contains pointers to interfaces on the inner
component. The outer component implements its own interfaces using the interfaces of
the inner component and it can also re-implement the inner component's interfaces by
forwarding calls to the inner component and adding code before and after the code for the
inner component. Containment reuses the implementation of the interfaces belonging to
the inner components. One of the components (CComponent3) from the demo application
(illustrated in the above figure), uses this mechanism. Until now, a component has been
created and its interface supports the Print method. Now, in order to use the containment
mechanism, we can use this interface in a new component. In the following, a new
component will be created, which reuses the previous component’s interface in order to
build its own interface. This new interface will support two new mathematical functions
and will be called IMath interface.

Step 1: Definition of the Component and implementation of its Server

Using the same method in part two, create en empty project in order to make the
component's server(Component2.dll). The steps for creation of the DLL are explained in
details in part two.

• Definition of the new interface:

As you may know, every COM interface should be derived directly or indirectly
from the IUnknown interface, so the new interface may be derived form the
interface which was implemented in the pervious parts, and that interface was
derived directly from the IUnknown interface:

interface IMath: IComponent;


{
// desired functionality for the new component

virtual int __stdcall Sum(int a,int b)=0;


virtual int __stdcall Subtract(int a,int b)=0;

};

• Definition of the component's class:

As you may know, the component's class can be defined by deriving a class from
the newly created COM interface, and because the containment mechanism will
be used, there is a need for a method to create the inner component (which can be
called from the class factory), and a pointer to the inner interface:

class CComponent2:public IMath


{
private:
long m_cRef;// Referance count
IComponent* m_pInnerInterface;
// pointer to the interface of the inner component

public:
//IUnknown
virtual HRESULT __stdcall QueryInterface(const IID & iid,void**
ppv);
virtual ULONG __stdcall AddRef();
virtual ULONG __stdcall Release();
//IComponent (interface of the inner component)
virtual void __stdcall Print (const char* msg);
//IMath
virtual int __stdcall Sum(int a,int b){return(a+b);}
virtual int __stdcall Subtract(int a,int b){return(a-b);}
// A method to create the inner component
HRESULT __stdcall CreateInnerComponenet();
//Constructor
CComponent2();
//Destructor
~CComponent2();

};

• Implementation of CComponent2::CreateInnerComponent:

• Implementation of CFactory::CreateInstance():
• /////////////////////////////////////////////////////////////////
///////
• HRESULT __stdcall CFactory::CreateInstance(IUnknown* pUnkOuter,
• const IID& iid,void**
ppv)

• {
• // Cannot aggregate
• if (pUnkOuter != NULL)
• return CLASS_E_NOAGGREGATION ;

• // Create component2(the outer component).
• CComponent2* pComponent2 = new CComponent2 ;

• if (pComponent2 == NULL)
• return E_OUTOFMEMORY ;

• // Create the inner component.
• HRESULT hr = pComponent2->CreateInnerComponent() ;
• if (FAILED(hr))
• {
• pComponent2->Release() ;
• return hr ;
• }

• // Get the requested interface.
• hr = pComponent2->QueryInterface(iid,(void**) ppv) ;
• //if Query faild the component will delete itself

• if (FAILED(hr))
• pComponent2->Release() ;

• return hr ;
}

• Forwarding calls to the inner component:


• ///////////////////////////////////
• void __stdcall CComponent2::Print(const char* msg)
• {
• m_pInnerInterface->Print(msg);
}

• Providing GUID for the component's class and interface identifier for IMath
interface, using GUIDGEN.EXE:
• //CLSID for component2's class
• // {0CDD7D97-DD93-450a-BBCF-6B22894FAFF5}
• extern "C" const GUID CLSID_Component2 =
• { 0xcdd7d97, 0xdd93, 0x450a,
• { 0xbb, 0xcf, 0x6b, 0x22, 0x89, 0x4f, 0xaf, 0xf5 } };

• //IID for component2's IMath interface
• // {C8ACE8CC-0480-4f1a-8A62-89717E1D5705}
• extern "C" const IID IID_IMath =
• { 0xc8ace8cc, 0x480, 0x4f1a,
{ 0x8a, 0x62, 0x89, 0x71, 0x7e, 0x1d, 0x57, 0x5 } };
Step 2: the Client

The following figure shows the output window of the client program:
One of the uses of containment is to extend an interface by adding code to an existing
interface. As an example, the inner Component2 from demo application has a window
with yellow color, although Component2's background color is initially white. The other
change is that the butterfly starts flying after creation of the inner component, although
initially it does not fly. These changes are made through the containment mechanism. The
following piece of code and figure show how one of the methods of Component2 has
been specialized.

/////////////////////////////////////////////////////////////////////
void __stdcall CComponent3::ShowWndBackground_Com2(COLORREF bgcolor)
// default background color is white
{
//---------------------------------------------------------------------
-----------//
//The outer component (Component3) can reimplement an interface
supported
//by the inner component by forwarding calls to the inner component.The
outer
//component can specialize the interface by adding code before and
after the code
//for the inner component. As an example the background color of
component2's window
//will be changed to yellow
//---------------------------------------------------------------------
-----------//
bgcolor=RGB(255,255,0); //bgcolor is changed to yellow color
//---------------------------------------------------------------------
-----------//
m_pIComponent2->StartFlying();//The butterfly should fly
m_pIComponent2->ShowWndBackground_Com2(bgcolor);

}
The demo application's code is very similar to the example explained in the article and
only a window is used to visualize each component, and each window's menu acts as the
component's interface. I hope that these articles were useful for you and could be a guide
to begin with "COM" from scratch.

Introduction

Some programmers think DLLs are complicated but it's true they are like EXEs.
However I have seen programmers who can't build DLL. There are multiple way to
create a DLL. For usage with exe, easiest way is "Implicit linking". In this way, we
require .LIB file produced by DevStudio. Below, we create a DLL and then use it with
exe. Inside DLL, we calculate (multiply) two numbers that has been send from the exe.
Practically, exe is "Sender/Receiver" and DLL is "Calculator".

How to build MFC DLL?

1. Run VC++.
2. Choose : File > New.
3. Create "MFC AppWizard (DLL)" (named e.g. : MyFirstDll).
4. Declare in top of file:
5. //
6. #define DLLEXPORT __declspec(dllexport)
7. //

__declspec(dllexport)'s purpose is to add the " export directive " to the object
file. So you don't need a .DEF file. To make code more readable, define a macro
for __declspec(dllexport) : DLLEXPORT.

8. Now for exporting per function that you want, type before it : "DLLEXPORT" like
this:
9. DLLEXPORT int Multiply(int ParOne,int ParTwo)
10.{
11. int Mlt=ParOne*ParTwo;
12. return Mlt;
}
13. Press Build button.
14. Bring out DLL from oven!!

Note that linker also builds an " import library " with the same DLL name but
with .lib extension.

How to use MFC DLL?

1. Run VC++.
2. Choose : File > New.
3. Create "MFC AppWizard (exe)".
4. Choose "Dialog based".
5. Choose : Project > Add To Project > New > C/C++ Header File.
6. Name file e.g. : Imports.
7. Declare in Imports.h:
8. //
9. #define DLLIMPORT __declspec(dllimport)
//

__declspec(dllimport)'s purpose is to add the " import directive " to the object
file. To make code more readable, define a macro for __declspec(dllimport) :
DLLIMPORT.

10. Type after it:

DLLIMPORT int Multiply(int ParOne,int ParTwo);

This is the same function that you defined in DLL. Here, it has been introduced as
an "import function". But it is unrecognized for current project and we must
resolve it for linker.

11. Copy .lib file from release or debug folder (depends current project setting) in
previous project to current directory project because it must link with exe. Inside
.lib file (same import library), exists information about exported functions of
DLL.
12. Back to VC++ environment and Choose : Project > Settings > Link (tab).
13. Type [.lib file previous project] in "Object/library modules:". For example,
MyFirstDll.lib. Then press OK. It will resolve externals. Here, "int
Multiply(int ParOne,int ParTwo);" function.
14. Because we intend to use function in ...Dlg.cpp file (e.g. SimpleDllDlg.cpp), type
in top of file:
15.//
16.#include "Imports.h"
//

17. Now you can use exported function of DLL like this:
18.void CDllFunDlg::OnMltply()
19.{
20. UpdateData(TRUE);
21. m_Result=Multiply(m_ParamOne,m_ParamTwo);
22. UpdateData(FALSE);
}

Introduction

In my previous article, I mentioned simply DLL creation and using it. But it exports
function only. Now, I want to describe "how to export classes from a DLL?"

How to build MFC DLL Containing New Class?

1. Run VC++.
2. Choose : File > New.
3. Create "MFC AppWizard (DLL)" (named e.g.: MyScndDll).
4. Click right mouse button on root branch in "ClassView", point and click "New
Class...".
5. Select Generic Class as class type, and name it e.g. CRectArea.
6. Declare in top of file of "RectArea.h":
7. //
8. #define DLLEXPORT __declspec(dllexport)
//

__declspec(dllexport)'s purpose is to add the "export directive" to the object


file so you don't need a .DEF file. To make code more readable, define a macro
for __declspec(dllexport): DLLEXPORT.

9. Now, for exporting per class that you want, type before its declaration:
"DLLEXPORT" like this:
10.class DLLEXPORT CRectArea
11.{
12. public:
13. CRectArea();
14. virtual ~CRectArea();
};

15. Now, declare a member function for newly created class. E.g., Calculate:
16.class DLLEXPORT CRectArea
17.{
18.public:
19. int Calculate(int ParOne,int ParTwo);
20. CRectArea();
21. virtual ~CRectArea();
};

22. and define it:


23.int CRectArea::Calculate(int ParOne,int ParTwo)
24.{
25. return ParOne*ParTwo;
}

26. Press Build button.


27. Bring out DLL from oven!!

Note: linker also builds an "import library" with same DLL name but .lib extension.

How to use MFC DLL?

1. Run VC++.
2. Choose : File > New.
3. Create "MFC AppWizard (exe)".
4. Choose "Dialog based", then click Finish.
5. Choose: Project > Add To Project > New > C/C++ Header File.
6. Name file, e.g.: Imports.
7. Declare in Imports.h:
8. //
9. #define DLLIMPORT __declspec(dllimport)
//

__declspec(dllimport)'s purpose is to add the "import directive" to the object


file. To make code more readable, define a macro for __declspec(dllimport):
DLLIMPORT.

10. Type after it, declaration of exported function from DLL. Note: we must type
"DLLIMPORT" whenever intending to import class:
11.class DLLIMPORT CRectArea
12.{
13. public:
14. int Calculate(int ParOne,int ParTwo);
15. CRectArea();
16. virtual ~CRectArea();
};
This is the same class that you declared and defined in the DLL. Here, have
introduce as "import class". But it is unrecognized for current project and we must
resolve it for linker.

17. Copy .lib file from release or debug folder (depending on current project setting)
in previous project to current directory project because it must link with EXE.
Inside .lib file (same import library), exist information about exported class from
DLL.
18. Back to VC++ environment and choose: Project > Settings > Link (tab).
19. Type [.lib file previous project] in "Object/library modules:"

For example: MyScndDll.lib, then press OK. It will resolve externals. Here:
CRectArea class.

20. Cause we intend to use function in ...Dlg.cpp file (e.g.: ImportClassDlg.cpp), type
in top of file:
21.//
22.#include "Imports.h"
//

23. Now, you can use function of exported class, like this:
24.void CImportClassDlg::OnCalc()
25.{
26. UpdateData();
27. m_Result=m_RectArea.Calculate(m_ParOne,m_ParTwo);
28. UpdateData(FALSE);
}

Introduction

In third part of assortment articles "DLLs are Simple!", I describe how to create a DLL
using a .DEF file and using it.

What is .DEF file? It is a module-definition (.DEF) file that is a text file containing one
or more module statements that describe various attributes of a DLL include:

LIBRARY statement statement identifies the .DEF file as belonging to a DLL


EXPORTS statement lists the names of the functions exported by the DLL
DESCRIPTION statement describes the purpose of the DLL (Optional)
LIBRARY "DefExported"
DESCRIPTION 'DefExported For Present in CodeProject'
EXPORTS
Multiply @1
How To Build A DLL Using .DEF File

1. Run VC++.
2. Choose File>New.
3. In the dialog box, choose "MFC AppWizard (DLL)" and name it (e.g.
"DefExported")
4. Declare a member function:
5. public:
6. int Multiply(int PartOne,int PartTwo);
7. CDefExportedApp();
8.
9. Then define it:
10.int CDefExportedApp::Multiply(int PartOne, int PartTwo)
11.{
12. return PartOne*PartTwo;
13.}
14.
15. In the FileView tab, click "Source Files" and double click on "DefExported.def."
After the EXPORT statement, enter "[function name] @[number]" like this:
16.LIBRARY "DefExported"
17.DESCRIPTION 'DefExported For Present in CodeProject'
18.EXPORTS
19. ; Explicit exports can go here
20. Multiply @1
21.
22. Click Build Button.
23. Bring out DLL out of the oven!!

How To Use A DLL

To use a DLL dynamically, there are three simple API functions:

• LoadLibrary ( [path of DLL] )


Loads a DLL into the process address, returning a handle to the DLL.
• GetProcAddress ( [loaded library] , [function name] )
Returns a handle of a function so it can be used in your application.
• FreeLibrary( [handle of loaded DLL] )
Releases the memory allocated when the DLL was loaded

1. Run VC++.
2. Choose from menu File>New.
3. In the dialog box, choose "MFC AppWizard (EXE)" and name it (e.g.
"DynamicLoad")
4. Select "Dialog Based," and click the Finish button.
5. Place a button control on the dialog and double click on it for create its click
event.
6. Before typing the code for the button click (BN_CLICKED) event, we must
define a new function pointer with the correct number of parameters,
according to the parameters of the function we exported above.
typedef int (CALLBACK* LPFNMLTPLY)(int,int);

Sometimes you have to convert some variable types. For more information about
this conversion see Microsoft Support Article ID: Q117428.

7. Enter the code for the button click event:


8. HINSTANCE hClcltr=LoadLibrary("DefExported.dll");
9. LPFNMLTPLY lpfnMuliply;
10.lpfnMuliply = (LPFNMLTPLY)GetProcAddress(hClcltr,"Multiply");
11. Now we can use the "Multiply" function by calling "lpfnMultiply" and storing
the return value.
12.m_Rslt=lpfnMuliply(m_PartOne,m_PartTwo);
13. When you are finished using the library, you must call the FreeLibrary API to
release the memory allocated from the LoadLibrary method.

FreeLibrary( hClcltr );

Introduction

As you know, creating multimedia applications with special effect like sound, animation,
web-link and so on is time-consuming. But there is a method that is rapid and engaging.

Method is : Using Resource-Only DLL.

A resource-only DLL is a DLL that contains nothing but resources, such as icons,
bitmaps, strings, and dialog boxes.

What is necessary?

1. An HTML file composed of image, sound, GIF animation, link,...


2. Bring the HTML file and all its needed files together in a folder.

Note: In your HTML file, create two buttons with links "exit" and "execute" like below :
<p><a hidefocuse href="execute">
<img border="0" src="ExecNorm.gif" ALT="This may execute somethings"
style="cursor: hand; position: absolute; z-index: 1; left: 45; top: 120"
onmouseover="this.src='ExecHot.gif'"
onmouseout="this.src='ExecNorm.gif'"
width="75" height="43" >
</a></p>

How to create a Resource-Only DLL?

1. Run VC++.
2. Click New on the File menu and then choose MFC AppWizard (dll); name it
"ResOnly"
3. Click finish button in next step.
4. In ResourceView tab right-click on the root branch.
5. Choose the Import... command.
6. Import your HTML file; note only the HTML file.
7. Click Save All on the File menu and then close VC++.
8. Go to the folder that contains HTML file and all its needed files and copy all
files to VC++ project folder (ResOnly).
9. In ResOnly project folder find .rc file and open it with notepad; point to HTML
section. Now convert:
10."IDR_HTML1 HTML DISCARDABLE "Skin.htm"
11.TO :
"Skin.htm HTML DISCARDABLE "Skin.htm"

Then add all needed files for imported HTML just below it:

/////////////////////
Skin.htm HTML DISCARDABLE "Skin.htm"
BG.gif HTML DISCARDABLE "BG.gif"
c.gif HTML DISCARDABLE "c.gif"
ExecHot.gif HTML DISCARDABLE "ExecHot.gif"
ExecNorm.gif HTML DISCARDABLE "ExecNorm.gif"
ExitHot.gif HTML DISCARDABLE "ExitHot.gif"
ExitNorm.gif HTML DISCARDABLE "ExitNorm.gif"
WMPAUD7.WAV HTML DISCARDABLE "WMPAUD7.WAV"
pupil.gif HTML DISCARDABLE "pupil.gif"
whites.gif HTML DISCARDABLE "whites.gif"
////////////////////

12. Save and close notepad and open VC++ Project (ResOnly). Press Build
button. Now you have a DLL containing resources; in fact, it is the "Resource-
Only DLL".

How to use created Resource-Only DLL in multimedia App?

1. Run VC++.
2. Choose New on the File menu and then choose MFC AppWizard (exe).
3. In step1 dialogbox, select Single document and press next button till
step6. Now you choose CHtmlView as Base Class; then press Finish button.
4. Open CHtmlView derived class; in my project "CShowcaseView".
5. In OnInitialUpdate() member function, instead of default parameter of
Navigate2 type give:

res:// [Resource-Only Dll Name] // [Html file name]

e.g. : res://ResOnly.dll//Skin.htm

What is OnInitialUpdate()?

This member function is called by the framework after the view is first attached to
the document, but before the view is initially displayed. Here you call Navigate2
function and force view for representing Skin.htm file. By "res:", you can refer to
an HTML page embedded in the resources of a dynamic-link library (.dll) file. It
is a protocol like "http:".

6. In ClassView panel, right-click on the C...View (e.g. CShowcaseView) and


select Add Virtual Function... command.
7. In related dialogbox choose OnBeforeNavigate2 and press Add and Edit
button. OnBeforeNavigate2 member function is called by the framework to
cause an event to fire before a navigation occurs in the web browser. By this
function, we can lead web browser to the URL we want.

OnBeforeNavigate2 have some parameters. First of all is target URL to navigate


to, and end of all is a pointer to a cancel flag; an application can set this parameter
to nonzero to cancel the navigation operation.

8. Type the below code snippet after TODO comment:


9. CString url=lpszURL;
10.if (url.Right(4) == _T("exit"))
11. {
12. *pbCancel = TRUE;
13. keybd_event( VK_MENU, 0, 0, 0 );
14. keybd_event( VK_F4, 0, 0, 0 );
15. keybd_event( VK_F4, 0, KEYEVENTF_KEYUP, 0 );
16. keybd_event( VK_MENU, 0, KEYEVENTF_KEYUP, 0 );
17. }
18. else if(url.Right(7) == _T("execute"))
19. {
20. *pbCancel = TRUE;
21. MessageBox("This button could execute some commands.");
}

It is clear, it tests URL. If the last segment was "exit", it would have terminated program;
but it was "execute"; it does something that you want.

Now it is ready. If you act correctly, that must be like my demo exe file.

Thankfully greet guru Paul DiLascia.


Don't remember to visit my website www.pishro-narmafzar.com

Introduction

Based on DelayLoadProfileDLL.CPP, by Matt Pietrek for MSJ February 2000. This code
is intended to be included in a DLL inserted through a global Windows Hook (CBT hook
for example). It will replace functions from other DLLs (e.g. DDRAW.DLL) with
functions from your DLL.

Functions are hooked by passing a parameter structure to the HookAPICalls() function


as follows:

SDLLHook D3DHook =
{
"DDRAW.DLL",
false, NULL, // Default hook disabled, NULL function pointer.
{
{ "DirectDrawCreate", MyDirectDrawCreate },
{ NULL, NULL }
}
};

BOOL APIENTRY DllMain( HINSTANCE hModule, DWORD fdwReason, LPVOID


lpReserved)
{
if ( fdwReason == DLL_PROCESS_ATTACH ) // When initializing....
{
hDLL = hModule;

// We don't need thread notifications for what we're doing.


Thus,
// get rid of them, thereby eliminating some of the overhead of
// this DLL
DisableThreadLibraryCalls( hModule );

// Only hook the APIs if this is the right process.


GetModuleFileName( GetModuleHandle( NULL ), Work,
sizeof(Work) );
PathStripPath( Work );

if ( stricmp( Work, "myhooktarget.exe" ) == 0 )


HookAPICalls( &D3DHook );
}

return TRUE;
}

Now all that remains is to get your DLL loaded into the target process.


Introduction

This article shows how to add a menu interface to an application from a DLL at any time.

This example was written using VC++.NET 2003 but it should translate to VC++ 6.0
easily, as much of this was developed using 6.0 early on. No managed code was harmed
in the process of writing this article.

Background

I wanted a flexible way to load a DLL into my application to test it in-house and not
leave any marks when the customer got the application. Future enhancements could
include targeted computer-based training modules.

This is not meant to be a be-all end-all treatise, but, rather, a springboard for extending
applications after the application has been written.

Using the code

There are two parts to this problem: the plug-in DLL and the target application. The
target application needs to know how to call the plug-in DLL without the benefit of lib
file. We accomplish this by standardizing the plug-in interface. The interface for this
project is in the TestPlugin project in plugin_api.h. There are other ways of handling the
interface. For instance, you could create a structure of function pointers and populate it
after LoadLibrary and clean it out before FreeLibrary. In this example, you only have
to store the HMODULE value. If you had more than one plug-in DLL, you would only need
to store the HMODULE values and not have to carry many structures of the function
pointers.

Let's talk about the plug-in DLL first.

The Interface: There are four methods defined publicly for this DLL in TestPlugin.def.
They are InstallExtMenu and RemoveExtMenu which install and remove the menus
respectively, GetExtMenuItemCount which gives the application the number of menu
items installed, and GetExtMenuItem which serves to map the menu control ID to a
Windows message identifier. More can be done to extend the interface but this appears to
be the bare minimum to get this up and running. The file plugin_api.h handles the details
of connecting to the correct DLL based on a save HMODULE value from LoadLibrary.

CTestPluginApp: There are two ways of introducing user-defined Windows messages.


One is defining a value greater than WM_USER; the other is to get a value using
RegisterWindowMessage. WM_USER is useful for messages internal to an application.
RegisterWindowMessage is useful when a particular message may be used across
applications. We are using RegisterWindowMessage because this DLL may be servicing
more than one application and because other DLLs can also use the registered messages.
The registered messages are really static UINTs, attached to the CTestPluginApp object
and initialized when the DLL is loaded. CTestPluginApp also contains the menu ID
registered to the message ID map which is used by GetExtMenuItem to return the
registered message when the menu item is selected. You will notice that the map class
used is MFC's CMap<> template. My only reason for using it here is to maintain the MFC
framework and not to clutter the code by importing STL. My personal preference is to
use std::map over CMap.

CCommandWnd: This window receives the registered message from the target application.
When the application initializes the plug-in DLL, it passes an HWND to the DLL so that the
DLL can set up the menus for that window. In addition to setting up the menus, the DLL
also creates a CCommandWnd window as a child to the window passed in.

Now let's talk about the target application.

1. CMainFrame: Okay, so making the CMainFrame window responsible for


maintaining the menus and how they are handled is a bit arbitrary. You could
do this either from the CView-based window or the CDocument object. I
wanted to keep all of the code for handling the menus and the plug-in in one
place and CMainFrame seemed to be the easiest way to handle it. There are
some really good reasons for relocating the code to the CDocument or CView
object and much of what I am presenting here can be translated with little
work to the other CDocument and CView. When the function InstallExtMenu
is called, the HWND for CMainFrame is sent along with a value which will be
used to identify the CCommandWnd from the plug-in. The plug-in creates the
CCommandWnd as a child window to CMainFrame and the CCommandWnd window
can be retrieved using CWnd::GetDlgItem.
2. CMainFrame::OnCommand: This is where we translate the menu command ID
to the registered message used by the plug-in DLL (the red indicates the
plug-in's interface):
3. BOOL CMainFrame::OnCommand(WPARAM wParam, LPARAM lParam)
4. {
5. // if wParam translates to our internal message
6. // then send the internal message
7. UINT nSendMsg = 0 ;
8. if (::GetExtMenuItem(m_TestModule, (UINT)wParam, &nSendMsg) !
= FALSE)
9. {
10. CWnd * pWnd = GetDlgItem( CHILD_WINDOW_ID ) ;
11. if ( pWnd != NULL && pWnd->GetSafeHwnd() != NULL )
12. {
13. // if ::GetExtMenuItem returns TRUE and we have the
child
14. // window then send the message to the child
15. return (BOOL)pWnd->SendMessage( nSendMsg, 0, 0 ) ;
16. }
17. }
18. return CFrameWnd::OnCommand(wParam, lParam);
}

19. CMainFrame::OnCmdMsg: CFrameWnd contains a member variable called


m_bAutoMenuEnable which is used to disable menus that do not have
ON_COMMAND or ON_UPDATE_COMMAND_UI handlers. The menus that the plug-in
installs do not have native handlers, so you would think that we need to set
m_bAutoMenuEnable to FALSE to enable our menus. Unfortunately, this would
also enable other menus that we may not want enabled. Fortunately, we don't
need to bother with m_bAutoMenuEnable. By overriding
CFrameWnd::OnCmdMsg, we can ask the plug-in (if it is loaded) if it owns this
menu and enable it if it does:
20.BOOL CMainFrame::OnCmdMsg(UINT nID, int nCode, void* pExtra,
21. AFX_CMDHANDLERINFO* pHandlerInfo)
22.{
23. if ( nCode == CN_COMMAND )
24. {
25. // if nID translates to our internal message
26. // then enable the menu item
27. // otherwise, let OnCmdMsg() handle nID.
28. UINT nPostItem = 0 ;
29. // does the plugin own this menu item?
30. if ( ::GetExtMenuItem( m_TestModule, nID, &nPostItem ) !=
FALSE )
31. {
32. return TRUE ; // if yes, then enable it by returning
TRUE
33. }
34. }
35. // otherwise, let the CFrameWnd handle it
36. return CFrameWnd::OnCmdMsg(nID, nCode, pExtra, pHandlerInfo);
}

Points of Interest

I have set up a macro called _PLUGIN_ON_DEMAND in stdafx.h. If you undef this macro, the
CTargetApp will try to load the DLL in its InitInstance method and unload it in the
ExitInstance method. I have also included an alternate IDR_MAINFRAME menu (with the
subtitle ALTMENU) that you can use when the _PLUGIN_ON_DEMAND is not defined to
show how the menus can be added when the top-level Tests menu does not exist.

History
This is version 1.0!

Introduction

DLLs are a great way of sharing common pieces of code data between applications.
When it comes down to exporting C++ classes from DLLs most of us go for MFC
extension DLLs where we can use the AFX_EXT_CLASS macro to export an entire class.
Unfortunately, MFC is no lean and mean class architecture, which means that distributing
MFC extension DLLs mean that you have to include the big MFC runtime not to mention
the fact that your DLL can only be linked to MFC applications exclusively. What's the
solution then? Enter standard Win32 DLLs.

Details

I couldn't believe my eyes on how easily one can export C++ classes directly from a plain
vanilla Win32 DLL. Just make one and insert your classes into the DLL. Now simply put
__declspec(dllexport) in between the class keyword and the class name, i.e.

// in your header...

class __declspec(dllexport) CDllTest


{
public:
CDllTest(){}
~CDllTest(){}

public:
void SayHello();
};

// in your cpp...

void CDllTest::SayHello()
{
printf(_T("Hello C++"));
}

That's it! The sample code and project are pretty self explanatory. Enjoy.

Introduction

This article shows a step by step technique to create your first DLL with VC++.

Steps to create your first DLL

• Create a Win32 Dynamic Link Library project and add a .cpp & a .h file.
• In the .cpp file, create a class instantiated from the CWinApp file.

• # include <stdafx.h>
• # include "SourceFile.h"

• class CDllApp : public CWinApp
• {
• public:

• CDllApp::CDllApp()
• {
• Calc(0,0);
• }

• DECLARE_MESSAGE_MAP()
• };

• BEGIN_MESSAGE_MAP(CDllApp,CWinApp)

• END_MESSAGE_MAP()

CDllApp DllObject;

• In the .h file (Here it is SourceFile.h) define the functions to be used. Also


specify the "dllexport" value for the _declspec function.
• extern "C" _declspec(dllexport) int Calc(char no1,char no2)
• {
• char result;
• result = no1 + no2;
• return result;
}

• Then Compile the Dll.


• Then Create a normal Win32 Application with a .cpp file & a .h file.
• In the .h file, ( Here it is AppHeader.h )declare the function with the
dllimport value of _declspec
• extern "C" _declspec(dllimport) Calc(int FirstValue,
int SecondValue);

• In the .cpp file, use the function.


• # include "AFXWIN.H"
• # include "AppHeader.h"

• class MainFrame : public CFrameWnd
• {
• public:

• MainFrame()
• {
• Create(0,"Trial");
• }

• void OnLButtonDown(UINT nFlags,CPoint point)
• {
• int res;
• char str[5];
• res = Calc(998,226);
• sprintf(str,"%d",res);
• MessageBox(str);
• }

• DECLARE_MESSAGE_MAP()
};

• In the Link tab of the "Project->Settings" Dialog, Go to the Text Box labeled
"Object / Library Modules" and specify the path of the Dll File. Then copy the
compiled dll file to your current app path directory and run the program.

Some things to note

The DLL file may not be visible due to the File View Options in the Windows folder. So,
U can either go to the DOS Prompt and copy the file or enable the setting "Show all files"
in Windows explorer to copy the file. To Create a DLL that uses MFC, See the following
example. Note that extern "C" has not been used and the macro
AFX_MANAGE_STATE(AfxGetStaticModuleState()); has been used to implement MFC.

_declspec(dllexport)CString Display(CString a,CString b)


{
AFX_MANAGE_STATE(AfxGetStaticModuleState());
CString Str;
Str = a + b;
return Str;
}

That's all folks. All luck and have a great time.


Introduction

What is a DLL? DLL stands for Dynamic Link Library. Using DLL's offers several
advantages as mentioned below:

1. They simplify project management. If different groups work on different


modules during the development process, the project is easier to manage.
2. They help conserve memory. If two or more applications use the same
DLL, the DLL has its pages in RAM once and the pages are shared by all of the
applications. The C/C++ run-time library is a perfect example. Many
applications use this library.
3. They extend the features of an application. Since DLLs can be
dynamically loaded into a process's address space, an application can
determine at run time what actions to perform and then load the code to
execute those actions on demand.
4. They can be written in many programming languages. You can choose
the best language for the job at hand. Perhaps your application's user
interface is best programmed with Microsoft Visual Basic, but the business
logic is better handled by C++.

Writing the application using a DLL

There are two parts to writing this application.

1. Writing the DLL whose functions we are going to call


2. Writing a test client for the DLL

Creating the DLL


1. Fire up Visual C++ & choose the Project as MFC AppWizard(Dll) and type in
Project name as MyDll
2. Let the default selection for DLL type remain, i.e "Regular DLL using Shared
MFC DLL"
3. Click Finish and then Ok to get Visual Studio to generate the necessary files.
4. Go to the class view, right click on “MyDll classes” and choose “New
Class”.
5. Now choose Class type as "Generic Class" .
6. Type in the Class name CMyClass, the wizard automatically fills in the rest of
the names.
7. Go to the class view again
8. Right click on the CMyClass and choose “Add Member Function”
9. Type in the name of the function type as CString and fill in the function
declaration as SayHello (CString strName). Choose Access type of the
function as public. Note : CString is a MFC class that makes manipulation of
strings very easy.
10. Now go to file view and type in the following code into CMyClass.cpp as
shown below in Code Snippet 2
11. To be able to call the functions from an external application we have to prefix
all function signatures with __declspec(dllexport) . This change is made in
the CMyClass.h file as shown in Code Snippet 1

Compile the application and your DLL is ready

// CMyClass.h
//{----------- Code Snippet 1 --------------------------
class CMyClass
{
public:
__declspec(dllexport) CString SayHello (CString strName);
__declspec(dllexport) CMyClass();
__declspec(dllexport) virtual ~CMyClass();

};

//{----------- Code Snippet 1 --------------------------


// CMyClass.cpp
//{----------- Code Snippet 2 --------------------------

CString CMyClass::SayHello(CString strName)


{
//Return the input string with a Hello prefixed
return "Hello " + strName;
}

//{----------- Code Snippet 2 --------------------------

Creating the DLL Client

Now we write a 'Client' to test our DLL This is a MFC dialog based application with a
edit box and two buttons.
1. Select New MFC AppWizard(exe) from the project menu and type in the
project name TestDLL
2. Choose Dialog based application and click Finish
3. You would now be looking at the application in the resource view. Add an edit
box to the application by selecting the edit box, next click on the dialog box
and drag.
4. Also create a CString (value) variable associated with it, call this variable
m_edit i.e Click on the edit box and press CTRL and W to bring up the class
wizard. Choose the member variables tab and choose IDC_EDIT1 and click on
“Add Variable”, of type "Value" and type m_edit
5. Now a file called TestDLLDlg.cpp would have been generated .
6. Double click the “Ok” button on the dialog, the wizard pops up a box asking a
name for the function, choose the default name “OnOk” to go to the
TestDLLDlg.cpp file
7. Modify TestDLLdlg.cpp to look like the Code Snippet 1, the code entered has
no effect and the project at this point will not compile. (Basically we are
calling a method of a class object, this object has not been defined as yet.)
8. Goto the file TestDLLDlg.h and include the header file for your class i.e
MyClass.h .
9. In the file TestDLLDlg.h declare an object of your class objMyClass present
in the DLL
10. After steps 8 & 9 the code looks as in Code Snippet 2
11. We need to modify the project settings to compile the project, Click on
"Project->Settings->Link" and in the "Object/Library Modules" enter the
complete or relative path to the DLL's library file. i.e. A .lib file is generated in
the same folder as your DLL, I have entered "..\MyDll\Debug\MyDll.lib" here.
12. Compile the application and if it has compiled successfully. Run it.

Hey, Why are we getting this stupid box saying "Unable to located DLL" ?
Solution: Copy the MyDll.dll to the same folder as the TestDll.exe

Run the application, enter a name in the text box and click the okay button, this will show
a message box containing the same text prefixed with a hello.

//TestDLLdlg.cpp
// ----------------- Code Snippet 1 ----------------------------
void CTestDLLDlg::OnOK()
{

UpdateData(true);
CString strResult = objMyClass.SayHello(m_edit);
AfxMessageBox (strResult);
//CDialog::OnOK();

}
// ----------------- Code Snippet 1 ----------------------------
// ----------------- Code Snippet 2 ----------------------------
//TestDLLDlg.h
///////////////////////////////////////////////////////////////////////
//////
// CTestDLLDlg dialog
#include "..\MyDll\MyClass.h"

class CTestDLLDlg : public CDialog


{
// Construction
public:
CTestDLLDlg(CWnd* pParent = NULL); // standard constructor
CMyClass objMyClass;

// Dialog Data

// ----------------- Code Snippet 2 ----------------------------

About Imran Ebrahim


I'm a software professional with 4 years of development experience. I've worked on JAVA, VB6, VC6, C#
& some JavaScript.

Most of my experience is on front & middle tiers.

I'm interested in Hi-Performance code, OO Methodology, OS API's, Optimizing Windows, Tweaking IE.

Click here to view Imran Ebrahim's online profile.

Introduction

This is a easy to use library which let you use different compilers, to optimize-compile
your DLL for different processors.

Details

Move all the to be optimized classes and functions to a DLL, and the DelayLoader library
will automatically import all the functions from the fastest (for this processor available)
DLL.

There are 4 projects included:

• DelayDLL, a library project which is the one to use in your own workspaces
• MainDLL, a sample DLL which exports a function
• Test, a sample DLL which exports a function
• MainMFC, a sample project which makes uses of the projects above, no fancy
MessageBox, just step through with debugger to see which DLL is loaded. A
TRACE output is done, so you can easily see which DLL is currently loaded

The library is based on the Delay loading of DLL. So we start loading the DLL when it is
needed for the first time. No LoadLibrary and GetProcAdress functions need to be
called, The Delay Loading from Microsoft takes care of this.
With a specific hooking mechanism we can force our app to load a different DLL than
compile-time was programmed. We could even get the adress of a different named
function. The DelayDLL.dll should be loaded by the MainMFC program, but because we
run this program on my Athlon MP the DelayDLL.Athlon.dll file is loaded.

This hook makes it very easy when we build a program which should make use of
processor specific optimizations. The UnloadDelayedDLL( LPSTR ) method can unload
a DLL at runtime, to free up memory, for if you don't have 1Gb of RAM like me :-p

Hope this is useful for you guys.

How to use

• Add the DelayDll project to your workspace.


• Set a dependency for your project to this project, the library will automatically
be linked against your exe.
OR
Add the precompiled library delaydll.lib to your DLL's configuration.
• Add a new file to your project, so you can easy change / add new DLLs (See
below.)
• Make a copy of the configuration
• Build->Configuration
• Select the DLL-project you want to optimize,

You could only add a copy of Win32 Release, and call it w32 Athlon Rls. Or also make a
copy of the win32 Debug, and call it w32 Athlon Dbg. The only thing you have to change
in the project settings, is on the link tab, the Output name, to MainDLL.Athlon.dll for
example.

And of course you should select another compiler, for this configuration ( using the
Codeplay Compiler Controller Plug-in ). Or make some assembler files yourself and use
them for this configuration. For now only compile the .c files using VectorC, so not the
complete project. ( no cpp support yet ). Of course you can also use the Intel compiler to
create optimized DLLs.

Here is an example of file to be added to your project, which will make use of the
optimized DLLs.

// <START OF FILE : MyDelayDlls.cpp>


/******************************************************************/
/* Sample .cpp file, to include in your executable */
/******************************************************************/
#include "..\DelayDll\DelayDll.h"

// Tell delay loader to call my hook function


SET_DLIHOOK_PROC

/*****************************************/
/*Which DLLs should be Delay loaded */
/***************************************/
// Use Upper and Lower case letters, exactly the same as in de DLL
// Project ,Thanks to MS DelayLoader :-(
#pragma comment(linker, "/DelayLoad:Test.dll")
#pragma comment(linker, "/DelayLoad:MainDLL.dll")

// Descriptor for Test.dll


// value nr,Description
// 1InUse, always 1, for the last in line, use 0, so library known
array
// is ended.
// 2DllName, use this if your optimized DLL has a complete
// different name than your original DLL
// 3DllExtension, if you use an extension, Original:Test.dll,
// Optimized:Test.Pentium.dll
// 4Optimized for Processor, A PIV dll, will not be loaded on a PIII
// 5Used Compiler, not used, but maybe we ever want to know this??

DLLDescriptor TestDLL[] =
{
1,NULL,"Pentium.dll",DELAYDLL_PROC_PENTIUM,DELAYDLL_COMP_INTEL,
NULL
};

// Descriptor for another DLL


DLLDescriptor MainDLL[] =
{
1,"MainPIV.dll",NULL,DELAYDLL_PROC_PIV,DELAYDLL_COMP_VECTORC,
1,NULL,"Athlon.dll",DELAYDLL_PROC_ATHLON,DELAYDLL_COMP_VECTORC,
1,NULL,"PII.dll",DELAYDLL_PROC_PII,DELAYDLL_COMP_VECTORC,
NULL
};

// Only these DelayLoaded DLL will be checked for there Optimized


versions
// value nr,Description
// 1Dll name, (case sensitive)
// 2Use this Descriptor struct
// ( All DLLs, could have the same Descriptor struct,
// ONLY when Extensionname is
// used and not a fullname )

DLLCollection pDelayLoadedDLLs[] =
{
"Test.dll", TestDLL, // Tries to load first : Test.Pentium.dll,
then Test.dll
"Test2.dll", TestDLL, // Tries to load first : Test2.Pentium.dll,
then Test2.dll
"MainDLL.dll", MainDLL,// Tries to load first : MainPIV.dll, then
MainDLL.Athlon.dll,
// then MainDLL.PII.dll, then MainDLL.dll
NULL
};
//<END OF FILE>
About ETA

Introduction

The readers/writers problem is one of the classic synchronization problems. Like the
dining philosophers, it is often used to compare and contrast synchronization
mechanisms. It is also an eminently practical problem.

Readers/Writers Problem - Classic definition

Two kinds of processes -- readers and writers -- share a database. Readers execute
transactions that examine database records; writer transactions both examine and update
the database. The database is assumed initially to be in a consistent state (i.e., one in
which relations between data are meaningful). Each transaction, if executed in isolation,
transforms the database from one consistent state to another. To preclude interference
between transactions, a writer process must have exclusive access to the database.
Assuming no writer is accessing the database, any number of readers may concurrently
execute transactions.

Some time ago at work, we had to implement a server that relays and translates his
incoming datafeed to multiple (typically > 32) clients. As this datafeed represents the
continuously (but on a non-regular time base) changing (stock/option) market prices, fast
relaying is crucial. We developed a solution that consists of one receiving thread, multiple
translator threads, and even more sending threads (since we do not want to block the
server if a client fails to receive).

Obviously, all the threads need access to the received and / or translated packet. To
achieve this without corrupting data, synchronization is necessary. Searching the MSDN,
resulted in finding several synchronization objects (CCriticalSection, CMutex, etc.) of
which none seem to fulfill our demands: Multiple read-access when not writing. We thus
decided to write the desired synchronization object ourselves: CMutexRW.

Formal readers and writers solution using semaphores

Since our problem has extensively been studied (since 1960?) we first turned to
some old college-books on parallel formal semantics to refresh our knowledge about
the problem. Soon we found the pages describing the readers and writers problem
and (several) solution outlines. We chose to implement our solution (with readers
preference) using semaphores.

First some quick (probably skipable) refresh course to (formal) semaphores: Semaphores
where first introduced by Dijkstra in 1968, who thought it to be an useful tool for
implementing mutual exclusion and for signalling the occurrence of events such as
interrupts. A semaphore is an instance of an abstract data type: it has a representation that
is manipulated only by two special operations, P and V. Because Dijkstra is Dutch, the P
and V stand for Dutch words. In particular, P is the first letter of the Dutch word
passeren, which means `to pass'; V is the first letter of vrijgeven, which means `to
release'. The V operation signals the occurrence of an event; the P operation is used to
delay a process until an event has occurred. In particular, the two operations must be
implemented so that they preserve the following property for every semaphore in a
program.

Semaphore Invariant: For semaphore s, let nP be the number of completed P operations


and let nv be the number of completed V operations. If init is the initial value of s, then in
all visible program states, nP <= nV + init.

Consequently, execution of a P operation potentially delays until an adequate number of


V operations have been executed.

By using this definition of semaphores the readers and writers problem can be solved in
the following way:

integer nReaders := 0
semaphore semReaders := 1
semaphore semWriters := 1
Reader[i: 1 .. m] :: do true ->
P( semReaders )
nReaders := nReaders + 1
if nReaders = 1 -> P( semWriters ) fi
V( semReaders )

read the database

P( semReaders )
nReaders := nReaders - 1
if nReaders = 0 -> V( semWriters ) fi
V( semReaders )
od
Writer[j: 1 .. n] :: do true ->
P( semWriters )
write the database
V( semWriters )
od

Clearly this solution gives readers preference over writers; once one reader is accessing
the database, all readers are allowed to read the database without performing the P or V
operations on semWriters.

The solution in C++

Without further delay, here's my implementation of CMutexRW. I hope the code is


self-explanatory:
class CMutexRW
{
protected:
HANDLE m_semReaders;
HANDLE m_semWriters;
int m_nReaders;
public:
CMutexRW() :
m_semReaders(NULL),
m_semWriters(NULL),
m_nReaders(0)
{
// initialize the Readers & Writers variables
m_semReaders = ::CreateSemaphore(NULL, 1, 1, NULL);
m_semWriters = ::CreateSemaphore(NULL, 1, 1, NULL);
m_nReaders = 0;

if (m_semReaders == NULL || m_semWriters == NULL)


{
LPVOID lpMsgBuf;
FormatMessage(
FORMAT_MESSAGE_ALLOCATE_BUFFER |
FORMAT_MESSAGE_FROM_SYSTEM |
FORMAT_MESSAGE_IGNORE_INSERTS,
NULL,
GetLastError(),
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
(LPTSTR) &lpMsgBuf,
0,
NULL
);
TRACE( "***** ERROR: CreateSemaphore: %s\n",
(LPCTSTR)lpMsgBuf );
LocalFree( lpMsgBuf );
}
};

virtual ~CMutexRW()
{
if (m_semWriters)
VERIFY( ::CloseHandle(m_semWriters) );

m_semWriters = NULL;
if (m_semReaders)
VERIFY( ::CloseHandle(m_semReaders) );
m_semReaders = NULL;
}

inline void Lock_DataRead(){


DWORD dwEvent = WAIT_TIMEOUT;

// P( semReaders )
dwEvent = ::WaitForSingleObject( m_semReaders, INFINITE );
ASSERT(dwEvent == WAIT_OBJECT_0);

m_nReaders++;

if (m_nReaders == 1)
{
// P( semWriters )
dwEvent = ::WaitForSingleObject( m_semWriters, INFINITE );
ASSERT(dwEvent == WAIT_OBJECT_0);
}
// V( semReaders )
VERIFY( ::ReleaseSemaphore( m_semReaders, 1, NULL ) );
};

inline void Unlock_DataRead(){


DWORD dwEvent = WAIT_TIMEOUT;
// P( semReaders )
dwEvent = ::WaitForSingleObject( m_semReaders, INFINITE );
ASSERT(dwEvent == WAIT_OBJECT_0);

m_nReaders--;

if (m_nReaders == 0)
{
// V( semWriters )
VERIFY( ::ReleaseSemaphore(m_semWriters, 1, NULL) );
}
// V( semReaders )
VERIFY( ::ReleaseSemaphore( m_semReaders, 1, NULL ) );
};

inline void Lock_DataWrite(){


DWORD dwEvent = WAIT_TIMEOUT;

// P( semWriters )
dwEvent = ::WaitForSingleObject( m_semWriters, INFINITE );
ASSERT(dwEvent == WAIT_OBJECT_0);
}

inline void Unlock_DataWrite(){


// V( semWriters )
VERIFY( ::ReleaseSemaphore(m_semWriters, 1, NULL) );
};

};

Of course we would also like to have some objects behaving like MFC's CSingleLock,
i.e. automatically unlocking the locked CMutexRW upon leaving scope. Here's my
implementation of CReadLock:

class CReadLock
{
protected:
CMutexRW* m_pMutexRW;
bool m_bIsLocked;
public:
CReadLock(CMutexRW* pMutexRW, const bool bInitialLock = false) :
m_pMutexRW(pMutexRW), m_bIsLocked(false)
{
ASSERT(m_pMutexRW);
if (bInitialLock){
m_pMutexRW->Lock_DataRead();
m_bIsLocked = true;
}
};
inline const bool& IsLocked() const{
return m_bIsLocked;
};

inline void Lock(){


ASSERT(m_bIsLocked == false);
m_pMutexRW->Lock_DataRead();
m_bIsLocked = true;
};

inline void Unlock(){


ASSERT(m_bIsLocked);
m_pMutexRW->Unlock_DataRead();
m_bIsLocked = false;
};
virtual ~CReadLock(){
if (m_bIsLocked){
m_pMutexRW->Unlock_DataRead();
}
};
};

followed by the implementation of CWriteLock:

class CWriteLock
{
protected:
CMutexRW* m_pMutexRW;
bool m_bIsLocked;
public:
CWriteLock(CMutexRW* pMutexRW, const bool bInitialLock = false) :
m_pMutexRW(pMutexRW), m_bIsLocked(false)
{
ASSERT(m_pMutexRW);
if (bInitialLock){
m_pMutexRW->Lock_DataWrite();
m_bIsLocked = true;
}
};

inline const bool& IsLocked() const{


return m_bIsLocked;
};

inline void Lock(){


ASSERT(m_bIsLocked == false);
m_pMutexRW->Lock_DataWrite();
m_bIsLocked = true;
};

inline void Unlock(){


ASSERT(m_bIsLocked);
m_pMutexRW->Unlock_DataWrite();
m_bIsLocked = false;
};
virtual ~CWriteLock(){
if (m_bIsLocked){
m_pMutexRW->Unlock_DataWrite();
}
};
};

Usage example

Using these objects is quite straightforward; when wanting to read the data guarded
by a CMutexRW object, construct a CReadLock object in the local scope, obtain the
lock and do the actual read. Next, release the lock, or leave the local-scope. In the
same way we can write the data in a thread-safe way.
class MyExampleObject{
protected:
CMutexRW m_mutexRW;
int m_Data;
public:
void Set(const int& srcData){
CWriteLock writeLock(&m_mutexRW, true);
m_Data = srcData;
}
void Get(int& destData){
CReadLock readLock(&m_mutexRW, true);
destData = m_Data;
}
};

There is (as observable) a small annoying problem; the Get routine is not const, i.e. the
MyExampleObject does not behave as proper (data) objects should. This can be easily
fixed by implementing the code as follows:

class MyExampleObject{
protected:
mutable CMutexRW m_mutexRW;
int m_Data;
public:
void Set(const int& srcData){
CWriteLock writeLock(&m_mutexRW, true);
m_Data = srcData;
}
void Get(int& destData) const{
CReadLock readLock(&m_mutexRW, true);
destData = m_Data;
}
};

Standard disclaimer

Although we use the discussed objects (slightly modified for better performance)
extensively in our software, I cannot guarantee a 100% bug-proof, thread-safe and
so on behaviour. So use it, use it well, but never forget, I'm just another programmer
;).
About Joris Koster
Joris lives in the geographical center of The Netherlands, Utrecht, where he started his academic
education Computational Science in 1995.
In (aug) 2002 he (finally ) obtained his master's degree by finishing his Master's thesis resulting from
research into parallel matrix-vector multiplication on supercomputers.

In 1997 he started working (parttime) at Accent Pointe, the IT department of Accent Groupe, a market
maker company at the Amsterdam Euronext Exchange.

From aug 2002 he continued working at Accent Pointe, but on a full-time basis, where his major
occupation became the development of a new portfolio management model (like Black & Scholes,
Binominal trees, etc.) and a fast (parallel) implementation.

Besides this major occupation, several minor engineering projects ensure his programming skills are
kept up to date

Click here to view Joris Koster's online profile.

Abstract

In this article, I am going to touch on problems of DLL backwards compatibility, which


are also well known as the ‘DLL Hell’.

I am going to list results of my own investigation and also refer to other investigators’
results. At the end of the article, I will give my approach for solving one of the ‘DLL hell’
problems.

Introduction

Once I’ve got a task to solve the problem with the DLL updates. Some company supplied
users with DLLs that contained classes that developers used in their C++ applications. In
other words, it was some kind of a SDK in the DLL. There were no restrictions on how to
use these classes, so developers derived from them while creating their applications. Soon
they realized that their applications started to crash after the DLL updates. Developers
were required to rebuild their applications to get versions that can work with the new
DLL.

Further I will provide you with my research results on this problem, with the information
I have gathered from the external sources and finally I will give my solution for one of
the ‘DLL Hell’ problems.

Research results

As I have understood, the issue was in the modification of the base classes placed in the
supplied DLL. I’ve overviewed several articles and found that the problem of the DLL
backwards compatibility is not a new one but as a true researcher I’ve decided to make
several experiments personally. As a result, I have got the following list of problems:
1. Adding of a new virtual method to a class exported from a DLL can cause the
following problems:
o If class already has a virtual method B and we are adding a new one
named A before it, then we are changing the table of virtual methods.
Now the first virtual method in the table will be A but not B and the
client program which calls B will fail without recompilation as the call of
B will cause the call of A and this is another method which possibly has
other parameters and return type.
o When a class doesn’t have virtual methods and none of its base
classes have, then adding of a new virtual method to it or to its base
class will cause adding of the pointer for the table of virtual methods.
This will cause change in the class size. So the error will occur after a
client program will allocate memory for the class (an amount of
memory that was required before the class has been changed) and will
try to modify some of the class' fields explicitly or implicitly. In this
case, as the pointer to the table of virtual method is added to the
beginning of the class, all addresses of the class fields are shifted and
thus we will get an abnormal client program behavior or a runtime
error.
o In case when a class has virtual methods or any of its parent classes
has, we can’t add virtual methods to classes exported from the DLL if
they participate in the inheritance. We can’t add virtual methods not
only to the beginning of the class declaration but also to the end of it.
The problem is in shifting in the table of virtual methods. Note that
even if you add a new virtual method to the end of the class
declaration then the child’s virtual methods will be shifted.
2. Adding of a new field (of any kind) to a class declaration can cause following
problems:

1.Adding of a new field to the end of a class causes class size to change
as in the case of adding of a virtual method to a class that didn’t have
any. The client program will allocate less memory than it is required for a
new class and this will cause referencing to the memory out of the class
scope.
2.Adding of a new field between existing ones is even worse. It will case
all addresses of fields after the new one to be shifted and a client
application will work with the incorrect addresses while working with the
fields that are situated after the new field. We also have the problem as in
the previous point here.

There are other reasons that can cause DLL backwards compatibility problems, so below
is the list of solutions that are usually offered to overcome most of them.

DLL coding conventions in brief

This is the summary of solutions I’ve found in articles on the web and I’ve got while
talking with different developers.

The following conventions of a DLL development are usually offered to support DLLs
backwards compatibility:
1. Coding conventions:
o Each class exported from a DLL or its base class should have at least
one virtual method to add a virtual methods table to a class. This will
allow adding of virtual methods to it later.
o If you are going to add a virtual method to a class, add it after all
other virtual methods’ declarations. This will allow not to spoil the
order of methods in the virtual methods table.
o If you are planning to extend a class with the new fields later, then add
there a pointer to the structure where you will add them. In this case
while adding a new field, you will add it not to the class but to the
structure and so the size of the class won’t change. Work with the
structure as with the storage of additional class fields and add
corresponding fields’ accessors to the class declaration if it is required.
In this case, the DLL must be linked to a client application implicitly.
o To solve the problems stated in the previous points, you may also
make the pure interface classes for all exported classes, but in this
case it will be impossible to inherit these classes in the client
application and it will be also impossible to make the hierarchy of
exported classes in the DLL.
o Distribute two versions of the DLL and LIB (Debug/Release) because if
you will distribute only Release version then users who are developing
clients won’t be able to debug their application as Release and Debug
modes have different heap managers and thus if release DLL will
allocate memory which will be freed in the debug client application
(vice versa), it will cause a runtime failure. There is also another
possibility to solve this problem. You may add methods to allocate and
deallocate memory for the DLL classes to the DLL and also prohibit
deallocation of the client’s application objects in the DLL. But it is not
simple to maintain this solution.
o Don’t change the default parameters in the DLL classes’ methods as
they are moved to the client’s code at compile time.
o Beware of modification of inline methods.
o Check that all enumerations don’t have default element values. As
while adding/removing a new enumeration member you may shift
values of the old enumeration members. That is why each member
should have a unique value assigned to it. Also it should be
documented if enumeration can be extended, because in this case
client application developer should keep it in his mind.
o Don’t change macros in DLL headers.
2. Use DLL versioning: In the case of major DLL changes it is good to have an
ability to change the DLL name like Microsoft does with the MFC DLL. For
example, there can be the following DLL name template: Dll_name_xx.dll
Where xx is the number of the DLL version. Sometimes it is required to make
big changes in the DLL, which break its backwards compatibility. In this case,
the new name should be assigned to the DLL. While an installation to an OS,
a new DLL, the old one will not be removed and clients compiled with it will
still use it. At the same time, clients compiled with the new DLL will use the
new one.
3. DLL backwards compatibility testing: There are too many possibilities to break
the DLL’s backwards compatibility and that is why there should be some kind
of backwards compatibility testing.
Below I am going to discuss the problem of virtual methods and I will also give a
solution for one of its variants.

Virtual methods and inheritance

Let us investigate an example with virtual methods and inheritance:

/**********Exported class (dll) **********/


class EXPORT_DLL_PREFIX VirtFunctClass{
public:
VirtFunctClass(){}
~VirtFunctClass(){}
virtual void DoSmth(){
//this->DoAnything();
// Uncomment of this line after the corresponding method
//will be added to the class declaration
}
//virtual void DoAnything(){}
// Adding of this virtual method will make shift in
// table of virtual methods
};

/**********Inherited class (client app) **********/


class VirtFunctClassChild : public VirtFunctClass {
public:
VirtFunctClassChild() : VirtFunctClass (){}
~VirtFunctClassChild(){};
virtual void DoSomething(){}
};

Let’s imagine that we have classes listed above. The first one is the my.dll member and
the second one is the class defined in the client’s application. Now we will make some
changes in the DLL class i.e. uncomment the following two lines:

//virtual void DoAnything(){}

and

//this->DoAnything();

This will symbolize some changes in the SDK classes.

Now if we won’t recompile the client’s application, then the VirtFunctClassChild class
will not know anything about the new virtual method void DoAnything() that was
added to its base class, so its table of virtual methods (vtable) will contain only the two
methods in the following order:

1. void DoSmth()
2. void DoSomething()
This is not correct because the vtable should be as follows:

1. void DoSmth()
2. void DoAnything()
3. void DoSomething()

Then if after instantiation of the VirtFunctClassChild class, someone will call its
void DoSmth() method, it will cause calling of the new virtual method void
DoAnything(). The base class knows that it should be a second method in the vtable
and so it will try to call it. Obviously this will cause calling of the void DoSomething()
method of the child class as the vtable of the VirtFunctClassChild class contains this
method in the second position and it doesn’t contain the void DoAnything() method at
all !!!!

It is important to note that in case of prohibiting of inheritance from the DLL classes with
adding of new virtual methods, we will still have the same problem because in this case
(just imagine that there was no virtual void DoSomething(){} method in the
VirtFunctClassChild class), the call of the void DoAnything() method from the
base class will cause referencing to the empty memory as there will be only one member
in the vtable.

Now it is obvious that there is a serious problem with adding of the new virtual methods
but there is an ability to solve it in case when virtual methods are used to handle
callbacks.

COM and others

Now it is time to tell that these problems are not only the well know ones and that there
are not only special conventions that can help to solve them but that there are
technologies that allow to avoid most of the DLL backwards compatibility problems. One
of these technologies is Component Object Model (COM). So if you want to forget
problems with your DLLs, use COM or any other appropriate technology.

Let us now return to the task that was given to me. The task I was talking about in the
beginning of the article. It was to solve the backwards compatibility problem of one
product that was a DLL.

I knew about COM and thus my first proposal was to start using COM in this project to
overcome all the issues. This approach was rejected because of the following reasonable
causes:

1. The product already had a COM server on one of its inner layers.
2. It is rather expensive to rewrite a large set of interface classes into COM.
3. As this product is a DLL library, there are a lot of applications that use it, so
they did not want to force their customers to rewrite them.
In other words, what was required was to propose the most costless way to check the
DLL backwards compatibility problem.

Of course, I should say that the main issue of this project was adding the new fields and
virtual callback functions to the interface classes. The first problem is simply solved by
adding a pointer to a structure (that will contain all new fields) to the class declaration
and it is a common approach I have mentioned above. But the problem with the virtual
callback functions was new. Further I propose the costless and effective way to solve it.

Virtual callback methods and inheritance

Let us imagine that we have a DLL with the exported classes someone has to inherit
from, to implement virtual functions to handle callbacks in his application. We want to
make minor changes in the DLL. This change should allow us to painlessly add new
virtual call back functions to the DLL classes in the future. At the same time, we don’t
want to affect applications that use the current DLL version i.e. all we can expect is that
they will be once recompiled with the new DLL version but that should be done for the
last time. So these are target settings and here comes the solution:

We can leave every virtual callback method as it is in the DLL's classes. All we have to
remember is that adding of the new virtual method to any class definition may cause
failure if a client application would not be recompiled with the new DLL version. We
want to avoid this problem. The “Listeners” mechanism can help us here! If virtual
methods, defined and exported from the DLL classes, are used to handle callbacks, then
we can move new virtual methods to the separate interfaces.

Let’s look at the following example:

// Uncomment this definition to get sources after the DLL


// interface extension
//#define DLL_EXAMPLE_MODIFIED

#ifdef DLL_EXPORT
#define DLL_PREFIX __declspec(dllexport)
#else
#define DLL_PREFIX __declspec(dllimport)
#endif

/**********Exported class (dll) **********/


#define CLASS_UIID_DEF static short GetClassUIID(){return 0;}
#define OBJECT_UIID_DEF virtual short
GetObjectUIID(){return this->GetClassUIID();}

//Base interface for all callback handlers extensions


struct DLL_PREFIX ICallBack
{
CLASS_UIID_DEF
OBJECT_UIID_DEF
};
#undef CLASS_UIID_DEF

#define CLASS_UIID_DEF(X) public: static


short GetClassUIID(){return X::GetClassUIID()+1;}

#if defined(DLL_EXAMPLE_MODIFIED)
//Newly added interface extension
struct DLL_PREFIX ICallBack01 : public ICallBack
{
CLASS_UIID_DEF(ICallBack)
OBJECT_UIID_DEF
virtual void DoCallBack01(int event) = 0; //new call back method
};
#endif // defined(DLL_EXAMPLE_MODIFIED)

class DLL_PREFIX CExample{


public:
CExample(){mpHandler = 0;}
virtual ~CExample(){}
virtual void DoCallBack(int event) = 0;
ICallBack * SetCallBackHandler(ICallBack *handler);
void Run();
private:
ICallBack * mpHandler;
};

It is easy to see that all we have to do to prepare the DLL’s classes for extension with the
new virtual methods is to:

1. Add ICallBack * SetCallBackHandler(ICallBack *handler); method;


2. Add the corresponding pointer to each exported class definition;
3. Define 3 macros;
4. Define one common CallBackI interface.

I have added the ICallBack01 interface definition here as an example of how the
CExample class can be extended with the new virtual callback method.

It is obvious that new virtual methods should be added to a new interface. One interface
for each DLL update (i.e. if we want to add several new virtual callback methods to one
class at the same time [for one DLL release], we should add them to one interface).

Each new interface (for one class) should extend the previous interface. In my example I
have only one extension interface ICallBack01. If in the next release we will need to add
a new call back virtual method to this class then we will create a new interface
ICallBack02 that will extend the ICallBack01 interface in the way the ICallBack01
extends the ICallBack.

Macros in the code above are used to define methods required to check the interface
version. For example when we are adding the new interface ICallBack01 with the new
method DoCallBack01, we should check (in the CExample class) if we can call it on the
ICallBack * mpHandler; member. This check should be done in the following way:
if(mpHandler != NULL && mpHandler-
>GetObjectUIID()>=ICallBack01::GetClassUIID()){
((ICallBack01 *) mpHandler)->DoCallBack01(2);
}

All these is made in the case when the new call back interface is added and the call back
is inserted in the CExample class, so it is very easy.

Now you can see that the client’s applications would not be affected by these changes.
The only thing will be required after the first DLL classes’ update (adding of macros,
base interface ICallBack definition, SetCallBackHandler method, ICallBack pointer)
is the client application's recompilation.

Later when someone will add the new callback, he will do it via the new interface
addition (like adding of the ICallBack01 interface in our example). It is obvious that
such change would not affect anything, as the order of virtual methods will not be
changed. So the client applications will work in the old manner. The only thing you
should remember is that client applications will not receive the new callbacks until they
implement the new interface.

It is also important to note that DLL users will still be able to work with it easily. This is
the client’s class example:

#if !defined(DLL_EXAMPLE_MODIFIED)
//Before a new interface is added to the dll.
class CClient : public CExample{
//Everything remains as it was
public:
CClient();
void DoCallBack(int event);
};
#else // !defined(DLL_EXAMPLE_MODIFIED)
//After the interface ICallBack01 was added client may (not
necessarily)
//change his class in the following manner to handle new events
class CClient : public CExample, public ICallBack01{
public:
//The constructor changes
CClient();
//Everything remains as it was
void DoCallBack(int event);
//Add the new virtual method definitions
//(for methods inherited from the ICallBack01)
void DoCallBack01(int event);
};
#endif // defined(DLL_EXAMPLE_MODIFIED)

Note that an example project is attached to this article. It is called Dll_Hell_Solution.

Example Project
In the example workspace, you will find two sub projects:

1. Dll_example - contains the example DLL sources


2. Dll_Client_example - contains the example DLL client sources.

The ./Dll_Hell_Solution/Dll_example/dll_example.h file contains a commented out


DLL_EXAMPLE_MODIFIED macro definition. Uncomment it to emulate the DLL's classes
update.

To check that everything works, perform the following steps one by one:

1. Build the Dll_example and then the Dll_Client_example projects (with


DLL_EXAMPLE_MODIFIED undefined) to emulate the initial situation. Run client
application.
2. Uncomment the DLL_EXAMPLE_MODIFIED macro definition. Build the
Dll_example. Run the client application. Everything will work as it should!
3. Immediately rebuild the Dll_Client_example project. Run the client
application. This will emulate its update that will allow to access the new
functionality of the DLLs classes.

That is all about the example project. Just download and explore it to get all details.

Conclusion

In this article:

• I have represented my research on the DLL backwards compatibility problem.


• I have summarized the list of solutions proposed by different developers.
• I have offered my approach for solving the problem with virtual functions and
inheritance.

I believe that this article will be helpful for anyone who has come across the ‘DLL Hell’.

Overview

Shortcuts for the main chapters in this article:

1. The COM Macro-Architecture Topology


2. Some Theory
3. Some Practice
4. Conclusion
5. Bibliography

Purpose

First, I have to define what does "COM Macro-Architecture" mean.


By "Macro" I do not mean "pre-processor" or "script command". No, by
"Macro" I am referring to a high level of observation, also used in financial
terminology: "Macro economy" and "Micro economy" refer to the global economy and
the country economy respectively, where the country being the delimiter.

Because "COM Architecture" is so huge, I had to split it into two pieces. I defined the
unit of deployment (from a technical point of view we call it an Executable file or
DLL file) as the delimiter of the split. Therefore we have the two following groups:

• Macro-Architecture: as a unit of deployment, we will talk about Client


applications and COM Servers.
• Micro-Architecture: inside a unit of deployment we will talk about running
process, COM apartment, thread concurrency and synchronisation, memory
sharing.

I also used the Term "Topology". By definition, in computer science, Topology is


likely to be the arrangement in which the nodes of a LAN are connected to each
other. Here I wanted to point out how COM applications (Client & Server) are
connected to each other.

This series of articles is just about "Macro-Architecture".

You will learn how to make COM Server and Client Application. You will see how to
deploy and install them (on local machine and remote machine).

This series of articles want to be more practical than theoretical. However, you can
find many good books and articles that go deeper into details in the bibliography
section.

The series is called "COM Macro-Architecture Topology" and this is the main article.
It will give you:

• an overview of the relationships between Client applications and COM


Servers.
• all the instructions you need to build your system (Client application and COM
Server).
• all the process you need to install, configure and uninstall your system.

Here are the links to other articles:

• The COM servers, "COM Macro-Architecture Topology (Servers)". In this


article, you will find out how to build a COM Server.
• The client application, "COM Macro-Architecture Topology (Client)". In this
article, you will learn how to build a Client Application.
• COM IDs & Registry keys in a nutshell. You will find some COM definitions and
brief explanations on the most common COM registry keys.

Who should read this article ?

I have made the assumption that the reader:


• should know the C++ language as all code samples are written in C++.
• should have a basic knowledge of COM, such as Client/Server model, COM
Interface, Interface marshalling (Proxy/Stub).
• should have a basic knowledge of Windows NT/2000 security aspect.
• ought to be interested more in COM architecture than in development aspect.

For this reason, I classified this article as an Intermediate level.

However, beginners can still read this series and used the practical section like a
tutorial. From my own experience, I have started to learn COM by practice and then
books helped me to put it down and understand the theory.

I do remind to COM developers who are reading this series that the main subject is
COM architecture. I am not going to explain or compare the different COM helper
tools, C++ libraries (e.g. ATL) or even C++ design aspects (e.g. smart pointers or
wrapper classes) available to develop COM Client applications or Servers.

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).

Downloading

Demo files - You will find the necessary tools (Executables, DLLs,
Registry configuration and Batch files) to try the different cases. Download
demo project - 127 KB
Source files - You will find all the source code for making the 3 different
COM Servers (Executable, 2DLLs and 1DLL) and the Client Application.
Download source - 70 KB
The project's structure on my computer is like that:
All the Executable and DLL files are output in the \Implementation\Bin directory.

Deployment considerations
Be careful when deploying the components described in this article.
Take into account these points:

• In order to install them you need to have the Administrator rights (Client and
Server machine).
• Allow the Server to use the identity of the Interactive user or a user's
account with the Interactive rights (so dialog boxes could be showed).
• When calling the Server remotely, be sure that the User associated to your
Server is locally logged-on onto the server machine as the Interactive user, so
that the Dialog boxes can appear.
• Using the "Terminal Services Client" does not give you the Interactive user
account, although your account has the Interactive rights (For further
information read [Bi6]).
The Interactive User is the user currently logged-on the Server machine.

Limitations

Although COM is available in many operating systems, I have just considered the
Windows implementation. This means that some aspects of COM could have different
restrictions based on their operating system implementation (e.g. the Security, the
COM information storage, etc.) .

Some Theory

COM & Infrastructure


Before moving on to practice and building COM Servers you have to understand how
the information you will provide to COM will be used to bind a Client Application to a
COM Server.
Here, I will just talk about 2 COM Infrastructure points:

• The Activation mechanism.


• The Security.

Look at the article "COM IDs & Registry keys in a nutshell" for information on the
registry and COM vocabulary.

The Activation mechanism

The activation mechanism is the process used by COM to locate, load and launch a
COM Server (DLL or Executable). The result, if all goes to plan, will be to
instantiate a COM object or COM class object. As you can understand,
Activation is a dynamic action and it occurs at runtime.

COM proposed 3 activation models to bring COM objects into memory (see [Bi1],
chapter 5 and [Bi11], chapter 3):

• Clients can ask COM to bind to the class object or class factory of a given
class (using CoGetClassObject()).

• Clients can ask COM to create new instances of a class based on a CLSID
(using CoCreateInstance/Ex()).

• Clients can ask COM to bring a persistent object to life based on the
persistent state of the object (using CoGetInstanceFromFile()).

You can also define a custom way to activate your COM object based on one of these
3 basic activation models.

All these activation models use the services of the COM Service Control Manager
(SCM). The COM SCM is not the same as the Windows NT or Windows 2000 SCM
(used to start logon-independent processes known as Windows Services). Every host
that supports COM has a COM SCM. His role is to activate the COM objects on a
particular machine (locally or remotely).

Static/Dynamic aspect of a COM server


At this stage you should look at your COM Server from its Static and Dynamic
aspect:

• The static aspect of a COM Server is its package: it can be built as an


Executable or as a Dynamic Link Library (DLL) file.

• The dynamic aspect of a COM Server relates to the Server categories


defined by COM at runtime: In-process and Out-of-process.
The figure below expresses that idea:

From a location point of view, COM Servers that reside on the client machine are
called "local servers" and the ones that reside on a remote machine are called
"remote servers".
This figure points out 3 results:

• A COM Server packaged as an Executable file will never run as In-process


server. It will always run as an Out-of-process server (locally or remotely).

• A COM Server packaged as a DLL file will run either as an In-process or as


an Out-of-process server (locally or remotely).
Notice that a DLL COM Server always needs a process to run into. As In-
process server it will be loaded into the Client process space and as Out-
of-process it should be loaded into a Surrogate process space.

• Only COM Servers use as Out-of-process servers can be called on remote


machine.

Every route leads to a COM Server...


When deploying and installing your COM Server you must give some information to
COM, such as where your component will be (physically), which COM class object(s)
your component provides and so on.
All these information will allow COM library to answer to Client's requests about
launching and creating COM Server's objects.
Under Windows NT 4.0, COM stores these information on its local configuration
database also called the Registry. Under Windows 2000 (or v5.0), COM uses a
distributed secured database, i.e. the Active Directory.
Generally, all COM servers should support self-registration. This means that:

• the Executable COM Server must check the command line for these
switches:
o /RegServer or -RegServer: will insert keys in the registry and register
its class factories by calling CoRegisterClassObject().
o /UnregServer or -UnregServer: will suppress keys in the registry and
unregister its class factories by calling CoRevokeClassObject().

All these 4 switches are case insensitive.

• the DLL COM Server must export these 2 functions:


o DllRegisterServer(): will insert keys in the registry.
o DllUnregisterServer(): will suppress keys in the registry.

To register a DLL COM Server, you can use the utility REGSVR32.EXE in the
Win32 SDK. This application will call these 2 exported functions to register or
unregister the DLL COM server.

To illustrate the work done by COM in order to bind the Client application and the
COM Server together, let assume the client code is like this:
ISampleClass *psc = NULL;
HRESULT hr = CoGetClassObject(GUID1, CLSTX_ALL,
0, IID_IClassFactory,
(void**)&psc);

• CoGetClassObject() provides us an IClassFactory interface pointer on a


class object (i.e. a class factory) associated with the specified class ID:
GUID1.
• CoGetClassObject() takes as its second parameter a bitmask value that
specifies the desired context the client wants to activate the COM Server: In-
process, Out-of-process, locally or remotely.
CLSTX_ALL is a pre-processor macro defined like this:
• #define CLSCTX_ALL (CLSCTX_INPROC_SERVER| \
• CLSCTX_INPROC_HANDLER| \
• CLSCTX_LOCAL_SERVER| \
• CLSCTX_REMOTE_SERVER)

These values are defined in the standard enumeration of CLSCTX as:

typedef enum tagCLSCTX


{ CLSCTX_INPROC_SERVER = 0x1,
CLSCTX_INPROC_HANDLER = 0x2,
CLSCTX_LOCAL_SERVER = 0x4,
CLSCTX_REMOTE_SERVER = 0x10,
...
}CLSCTX;
You can find as well 2 other shortcut macros in the SDK header file
(objbase.h):

#define CLSCTX_INPROC (CLSCTX_INPROC_SERVER|\


CLSCTX_INPROC_HANDLER)

#define CLSCTX_SERVER (CLSCTX_INPROC_SERVER|\


CLSCTX_LOCAL_SERVER|\
CLSCTX_REMOTE_SERVER)

It is worth to know that environments such as Visual Basic and Visual Java
always use CLSTX_ALL, indicating that any available implementation will be
good ([Bi11], chapter 3).

Here is the Activation chain used by COM in order to locate, load and launch the COM
Server and return a interface pointer to the client (look at [Bi1], chapter 6, [Bi3],
chapter 5 and [Bi11], chapter 6):

In the figure above, a yellow rectangle indicates a registry key or subkey under
[HKEY_CLASSES_ROOT\CLSID] and a green rectangle indicates one under
[HKEY_CLASSES_ROOT\AppID].
For simplicity, I have not taken into account the security aspect. So, let see what
happens:
1. The client calls CoGetClassObject() which gives control to the COM Service
Control Manager (SCM). Because the third parameter of CoGetClassObject()
is not set with a host name, the SCM will first look in the local registry for the
key:
2. [HKCR\CLSID\{GUID1}]
o Notice that in Windows 2000, the SCM will look first under the key
[HKEY_CURRENT_USER] and then [HKEY_CLASSES_ROOT] (aliased as
[HKCR]).
o If the key does not exist locally, the SCM will then try to find
information on the Windows 2000 Active Directory and install the
component locally.
o If the key is not available, the call will fail with the HRESULT code
REGDB_E_CLASSNOTREG: CLSID is not properly registered.
This code error might also indicate that the value you specified in the
second parameter of CoGetClassObject() is not in the registry.

COM SCM is looking for an In-process

2. At this stage, the class ID GUID1 is available the SCM will then look for the
subkey:
3. [HKCR\CLSID\{GUID1}\InprocServer32]
@="C:\myserver.dll"

If the key is present, it should contain a valid path with a DLL file name. COM
will load the DLL into the client process space, call DllGetClassObject() to
get the IClassFactory interface pointer and return it to the client.

4. If this key is not present, then the SCM will look for the subkey:
5. [HKCR\CLSID\{GUID1}\InprocHandler32]
@="C:\myserver.dll"

Again, if the key is present, it should contain a valid path with a DLL file
name. COM will load the DLL into the client process space, call
DllGetClassObject() to get the IClassFactory interface pointer and return
it to the client.

If neither In-process subkey is available, the SCM will look for an Out-of-process
activation. After this stage, the client will always get back an interface pointer on a
proxy.

COM SCM is looking for an Out-of-process locally

4. The SCM is now looking on its cache to see whether the requested COM class
object (class factory) is currently registered. If so, the SCM will return a
marshalled interface pointer to the client.
The COM server will register its class factories with the SCM using the helper
function CoRegisterClassObject().

If the server uses the flag REGCLS_SINGLEUSE, then the SCM will not put the
class factory as public level and so will not be available for subsequent
activation request. In fact, for subsequent activation calls, the SCM will start a
new process using the step #5 and followings.
5. If the class factory is not registered in its cache (or has been removed from
the public view of the SCM), this means that there is not any Out-of-process
currently running and providing this class factory as public level.
Under Windows NT 4.0 or 2000, COM supports 3 process models for creating
servers:
o NT Services.
o Normal processes.
o Surrogate processes. They are used to host DLL servers:
 locally it provides fault isolation and security context.
 remotely it provides remote activation and distributed
architecture.

Whatever the process model is used to create a server process, the server will
have 120 seconds (or 30 seconds under Windows NT service Pack 2 or earlier)
to register the requested class factory. If the server process fails to do so in
time, the SCM will fail the client's activation request ([Bi11], chapter 6).

When creating a server process, the SCM will first look for a local service. It
will try to get an AppID:

o by looking under the named value:


o [HKCR\CLSID\{GUID1}]
o AppID={GUID2}

If it finds a value, e.g. GUID2, it will look for the named value under
the AppID root:

[HKCR\AppID\{GUID2}]
LocalService="MyService"

o If it can not find the AppID value GUID2 under the key
[HKCR\CLSID\{GUID1}] it takes the class ID value itself GUID1 as
an AppID and look under the AppID root:
o [HKCR\AppID\{GUID1}]
o LocalService="MyService"

In the figure above, GUID1 and GUID2 could have the same value, but it is
preferable if it is not the case.

GUID1 represents the class ID of the COM class factory whilst GUID2
represents the COM Application ID used by the COM class factory.

If a value exists, the (COM) SCM will ask to the Windows SCM to start the
service MyService. The service will register its class factories with the SCM
using the helper function CoRegisterClassObject(). Now, COM can query
for the class factory GUID1 and ask it to return a pointer for the
IClassFactory interface, which is to be marshalled back to the client.
6. If the SCM can not find this named value, then it will try to start a normal
process, so it will look for the subkey:
7. [HKCR\CLSID\{GUID1}\LocalServer32]
8. @="C:\myserver.exe"

If a value exists, the SCM will try to launch the local server by using API
functions such as CreateProcess() or CreateProcessAsUser().

After this step, the SCM could no start neither a Local service nor a Local Server,
so it would try to create an Out-of-process remotely.

COM SCM is looking for an Out-of-process remotely

7. If the SCM can not find a local server, it will look for the named value:
8. [HKCR\AppID\{GUID2}]
RemoteServerName="\\MyRemoteComputer"

If this value is available, the activation request will be forwarded to the SCM
on the designated host machine. It is worth noting that although the client
application will only use the CLSCTX_LOCAL_SERVER flag when querying the
activation, the request will be forwarded to the remote machine if no local
servers are registered.

The remote SCM will, following steps #4 through #6 (i.e. steps #7.c through
#7.e), try to find and launch a remote server that supports the requested
class factory. If it will succeed it will return a pointer for the IClassFactory
interface, which is to be marshalled back to the client machine.

f. If neither a Local service nor a Local Server could be launched, then


the remote SCM will check for the named value on the remote
machine:
g. [HKCR\AppID\{GUID2}]
h. DllSurrogate="C:\MySurrogate.exe"

If this named value exists, the remote SCM will start the Surrogate
process (e.g. MYSURROGATE.EXE) and ask it to load the DLL COM
server designated under the subkey:

[HKCR\CLSID\{GUID1}\InprocServer32]
@="C:\myserver.dll"

then it will ask it to return a pointer for the IClassFactory interface,


which is to be marshalled back to the client machine.

If the named value DllSurrogate is empty:

[HKCR\AppID\{GUID2}]
DllSurrogate=""

then the remote SCM will start the default Surrogate process (called
DLLHOST.EXE) and ask it to load the DLL COM server.
Maybe, you are now wondering "Why the SCM will not try to create a Surrogate
process after step #6 ?"
As I wrote before, there are three process models. Until step #6, the SCM have tried
to create the first two: NT service and normal process. The last process model is
Surrogate process and it can be used locally to load and activate DLL COM Server as
Out-of-process. Nevertheless to activate this kind of process locally the client
application must explicitly ask it to COM by using the CLSCTX_LOCAL_SERVER flag only
instead of the CLSCTX_ALL flag.

"Why ? (again)"
In order to use the Surrogate process the SCM still need to have the DLL file name
under the subkey [HKCR\CLSID\{GUID1}\InprocServer32]. That is fine, but
when the client specified CLSCTX_ALL, it is specified CLSCTX_INPROC_SERVER as
the same time, the SCM will look first for the subkey
[HKCR\CLSID\{GUID1}\InprocServer32] and if it exists then it will launch the
COM Server as an In-process server into the client space. In this situation, the SCM
will never reach the named value DllSurrogate.

Some remarks:

• if on the remote machine the named value RemoteServerName exists the


remote SCM will not consider it when it will be executing a remote activation.
This means that you can not reroute the remote activation into another
machine and so on.
• The third parameter of CoGetClassObject() takes as its third parameter a
pointer into a COSERVERINFO structure. You can use this structure to explicitly
set the remote machine name.
When this parameter is NULL, the SCM will look for the value named
RemoteServerName in the registry.

The Security

In general, you use a security infrastructure to protect your private data, or any
resource, against wrong hands.

At the beginning, COM did not have a specific security infrastructure to protect the
client or the server application. The release of Windows NT 4.0 introduced processes
and threads security levels and permitted server processes to be accessed remotely.
On the Windows platforms implementation (particularly NT 4.0 and 2000), the COM
security infrastructure is built by using:

• the operating system features,


• and the RPC security infrastructure (see [Bi11], chapter 6).

Before to go deeper in the COM Security Infrastructure, it is useful to have a brief


introduction to Windows NT/2000 Security.

The Windows NT/2000 security infrastructure


Windows NT/2000 has a support for security based on user accounts, groups, and
domains. Windows 95/98 security support is lower than Windows NT/2000. In this
section I will talk only about Windows NT/2000 security aspect, for specific Windows
95/98 implementations please refer to the platform documentation.
For the rest of this section, the word Windows will refer as Windows NT/2000 when
there is no need to distinguish between them.

Windows focuses on 2 areas ([Bi12], chapter 18):

• Authentication. Authentication try to answer to the question "Are you really


who you claim to be ?" . One of the most use of authentication in Windows is
when a user try to log on.
• Access control. Access control in Windows allows or limits certain users
access to specific resources or services.

In order to log on to Windows, the user must have a user account. Each user and
user group is identified by a unique value called a Security IDenfier (SID). Once the
user logged on successfully, Windows associated to the user an access token. This
access token contains information related to the user such as the user SID, the list of
the groups the user belong to and other privileges.

Every time a process is started under the session opened by the user, Windows (by
default) associated the user's access token to it. Then, when the process wants to
access a system object (e.g. a thread, a mutex, or a file) Windows compares the
security information attached to it and the access token of the process. After
comparison, Windows grants or denies the access.

Every system object can contain a Security Descriptor that is composed of:

• the owner SID.


• the primary group SID to which the user belong to.
• the System Access Control List (SACL), for audit messages
• the Discretionary Access Control List (DACL), for the control access.

Both types of access control lists (SACL and DACL) are lists of Access Control
Entries (ACEs). An ACE contains the SID of a user or group of user and some type of
access (e.g. Read, Write, Execute, etc.) that is either granted to (access-allowed
ACE) or denied (access-denied ACE).

I think it is time to have a picture:


The ACE-Denied entries are always placed in first positions.
In Windows, the job of authenticating a user is done by a Security Support
Provider (SSP). The Win32 API defines a security API called the Security Support
Provider Interface (SSPI). A SSP is a DLL that implements the SSPI and generally
contains common authentication and cryptographic data schemes.
In Windows NT 4.0, there was only one SSP deliver with the operating system: the
NT LAN Manager Security Support Provider (NTLM SSP).
In Windows 2000, there are more:

• NTLM SSP, as in Windows NT 4.0 .


• Kerberos is a more sophisticated authentication protocol than NTLM SSP.
• Snego for Simple GSS-API Negotiation Mechanism. In fact, Snego is not
really a SSP itself. It is protocol used to negotiate between real SSPs. It
chooses Kerberos if it supported by both sides (client and server), otherwise
it chooses NTLM.

This is an open architecture as third parties can develop new SSP and install them
without interfering with the existing ones. An application that already uses an SSP
would require few or maybe no modification with a new SSP.

The COM security infrastructure


COM distinguishes between 4 fundamental aspects of security ([Bi4]):

• Launch security: Which security users are allowed to create a new object in
a new process ?
=> Protecting the server machine
• Access security: Which security users are allowed to call an object ?
=> Protecting the object
• Identity: What is the security user of the object itself ?
=> Controlling the object
• Connection policy: Integrity- can messages be altered? Privacy- can
messages be intercepted by others? Authentication- can the object find out or
even assume the identity of the caller?
=> Protecting the data

These aspects can be set at different levels, i.e. the security grain:

• Machine-wide security level,


• Application-wide security level (or per-Server basis),
• per-Class basis
• per-Interface basis
• per-Method call basis

Nota: when talking about the last 3 levels (per-class, per-Interface, and per-method
call basis) they are generally called fined-grain security.

You can set the security information in 2 complementary ways:

• by Configuration. The security informations are stored in the registry. You


can use DCOMCNFG.EXE to set these informations. Many books explain how
to use this tool, such as [Bi3], chapter 5, [Bi12], chapter 18, [Bi10], chapter
10, and the MSDN article [Bi5].
• by Programmation. You can use the COM Security API to set security
information.

By Configuration you can not set all the security aspect information, and it is the
same with Programmation. For this reason, they are complementary.
Settings on the registry are also called the default settings.

If you set security information on more than one level, what happens ? In fact,
according to the security levels, you can override the settings of the lower level by a
higher level. Here is a figure pointing out the security level ordered:

An per-Method security level override a per-Interface security level, an so on.

And to terminate this high-level view of COM security model, some of these security
informations can be set only on the client side, only on the server side, or in both
sides.

All these parameters and combinations of settings make COM Security a huge
subject by itself, but if you are developing distributed applications you must look at
them.

Some Practice

The COM Macro-Topology

After finishing a COM component, we have to deploy, install and configure it. There
could be many situations to deploy our components.
By deployment I mean:

• What kind of unit (Executable or DLL) will you use for your Client or your
Server ?
• Where do you copy the Client unit and the Server unit ?

We can define 3 criterias to determine the COM deployment Architecture, or like I


call it the "COM Macro-Architecture":

• The location (2):


o Local means that the Client and the Server are in the same machine.
However maybe not in the same process.
o Remote means that the Client and the Server are not in the same
machine.
• The client (2): an Executable or a DLL.
• The COM server (3): an executable, DLLs or a DLL merged.
o an Executable. You have to be aware about DCOM Application
security and the Proxy/Stub DLL.
o DLLs. This case corresponds to 2 DLLs : one for the COM Server itself
and one more for the Proxy/Stub DLL.
o DLL with Proxy/Stub merged. only one DLL.

So, with those 3 criterias there are 12 cases (2 x 2 x 3).

The 12 cases

We will see case by case how to make and install our COM applications: Client &
Server.

Here are the 12 cases:

COM Macro-Architecture Location Client Server


Topology's cases

Local: Executable: Executable:


DLLs:
Remote: DLL:
DLL (merged):

Case 1

Case 2

Case 3

Case 4

Case 5

Case 6

Case 7

Case 8

Case 9

Case 10

Case 11

Case 12

Picture legends
Here are the picture symbol's legend used in the 12 cases:

Case 1
Here are the steps to build this case:

1. Create the COM Server as an Executable (Look at the "COM Macro-


Architecture Topology (Servers)" article).
o Go through step 1 to 6.
2. Create the Client application as an Executable (Look at the "COM Macro-
Architecture Topology - Clients" article).
3. Copy the server files (Executable and Proxy/Stub DLL) and client file to the
target machine.
4. Register the Server using the "Local consideration".
o Step 7.
5. Launch the Client application and use the COM Server.

The results are showed in the picture below:

Legend

Some remarks about this deployment architecture:

• Process relation:
o The client application and the COM server objects will never run in the
same process.

• The cardinality of the relation between instances (processes) of a COM Server


and a Client application might be:
o 1 to 1: Each client application could have its own COM Server.
o 1 to n: A COM Server could be shared between many client
applications.
o n to 1: A client application could have many COM Servers.

This depend on the initialisation of the Class Factories in the COM Server
(using the COM API function CoRegisterClassObject()) but this is out of the
scope of this series.

• COM Security aspect


o You can use DCOMCNFG.EXE to set the different security details
(Access, Launch, User, etc.).

• COM Server Executable can be developed as a Windows Service.

When you want to uninstall the system (Client application and COM Server):

1. Remove the Client application by simply deleting the Client executable file.
2. Unregister the COM Server following the instructions in the article "COM
Macro-Architecture Topology (Servers)".
o Step 9 using the "Local consideration".
3. Remove the COM Server files by simply deleting the COM Server executable
file and the Proxy/Stub DLL file.

Case 2

For this deployment, you can have 2 different configurations:

• the COM DLL server loaded into the client process. COM refers as this server
as an in-process server.
• the COM DLL server running outside the client process but in the same
machine. COM refers as this server as a (local) out-of-process server.

In-process Server
Here are the steps to build this case:

1. Create the COM Server as 2 DLLs (Look at the "COM Macro-Architecture


Topology (Servers)" article).
o Go through step 1 to 6.
2. Create the Client application as an Executable (Look at the "COM Macro-
Architecture Topology - Clients" article).
3. Copy the server files (COM DLL Server and Proxy/Stub DLL) and client file to
the target machine.
4. Register the Server using the "Local In-process consideration".
o Step 7.
5. Launch the Client application and use the COM DLL Server.

The results are showed in the picture below:


Legend

Some remarks about this deployment architecture:

• Process relation:
o The client application and the COM DLL server will run in the same
process.

• The cardinality of the relation between instances (processes) of a COM DLL


Server and a Client application is:
o 1 to 1: Each client application has its own COM DLL Server. In reality,
the instance of the DLL will be shared between clients' processes. This
means that every global data will be shared as well, so they should be
safe multithreaded. Nevertheless, every runtime instances (heap and
stack) will be part of the client's memory space process.

• COM Security aspect


o A DLL can never run without a process. All the security aspects will be
managed by this process, i.e. the client process.

• Other:
o The client code can access the COM object indirectly (links 1.a, 1.b and
1.c) if they are in different COM apartments or directly (link 2) if they
are in the same, but this matter is out of the scope of this series.
When you want to uninstall the system (Client application and COM DLL Server):

1. Remove the Client application by simply deleting the Client executable file.
2. Unregister the COM DLL Server following the instructions in the article "COM
Macro-Architecture Topology (Servers)".
o Step 9 using the "Local In-process consideration".
3. Remove the COM Server files by simply deleting the COM DLL Server file and
the Proxy/Stub DLL file.

Out-of-process Server
Here are the steps to build this case:

1. Create the COM Server as 2 DLLs (Look at the "COM Macro-Architecture


Topology (Servers)" article).
o Go through step 1 to 6.
2. Create the Client application as an Executable (Look at the "COM Macro-
Architecture Topology - Clients" article).
3. Copy the server files (COM DLL Server and Proxy/Stub DLL) and client file to
the target machine.
4. Register the Server using the "Local Surrogate consideration".
o Step 7.
5. Launch the Client application and use the COM DLL Server.

The results are showed in the picture below:

Legend

Some remarks about this deployment architecture:

• Process relation:
o The client application and the COM DLL server will run in the same
process.

• The cardinality of the relation between instances (processes) of a COM DLL


Server and a Client application is:
o 1 to 1: Each client application could have its own COM Server.
o 1 to n: A COM Server could be shared between many client
applications.
o n to 1: A client application could have many COM Servers.

The fact to have many instances of the default Surrogate application


(DLLHOST.EXE) running in the same machine and then of your COM DLL
Server depends on the security values set (see at The activation mechanism).

• COM Security aspect


o You can use DCOMCNFG.EXE to set the different security details
(Access, Launch, User, etc.).

• Other:
o If in the client code you use flags such as
CLSCTX_INPROC_SERVER, CLSCTX_INPROC_HANDLER,
CLSCTX_SERVER or CLSCTX_ALL the in-process server will be
launched first by COM.

When you want to uninstall the system (Client application and COM DLL Server):

1. Remove the Client application by simply deleting the Client executable file.
2. Unregister the COM DLL Server following the instructions in the article "COM
Macro-Architecture Topology (Servers)".
o Step 9 using the "Local Surrogate consideration".
3. Remove the COM Server files by simply deleting the COM DLL Server file and
the Proxy/Stub DLL file.

Case 3
For this deployment, you can have 2 different configurations:

• the COM DLL server loaded into the client process. COM refers as this server
as an in-process server.
• the COM DLL server running outside the client process but in the same
machine. COM refers as this server as a (local) out-of-process server.

In-process Server
Here are the steps to build this case:

1. Create the COM Server as 1 DLL (Look at the "COM Macro-Architecture


Topology (Servers)" article).
o Go through step 1 to 6.
2. Create the Client application as an Executable (Look at the "COM Macro-
Architecture Topology - Clients" article).
3. Copy the server file (this time the COM DLL Server is merged with the
Proxy/Stub code) and the client file to the target machine.
4. Register the Server using the "Local In-process consideration".
o Step 7.
5. Launch the Client application and use the COM DLL Server.

The results are showed in the picture below:

Legend

Some remarks about this deployment architecture:

• Process relation:
o The client application and the COM DLL server will run in the same
process.

• The cardinality of the relation between instances (processes) of a COM DLL


Server and a Client application is:
o 1 to 1: Each client application has its own COM DLL Server. In reality,
the instance of the DLL will be shared between clients' processes. This
means that every global data will be shared as well, so they should be
safe multithreaded. Nevertheless, every runtime instances (heap and
stack) will be part of the client's memory space process.

• COM Security aspect


o A DLL can never run without a process. All the security aspects will be
managed by this process, i.e. the client process.

• Other:
o The client code can access the COM object indirectly (links 1.a, 1.b and
1.c) if they are in different COM apartments or directly (link 2) if they
are in the same, but this matter is out of the scope of this series.

When you want to uninstall the system (Client application and COM DLL Server):

1. Remove the Client application by simply deleting the Client executable file.
2. Unregister the COM DLL Server following the instructions in the article "COM
Macro-Architecture Topology (Servers)".
o Step 9 using the "Local In-process consideration".
3. Remove the COM DLL Server file by simply deleting the file.

Out-of-process Server
Here are the steps to build this case:

1. Create the COM Server as 1 DLL (Look at the "COM Macro-Architecture


Topology (Servers)" article).
o Go through step 1 to 6.
2. Create the Client application as an Executable (Look at the "COM Macro-
Architecture Topology - Clients" article).
3. Copy the server file (this time the COM DLL Server is merged with the
Proxy/Stub code) and the client file to the target machine.
4. Register the Server using the "Local Surrogate consideration".
o Step 7.
5. Launch the Client application and use the COM DLL Server.

The results are showed in the picture below:


Legend

Some remarks about this deployment architecture:

• Process relation:
o The client application and the COM DLL server will run in the same
process.

• The cardinality of the relation between instances (processes) of a COM DLL


Server and a Client application is:
o 1 to 1: Each client application could have its own COM Server.
o 1 to n: A COM Server could be shared between many client
applications.
o n to 1: A client application could have many COM Servers.

The fact to have many instances of the default Surrogate application


(DLLHOST.EXE) running in the same machine and then of your COM DLL
Server depends on the security values set (see at The activation mechanism).

• COM Security aspect


o You can use DCOMCNFG.EXE to set the different security details
(Access, Launch, User, etc.).

• Other:
o In that configuration, we have lost the benefit to have only one DLL.
Because we want our component to run out-of-process, the client
application still needs the marshalling code (Proxy/Stub code) in its
space process. Therefore, you have to build and install the Proxy/Stub
DLL.
o If in the client code you use flags such as
CLSCTX_INPROC_SERVER, CLSCTX_INPROC_HANDLER,
CLSCTX_SERVER or CLSCTX_ALL the in-process server will be
launched first by COM.

When you want to uninstall the system (Client application and COM DLL Server):

1. Remove the Client application by simply deleting the Client executable file.
2. Unregister the COM DLL Server following the instructions in the article "COM
Macro-Architecture Topology (Servers)".
o Step 9 using the "Local Surrogate consideration".
3. Remove the COM DLL Server file by simply deleting the file.

Case 4, Case 5, Case 6

Here for deployment sake I have made the difference between Client DLL and
Executable, but there are "no differences" from a functional point a view. I mean the
Client code to write is the same in both cases.
However, with in-process COM Server (DLL) you have to take in account the COM
Apartment consideration, but this is outbound the subject of this article.
Obviously, if your client is also a COM server, you have to apply the same rules for
the COM server explained before. So:

• For the case 4 refer to the Case 1,


• For the case 5 refer to the Case 2,
• For the case 6 refer to the Case 3.

Case 7
Here are the steps to build this case:

1. Create the COM Server as an Executable (Look at the "COM Macro-


Architecture Topology (Servers)" article).
o Go through step 1 to 6.
2. Create the Client application as an Executable (Look at the "COM Macro-
Architecture Topology - Clients" article).
3. Copy the server files (Executable and Proxy/Stub DLL) to the server machine
(see below as HOST B).
4. Copy the server Proxy/Stub DLL and the client application to the client
machine (see below as HOST A).
5. Register the Server using the "Remote consideration". There are 2
registrations: one for the Client machine and one for the Server machine.
o Step 7.
6. Configure the DCOM security aspect of the Server.
o Step 8.
7. Launch the Client application and use the COM Server.

The results are showed in the picture below:

Legend

Some remarks about this deployment architecture:

• Process relation:
o The client application and the COM server objects will never run on the
same machine and so in the same process.

• The cardinality of the relation between instances (processes) of a COM Server


and a Client application might be:
o 1 to 1: Each client application could have its own COM Server.
o 1 to n: A COM Server could be shared between many client
applications.
o n to 1: A client application could have many COM Servers.

This depend on the initialisation of the Class Factories in the COM Server
(using the COM API function CoRegisterClassObject()) but this is out of the
scope of this series.

• COM Security aspect


o You can use DCOMCNFG.EXE to set the different security details
(Access, Launch, User, etc.).
• DCOM aspect
o You have to enable DCOM on both machines: Client and Server. You
can use DCOMCNFG.EXE (on the Default Properties tab).

• If you have more than one server machine (many HOST B), you can configure
client machines to use different server machine by simply changing the
"RemoteServerName" value (under the AppID key) in the registry.
You can as well use, in your client application code, the API function
CoCreateInstanceEx() to determine the machine on which the object will be
instantiated.

• COM Server Executable can be developed as a Windows Service.

When you want to uninstall the system (Client application and COM Server):

1. Unregister the COM Server following the instructions in the article "COM
Macro-Architecture Topology (Servers)". There are 2 unregistrations: one for
the Client machine and one for the Server machine.
o Step 9 using the "Remote consideration".
2. On the Client machine, remove the Client application and the Server
Proxy/Stub DLL by simply deleting these files.
3. On the Server machine, remove the COM Server files by simply deleting the
COM Server executable file and the Proxy/Stub DLL file.

Case 8
Here are the steps to build this case:

1. Create the COM Server as 2 DLLs (Look at the "COM Macro-Architecture


Topology (Servers)" article).
o Go through step 1 to 6.
2. Create the Client application as an Executable (Look at the "COM Macro-
Architecture Topology - Clients" article).
3. Copy the server files (COM DLL Server and Proxy/Stub DLL) to the server
machine (see below as HOST B).
4. Copy the server Proxy/Stub DLL and the client application to the client
machine (see below as HOST A).
5. Register the Server using the "Remote consideration". There are 2
registrations: one for the Client machine and one for the Server machine.
o Step 7.
6. Configure the DCOM security aspect of the Server.
o Step 8.
7. Launch the Client application and use the COM Server.

The results are showed in the picture below:


Legend

Some remarks about this deployment architecture:

• Process relation:
o The client application and the COM server objects will never run on the
same machine and so in the same process.

• The cardinality of the relation between instances (processes) of a COM Server


and a Client application might be:
o 1 to 1: Each client application could have its own COM Server.
o 1 to n: A COM Server could be shared between many client
applications.
o n to 1: A client application could have many COM Servers.

The fact to have many instances of the default Surrogate application


(DLLHOST.EXE) running in the same machine and then of your COM DLL
Server depends on the security values set (see at The activation mechanism).

• COM Security aspect


o You can use DCOMCNFG.EXE to set the different security details
(Access, Launch, User, etc.).

• DCOM aspect
o You have to enable DCOM on both machines: Client and Server. You
can use DCOMCNFG.EXE (on the Default Properties tab).

• If you have more than one server machine (many HOST B), you can configure
client machines to use different server machine by simply changing the
"RemoteServerName" value (under the AppID key) in the registry.
You can as well use, in your client application code, the API function
CoCreateInstanceEx() to determine the machine on which the object will
be instantiated.

When you want to uninstall the system (Client application and COM DLL Server):

1. Unregister the COM DLL Server following the instructions in the article "COM
Macro-Architecture Topology (Servers)". There are 2 unregistrations: one for
the Client machine and one for the Server machine.
o Step 9 using the "Remote consideration".
2. On the Client machine, remove the Client application and the Server
Proxy/Stub DLL by simply deleting these files.
3. On the Server machine, remove the COM Server files by simply deleting the
COM DLL Server file and the Proxy/Stub DLL file.

Case 9

Here are the steps to build this case:

1. Create the COM Server as 1 DLL (Look at the "COM Macro-Architecture


Topology (Servers)" article).
o Go through step 1 to 6.
2. Create the Client application as an Executable (Look at the "COM Macro-
Architecture Topology - Clients" article).
3. Copy the server file (this time the COM DLL Server is merged with the
Proxy/Stub code) to the server machine (see below as HOST B).
4. Copy the server Proxy/Stub DLL and the client application to the client
machine (see below as HOST A).
5. Register the Server using the "Remote consideration". There are 2
registrations: one for the Client machine and one for the Server machine.
o Step 7.
6. Configure the DCOM security aspect of the Server.
o Step 8.
7. Launch the Client application and use the COM Server.

The results are showed in the picture below:


Legend

Some remarks about this deployment architecture:

• Process relation:
o The client application and the COM server objects will never run on the
same machine and so in the same process.

• The cardinality of the relation between instances (processes) of a COM Server


and a Client application might be:
o 1 to 1: Each client application could have its own COM Server.
o 1 to n: A COM Server could be shared between many client
applications.
o n to 1: A client application could have many COM Servers.

The fact to have many instances of the default Surrogate application


(DLLHOST.EXE) running in the same machine and then of your COM DLL
Server depends on the security values set (see at The activation mechanism).

• COM Security aspect


o You can use DCOMCNFG.EXE to set the different security details
(Access, Launch, User, etc.).

• DCOM aspect
o You have to enable DCOM on both machines: Client and Server. You
can use DCOMCNFG.EXE (on the Default Properties tab).

• If you have more than one server machine (many HOST B), you can configure
client machines to use different server machine by simply changing the
"RemoteServerName" value (under the AppID key) in the registry.
You can as well use, in your client application code, the API function
CoCreateInstanceEx() to determine the machine on which the object will
be instantiated.

When you want to uninstall the system (Client application and COM DLL Server):

1. Unregister the COM DLL Server following the instructions in the article "COM
Macro-Architecture Topology (Servers)". There are 2 unregistrations: one for
the Client machine and one for the Server machine.
o Step 9 using the "Remote consideration".
2. On the Client machine, remove the Client application and the Server
Proxy/Stub DLL by simply deleting these files.
3. On the Server machine, remove the COM DLL Server file by simply deleting
the file.

Case 10, Case 11, Case 12

Same explanation as for Case 4, Case 5 and Case 6.


The corresponding cases are :

• For the case 10 refer to the Case 7,


• For the case 11 refer to the Case 8,
• For the case 12 refer to the Case 9.

Conclusion

A final question might be: "Should I build in-process servers or out-of-process


servers ?" (see [Bi10]).
In reality it will depend of your requirements and your constraints, but I think now
you have enough information to answer that question by yourself. Main ideas to keep
in mind is that:

• A COM Server as an Executable will never be used as an in-process Server.


• A COM Server as a DLL could be used as both: in-process and out-of-process
Server.
• In-process vs. Out-of-process:
o You can have performance by using in-process server (COM object
ought to share the same apartment of the Client code).
o You can have robustness by using out-of-process server.
• An in-process server will benefit from security privileges of its parent process.
• An in-process server has its life tight to the client process life.
I hope I have given you an informative overview to "COM Macro-Architecture". You
should have now a good understanding on how to create and deploy your COM
applications locally or remotely and some things you should consider in building COM
application systems.

When I started this series of articles I have tried to organise my work around a
duality idea and I chose the duality concept of Macro/Micro. Now, I maybe release
that a better duality concept would be Static/Dynamic:

• The split is still the same: the unit of deployment.


• We can use Static instead of Macro, as once a component is deployed it will
not move from its physical location (Host machine) and unit (Executable or
DLL file).
• We can use Dynamic instead of Micro, as at runtime, when we instantiate our
COM objects, we can talk about what it is happening inside the Application
and COM Server (such as Thread, Memory, Apartment, etc.) and the relation
between these instances.

One more remark about this article: I did not talk about mixing cases, such as local
and remote deployment at the same time (e.g. case 1 and 7). Maybe I will write this
section on a future update.

For my part, I will try to write the "COM Micro-Architecture" article, or maybe "COM
Dynamic-Architecture".

Thanks

I would like to thank Minh, my wife, to her support; Dave Haste and Anne-Sophie
Merot for spending the time to review my article.

You might also like