You are on page 1of 80

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

You might also like