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 Japanese Korean Chinese (simplified) AppWizard DLL APPWZJPN.DLL APPWZKOR.DLL 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 );

// 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 ); } } } }

} else {

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 threeletter 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 CMultiLanguage::DetectUILanguage(); // Detect language in } 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

locale MUI OS

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 threeletter 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 0x0436 0x041c 0x0401 0x0801 0x0c01 0x1001 0x1401 0x1801 0x1c01 0x2001 0x2401 0x2801 0x2c01 0x3001 0x3401 0x3801 0x3c01 0x4001 0x042b 0x042c 0x082c 0x042d 0x0423 0x0445 0x141a Column 2 Column 3 Description and notes AFK AFK Afrikaans SQI SQI Albanian ARA ARA Arabic (Saudi Arabia) ARI ARA Arabic (Iraq) ARE ARA Arabic (Egypt) ARL ARA Arabic (Libya) ARG ARA Arabic (Algeria) ARM ARA Arabic (Morocco) ART ARA Arabic (Tunisia) ARO ARA Arabic (Oman) ARY ARA Arabic (Yemen) ARS ARA Arabic (Syria) ARJ ARA Arabic (Jordan) ARB ARA Arabic (Lebanon) ARK ARA Arabic (Kuwait) ARU ARA Arabic (U.A.E.) ARH ARA Arabic (Bahrain) ARQ ARA Arabic (Qatar) HYE HYE Windows 2000/XP: Armenian. This is Unicode only. AZE AZE Azeri (Latin) AZE AZE Azeri (Cyrillic) EUQ EUQ Basque BEL BEL Belarusian BNG BNG Bengali (India) BSB HRV Bosnian (Bosnia and Herzego vina)

0x0402 0x0455 0x0403 0x0404 0x0804 0x0c04 0x1004 0x1404 0x041a 0x101a 0x0405 0x0406 0x0465 0x0413 0x0813 0x0409 0x0809 0x0c09 0x1009 0x1409 0x1809 0x1c09 0x2009 0x2409 0x2809 0x2c09 0x3009 0x3409 0x0425 0x0438 0x0429 0x040b 0x040c 0x080c 0x0c0c 0x100c 0x140c 0x180c 0x0456 0x0437 0x0407 0x0807 0x0c07

BGR === CAT CHT CHS ZHH ZHI ZHM HRV HRB CSY DAN DIV NLD NLB ENU ENG ENA ENC ENZ ENI ENS ENJ ENB ENL ENT ENW ENP ETI FOS FAR FIN FRA FRB FRC FRS FRL FRM GLC KAT DEU DES DEA

BGR === CAT CHT CHT CHT CHT CHT HRV HRV CSY DAN DIV NLD NLD ENU ENU ENU ENU ENU ENU ENU ENU ENU ENU ENU ENU ENU ETI FOS FAR FIN FRA FRA FRA FRA FRA FRA GLC KAT DEU DEU DEU

Bulgarian Burmese Catalan Chinese (Taiwan) Chinese (PRC) Chinese (Hong Kong SAR, PRC ) Chinese (Singapore) Windows 98/ME, Windows 2000 /XP: Chinese (Macao SAR) Croatian Croatian (Bosnia and Herzeg ovina) Czech Danish Windows XP: Divehi. This is Unicode only. Dutch (Netherlands) Dutch (Belgium) English (United States) English (United Kingdom) English (Australian) English (Canadian) English (New Zealand) English (Ireland) English (South Africa) English (Jamaica) English (Caribbean) English (Belize) English (Trinidad) Windows 98/ME, Windows 2000 /XP: English (Zimbabwe) Windows 98/ME, Windows 2000 /XP: English (Philippines) Estonian Faeroese Farsi Finnish French (Standard) French (Belgian) French (Canadian) French (Switzerland) French (Luxembourg) Windows 98/ME, Windows 2000 /XP: French (Monaco) Windows XP: Galician Windows 2000/XP: Georgian. This is Unicode only. German (Standard) German (Switzerland) German (Austria)

0x1007 0x1407 0x0408 0x0447 0x040d 0x0439 0x040e 0x040f 0x0421 0x0434 0x0435 0x0410 0x0810 0x0411 0x044b 0x0457 0x0412 0x0812 0x0440 0x0426 0x0427 0x0827 0x042f 0x043e 0x083e 0x044c 0x0481 0x043a 0x044e 0x0450 0x0414 0x0814 0x0415 0x0416 0x0816 0x0446 0x046b 0x086b 0x0c6b 0x0418 0x0419 0x044f 0x043b 0x083b 0x0c3b 0x103b 0x143b

DEL DEC ELL GUJ HEB HIN HUN ISL IND XHO ZUL ITA ITS JPN KAN KNK KOR === KYR LVI LTH === MKI MSL MSB MYM MRI MLT MAR MON NOR NON PLK PTB PTG PAN QUB QUE QUP ROM RUS SAN SME SMF SMG SMJ SMK

DEU DEU ELL GUJ HEB HIN HUN ISL IND XHO ZUL ITA ITA JPN KAN KNK KOR KOR KYR LVI LTH LTH MKI MSL MSL MYM MRI MLT MAR MON NOR NOR PLK PTB PTB PAN QUB QUB QUB ROM RUS SAN SME SME SME SME SME

German (Luxembourg) German (Liechtenstein) Greek Windows XP: Gujarati. This is Unicode only. Hebrew Windows 2000/XP: Hindi. This is Unicode only. Hungarian Icelandic Indonesian isiXhosa/Xhosa (South Africa) isiZulu/Zulu (South Africa) Italian (Standard) Italian (Switzerland) Japanese Windows XP: Kannada. This is Unicode only. Windows 2000/XP: Konkani. This is Unicode only. Korean Windows 95, Windows NT 4.0 only: Korean (Johab) Windows XP: Kyrgyz. Latvian Lithuanian Windows 98 only: Lithuanian (Classic) Macedonian (FYROM) Malay (Malaysian) Malay (Brunei Darussalam) Malayalam (India) Maori (New Zealand) Maltese (Malta) Windows 2000/XP: Marathi. This is Unicode only. Windows XP: Mongolian Norwegian (Bokmal) Norwegian (Nynorsk) Polish Portuguese (Brazil) Portuguese (Portugal) Windows XP: Punjabi. This is Unicode only. Quechua (Bolivia) Quechua (Ecuador) Quechua (Peru) Romanian Russian Windows 2000/XP: Sanskrit. This is Unicode only. Sami, Northern (Norway) Sami, Northern (Sweden) Sami, Northern (Finland) Sami, Lule (Norway) Sami, Lule (Sweden)

0x183b 0x1c3b 0x203b 0x243b 0x0c1a 0x1c1a 0x081a 0x181a 0x046c 0x0432 0x041b 0x0424 0x040a 0x080a 0x0c0a 0x100a 0x140a 0x180a 0x1c0a 0x200a 0x240a 0x280a 0x2c0a 0x300a 0x340a 0x380a 0x3c0a 0x400a 0x440a 0x480a 0x4c0a 0x500a 0x0430 0x0441 0x041d 0x081d 0x045a 0x0449 0x0444 0x044a 0x041e 0x041f 0x0422 0x0420 0x0820 0x0443

SMA SMB SMS SMN SRB SRN SRL SRS NSO TSN SKY SLV ESP ESM ESN ESG ESC ESA ESD ESV ESO ESR ESS ESF ESL ESY ESZ ESB ESE ESH ESI ESU === SWK SVE SVF SYR TAM TTT TEL THA TRK UKR URD === UZB

SME SME SME SME HRV HRV HRV HRV NSO TSN SKY SLV ESP ESP ESP ESP ESP ESP ESP ESP ESP ESP ESP ESP ESP ESP ESP ESP ESP ESP ESP ESP === SWK SVE SVE SYR TAM TTT TEL THA TRK UKR URD URD UZB

Sami, Southern (Norway) Sami, Southern (Sweden) Sami, Skolt (Finland) Sami, Inari (Finland) Serbian (Cyrillic) Serbian (Cyrillic, Bosnia, and Herzegovina) Serbian (Latin) Serbian (Latin, Bosnia, and Herzegovina) Sesotho sa Leboa/Northern Sotho (South Africa) Setswana/Tswana (South Africa) Slovak Slovenian Spanish (Spain, Traditional Sort) Spanish (Mexican) Spanish (Spain, Modern Sort ) Spanish (Guatemala) Spanish (Costa Rica) Spanish (Panama) Spanish (Dominican Republic) Spanish (Venezuela) Spanish (Colombia) Spanish (Peru) Spanish (Argentina) Spanish (Ecuador) Spanish (Chile) Spanish (Uruguay) Spanish (Paraguay) Spanish (Bolivia) Spanish (El Salvador) Spanish (Honduras) Spanish (Nicaragua) Spanish (Puerto Rico) Sutu Swahili (Kenya) Swedish Swedish (Finland) Windows XP: Syriac. This is Unicode only. Windows 2000/XP: Tamil. This is Unicode only. Tatar (Tatarstan) Windows XP: Telugu. This is Unicode only. Thai Turkish Ukrainian Windows 98/ME, Windows 2000 /XP: Urdu (Pakistan) Urdu (India) Uzbek (Latin)

0x0843 0x042a 0x0452

UZB VIT CYM

UZB VIT CYM

Uzbek (Cyrillic) Windows 98/ME, Windows NT 4 .0 and later: Vietnamese 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. 2. 3. 4. Start Visual C++ Studio. Close any open workspace and all files. Select New from the file menu. 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 The The The Windows System directory Windows directory Application local path 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*); typedef int (*MYFUN2)(int); int main() { MYFUN1 MYFUN2 HMODULE BOOL unloaded pfun1; pfun2; hMod; bRes; // pointer to: void function(char*) // pointer to: int function(int)

// handle to loaded library module // BOOL to check if DLL was successfully

// 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. 2. 3. 4. Start Visual C++ Studio. Close any open workspace and all files. Select New from the File menu. 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(); ~DLLclass(); int Add(int, int); int Sub(int, int); int Arg; };

// // // //

Class Class Class Class

Constructor destructor function Add function Subtract

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 COM AppWizard

HelloServ

Server With the ATL

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

member variable to the class. Move the cursor to the // data members section, and add the code shown below in bold:
m_szDisplayName class CServiceModule : public CComModule { ... // data members public: TCHAR m_szDisplayName[256]; TCHAR m_szServiceName[256];

// display name of service

... }; 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 #include #include #include "stdafx.h" "resource.h" < initguid.h > "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 // If you are call // instead to // This means // HRESULT hr = CoInitialize(NULL); running on NT 4.0 or higher you can use the following make the EXE free threaded. that calls come in on a random RPC thread = 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 { ... };

[

}; Listing 2. The _IHelloWorldEvents name replaced with DHelloWorldEvents.

] coclass HelloWorld { [default] interface IHelloWorld; [default, source] dispinterface DHelloWorldEvents; };

uuid(...), helpstring("HelloWorld Class")

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 Method

IHelloWorld

Interface to Add the

SayHello()

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. 2. 3. 4.
The client is the source of a method call, The server sinks (that is, acts as the sink for) the method call. An "event call" comes out of the now-server-as-source. 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 headers // Exclude rarely-used stuff from Windows

#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 headers #define _WIN32_WINNT #include #include #include #include Common < < < < afxwin.h > afxext.h > afxdisp.h > afxdtctl.h > // Exclude rarely-used stuff from Windows 0x0400 // // // // MFC MFC MFC MFC core and standard components extensions Automation classes support for Internet Explorer 4

// Controls #ifndef _AFX_NO_AFXCMN_SUPPORT #include < afxcmn.h > // MFC support for Windows Common Controls #endif // _AFX_NO_AFXCMN_SUPPORT #include < atlbase.h > #include < afxctl.h > // Support for CComPtr< > // 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 rollback 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 point BOOL server? m_dwCookie; m_bSinkAdvised; // Cookie to keep track of connection // Were we able to advise the

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

Sign up to vote on this title
UsefulNot useful