DLL Tutorial
DLL Tutorial
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)
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.
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.
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:
If you are making an MFC extension .DLL, you can instead use the AFX_EXT_CLASS macro:
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.
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:
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:
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:
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:
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:
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"
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();
}
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:
Remember, if you used the extern "C" specifier in the .DLL, you must also use it in the client application:
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:
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.
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:
– 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.
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.
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 );
langid = pfnGetSystemDefaultUILanguage();
AddLangId( langid );
TRACE(_T("CMultiLanguage::DetectUILanguage()"
_T" 3rd/4th = %04X\n"), langid );
}
else
{
// We're not on an MUI-capable system.
if (::GetVersion()&0x80000000)
{
// We're on Windows 9x, so look
// in the registry for the UI language
HKEY hKey = NULL;
LONG nResult = ::RegOpenKeyEx(HKEY_CURRENT_USER,
_T( "Control Panel\\Desktop\\ResourceLocale" ),
0, KEY_READ, &hKey);
if (nResult == ERROR_SUCCESS)
{
DWORD dwType;
TCHAR szValue[16];
ULONG nBytes = sizeof( szValue );
nResult = ::RegQueryValueEx(hKey, NULL, NULL, &dwType,
LPBYTE( szValue ), &nBytes );
if ((nResult == ERROR_SUCCESS) && (dwType == REG_SZ))
{
DWORD dwLangID;
int nFields = _stscanf( szValue, _T( "%x" ),
&dwLangID );
if( nFields == 1 )
{
langid = LANGID( dwLangID );
AddLangId( langid );
TRACE(_T("CMultiLanguage::DetectUILanguage()"
_T" 9X1st/2nd = %04X\n"), langid );
}
}
::RegCloseKey(hKey);
}
}
else
{
// We're on NT 4. The UI language
// is the same as the language of the
// version resource in ntdll.dll
HMODULE hNTDLL = ::GetModuleHandle( _T( "ntdll.dll" ) );
if (hNTDLL != NULL)
{
langid = 0;
::EnumResourceLanguages( hNTDLL, RT_VERSION,
MAKEINTRESOURCE( 1 ),
_AfxEnumResLangProc,
reinterpret_cast< LONG_PTR >( &langid ) );
if (langid != 0)
{
AddLangId( langid );
TRACE(_T("CMultiLanguage::DetectUILanguage()"
_T" NT1st/2nd = %04X\n"), langid );
}
}
}
}
if ( m_nLocales < MAX_NUM_LCID )
{
m_alcidSearch[m_nLocales] = LOCALE_SYSTEM_DEFAULT;
m_nLocales++;
} else {
m_alcidSearch[MAX_NUM_LCID-1] = LOCALE_SYSTEM_DEFAULT;
m_nLocales = MAX_NUM_LCID;
}
return LANGIDFROMLCID(m_alcidSearch[0]);
}
While the above code works for MUI OS, a user may change the default locale, not the
UI language. The following code detects the user default language, not the UI language.
LANGID CMultiLanguage::DetectLangID()
{
LANGID langid = 0;
int nPrimaryLang = 0;
int nSubLang = 0;
LCID lcid = 0;
int nLocales = 0;
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.
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:
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:
//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;
}
return NULL;
}
hLangDLL = ::LoadLibrary(szResDLLName);
if(hLangDLL != NULL)
return hLangDLL;// Successful return
}
return hLangDLL;
}
We can call LoadLangResourceDLL(…) to load the first available language DLL in the
list. You can change the language ID detected from above. For example, you only include
the resource DLL for Chinese PRC, and you want to use it for Chinese Taiwan and
Chinese Singapore too. You can do this hard-coding here. If you don't want to hard code
here, you can make a duplicated copy of the resource-only DLL file with another three-
letter language name. (Copy XXXXCHS.DLL to XXXXCHT.DLL for this case.)
If your EXE or DLL files don't include resource, you need to have one system default
language "LOC" in your resource-only language DLL file list. This is the last chance the
system will try to find the language resource.
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():
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(...):
You may want to free the library when system detaches the process.
Column one is LANGID; the lower 10 bits are for the language, the higher 6 bits are for the
sub-language. Column two is the three-letter language code. Column three is the three-
letter language code without sub-language. It looks for this language if the language in
column two does not exist. Column one and column four are got from “Language
Identifier”(3.); column two and column three are got from the GetLocaleInfo() function
on my XP Pro PC. See CMultiLanguage::PrintThreeLetterLanguageCodeList()
function in MultiLanguage.cpp for details.
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:
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.
First, we are going to make the Win32 DLL core files for the project, W32DLL.xxx.
/*******************************************************
File name: DLLCode.h
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.
/*********************************************************
File name: DLLCode.cpp
void DLLfun1(char* a)
{
cout << a << endl;
};
DLLclass::DLLclass() {};
DLLclass::~DLLclass() {};
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.
Close the "Executable For Debug Session" dialog box, we ran the DLL prematurely.
Congratulation, you finished building the Win32 DLL and its export Library.
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.
/***********************************************************
File name: DLLClient1.cpp
***********************************************************/
#include <iostream>
#include <conio.h>
#include <windows.h>
#include "DLLCode.h"
#pragma comment(lib,"W32DLL.lib")
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);
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:
For simplicity, we just copy the common files to the target directory, DLLClient1.
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.
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.
/************************************************************
File name: DLLClient2.cpp
************************************************************/
#include <conio.h> // Header file containing getch() prototype
#include <iostream>
#include <windows.h>
MYFUN1 pfun1;
MYFUN2 pfun2;
HMODULE hMod; // handle to loaded library module
BOOL bRes; // BOOL to check if DLL was successfully
unloaded
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;
};
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
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.
Save and recompile the W32DLL project. Copy the new W32DLL.DLL file to the project
DLLClient2 directory. Run the DLLClient2 application.
Note: You CANNOT export a class object to the DLL. The library object contains that
information, along with the header file.
This time, we are going to make the MFC DLL core files for the project, RMFCDLL.xxx.
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.
/******************************************************
File name: DLLCode.h
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
};
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
DLLclass::DLLclass() {};
DLLclass::~DLLclass() {};
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.
This client application is a MFC application. It provides the framework, a window, for
the MFC CBrush object used in the DLL function DrawEllipse.
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
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:
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.
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.
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
void CClass::LongFunc()
{
...
CClass class;
hResult = class.Func();
...
}
Variable Names
• m_szDisplayName
• CHelloWorld
• IHelloWorld
• DHelloWorldEvents instead of _IHelloWorldEvents
Functions
• Function()
• THIS_IS_A_SYMBOL
• ISayHello::SayHello()
• CServiceModule
• [IDL_attribute]
• __stdcall and typedef
• END_OBJECT_MAP()
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:
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:
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.
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.
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.
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:
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:
To start, we need to provide Windows with a user-friendly name it can use to display this
service to the user. This is called the "Display Name" of the service. AppWizard already
set up and specified a "Service Name" for us: HelloServ. For the user's sake, let's come
up with something which is easier to understand: Hello World Server. To do this, we'll
add a m_szDisplayName member variable to the CServiceModule class, and a String
Table entry to hold the display name. We do things this way so that it's easy for us to
change the display name of the service to whatever we wish.
First, double-click on the CServiceModule icon in ClassView. This will jump you to
where the CServiceModule class is declared, in STDAFX.H. We need to add a
m_szDisplayName member variable to the class. Move the cursor to the // data
members section, and add the code shown below in bold:
// data members
public:
TCHAR m_szDisplayName[256]; // display name of service
TCHAR m_szServiceName[256];
...
};
Listing 1. Adding the m_szDisplayName member variable to the CServiceModule
class.
The next thing to do is to add an entry to the String Table, IDS_DISPLAY_NAME, to hold
the display name we want to use. Figure 2, shown below, illustrates adding the String
Table entry. To do this, complete these steps:
...
}
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()
{
...
...
}
Listing 3. The call to the Windows CreateService() function, as called by
AppWizard.
...
}
Listing 4. Changing the second m_szServiceName to m_szDisplayName.
Finally, the last part of Step 2 is to initialize everything properly. Go to the place in the
HELLOSERV.CPP file shown // PLACE THE CURSOR HERE comment in Listing 5:
#include "stdafx.h"
#include "resource.h"
#include < initguid.h >
#include "HelloServ.h"
#include "HelloServ_i.c"
CServiceModule _Module;
BEGIN_OBJECT_MAP(ObjectMap)
END_OBJECT_MAP()
Add all of the following code at where I've just told you to place the cursor:
// Initialization successful
return TRUE;
}
Listing 6. Adding the InitApplication() function to the server. Next, we need to
replace some of the code in CServiceModule::Run() with a call to the
InitApplication() function which we added in Listing 6 above. Using ClassView,
go to the CServiceModule::Run() function, and delete the code shown in bold:
void CServiceModule::Run()
{
...
HRESULT hr = CoInitialize(NULL);
// If you are running on NT 4.0 or higher you can use the following
call
// instead to make the EXE free threaded.
// This means that calls come in on a random RPC thread
// HRESULT hr = CoInitializeEx(NULL, COINIT_MULTITHREADED);
_ASSERTE(SUCCEEDED(hr));
...
}
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.
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:
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:
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:
When everything is set correctly, the Attributes tab should appear as that in Figure 4,
below. When everything's ready, click OK to have the New ATL Object Wizard generate
code for us and add our new COM object to the server.
Figure 4. The Attributes tab of the ATL Object Wizard Properties dialog when we're done
changing settings.
When everything's been added, ClassView should look like that shown in Figure 5. There
is still one more change we need to make, though. Notice that, in Figure 5, the name of
the event interface (for the connection point) is not _IHelloWorldEvents but instead
DHelloWorldEvents? This is the result of a little change I made to the code.
Figure 5. ClassView after adding the HelloWorld object and changing the name of the
event interface.
The DHelloWorldEvents name for our event interface is a loose convention of
Microsoft's which I'm following. DHelloWorldEvents is a dispinterface, so a D
belongs in front of its name. Make sense? Good. Double-click the _IHelloWorldEvents
icon in ClassView. This jumps you to the project's IDL source code, which declares all of
the OLE stuff that our project uses. Next, replace all instances of _IHelloWorldEvents,
highlighted in bold in Listing 1 below, and change them to DHelloWorldEvents,
highlighted in bold in Listing 2 below.
[
...
]
library HELLOSERVLib
{
importlib("stdole32.tlb");
importlib("stdole2.tlb");
[
uuid(...),
helpstring("_IHelloWorldEvents event interface")
]
dispinterface _IHelloWorldEvents
{
...
};
[
uuid(...),
helpstring("HelloWorld Class")
]
coclass HelloWorld
{
[default] interface IHelloWorld;
[default, source] dispinterface _IHelloWorldEvents;
};
};
Listing 1. The instances of the _IHelloWorldEvents interface, which we need to
replace with DHelloWorldEvents:
[
...
]
library HELLOSERVLib
{
importlib("stdole32.tlb");
importlib("stdole2.tlb");
[
uuid(...),
helpstring("DHelloWorldEvents event interface")
]
dispinterface DHelloWorldEvents
{
...
};
[
uuid(...),
helpstring("HelloWorld Class")
]
coclass HelloWorld
{
[default] interface IHelloWorld;
[default, source] dispinterface DHelloWorldEvents;
};
};
Listing 2. The _IHelloWorldEvents name replaced with DHelloWorldEvents.
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:
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:
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:
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*/;
}
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:
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
return S_OK;
}
Listing 2. Adding code to implement the SayHello() method.
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:
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:
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...
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.
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:
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.
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:
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;
if (!GetComputerName(szComputerName, &dwSize))
return E_FAIL; // failed to get the name of this computer
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 [email protected] 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:
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:
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:
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.
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
• 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:
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:
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_
//{{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_
//{{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.
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.
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()
// {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.
BEGIN_INTERFACE_MAP(CHelloWorldEvents, CCmdTarget)
INTERFACE_PART(CHelloWorldEvents, DIID_DHelloWorldEvents,
Dispatch)
END_INTERFACE_MAP()
Listing 6. Putting DIID_DHelloWorldEvents in place of the Class-Wizard-added
IID_IHelloWorldEvents identifier.
Now, we're going to use ClassWizard to add the handler function which gets called when
the server fires the OnSayHello event. Bring up ClassWizard, and select the Automation
tab. Make sure that CHelloWorldEvents is selected in the Class Name box, as shown in
Figure 5 below:
Figure 5. Selecting the CHelloWorldEvents class on the Automation tab of
ClassWizard.
Click Add Method. The Add Method dialog box appears, as shown in Figure 6 below.
Here we're going to specify the "external name" for our event handler method, as well as
other information. The "external name" should ALWAYS match the name of the event
method that we used when we added it to the server! The Internal Name box should hold
the name of the member function that will get called when the event comes in; this can be
whatever you please. We're going to use what ClassWizard suggests; a name that matches
the OnSayHello "external name." ALWAYS specify void for the return type of an event
handler, because the server always uses HRESULT as its return types. For clients, anytime
we're handling connection point events, the return type should always be void. Next,
specify a parameter, LPCTSTR lpszHost as the event handler's single parameter. You
notice that BSTR isn't in the list of event handler types (alright!). This is because you use
LPCTSTR instead; ClassWizard makes sure that MFC will convert between BSTR and
LPCTSTR for you (!).
Figure 6. Setting up a handler for the OnSayHello event.
Click OK. ClassWizard adds code to CHelloWorldEvents to make the magic happen
(with CCmdTarget's help), and then shows a new entry in its External Names listbox to
show that the event handler has been added:
Figure 7. ClassWizard showing the addition of our new event handler.
Save your changes! Make sure and click OK in ClassWizard, otherwise it will roll-
back all of its changes that it made. If you click Cancel, you'll have to add the event
handler again. Just a word of warning. Now it's time to implement our event handler.
We'll grab the name of the server computer from lpszHost, and then we'll show the user
a message box saying that the server said Hello. We might also want to add text to the
Status window of our dialog saying that the event handler function got called. Here's how
I did that:
#include "HelloCliDlg.h"
void CHelloWorldEvents::OnSayHello(LPCTSTR lpszHost)
{
CHelloCliDlg* pDlg = (CHelloCliDlg*)AfxGetMainWnd();
if (pDlg != NULL)
{
pDlg->m_strStatus
+= "The OnSayHello() connection point method has been
called\r\n";
pDlg->UpdateData(FALSE);
}
// Show a message box saying 'Hello, world, from host ' + lpszHost:
CString strMessage = "Hello, world, from ";
strMessage += lpszHost;
AfxMessageBox(strMessage, MB_ICONINFORMATION);
}
Listing 7. Implementing the CHelloWorldEvents::OnSayHello() event handler
function.
...
};
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;
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;
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;
if (spUnk.p)
{
// Unadvise the connection with the event source
return AfxConnectionUnadvise(pUnk, DIID_DHelloWorldEvents,
m_pHelloWorldEventsUnk, TRUE, m_dwCookie);
}
BOOL CHelloCliDlg::OnInitDialog()
{
CDialog::OnInitDialog();
...
CoInitialize(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));
serverInfo.pwszName = L"\\\\Viz-06";
serverInfo.pAuthInfo = &athn;
serverInfo.dwReserved1 = 0;
serverInfo.dwReserved2 = 0;
...
try
{
m_pHelloWorld = new IHelloWorldPtr;
}
catch(...)
{
AfxMessageBox(AFX_IDP_FAILED_MEMORY_ALLOC, MB_ICONSTOP);
...
return;
}
if (FAILED(hResult))
{
...
return;
}
m_pHelloWorld->Attach((IHelloWorld*)qi.pItf);
...
return;
}
Listing 13. How to get an interface pointer to the IHelloWorld interface on the
remote server.
To release the server when we're done with it, simply delete the m_pHelloWorld pointer:
delete m_pHelloWorld;
m_pHelloWorld = NULL;
Listing 15.
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.
History