Adding DCOM to the Simple Client
To activate our DCOM server we take the basic COM client shell and add some additional methods to make it DCOM-ready. One obvious change
is that we specify the name of the server computer. Here are the other things we must add to the client:
l A way to specify the server computer name. This will load into the COSERVERINFO structure.
l Call CoCreateInstanceEx() instead of CoCreateInstance(). This also involves some different parameters and a structure called
MULTI_QI.
l The first thing you do in any COM program is call CoInitialize. We'll use the default-threading model, which is apartment threading.
// initialize COM
hr = CoInitialize(0);
Specifying the Server with COSERVERINFO
When making a remote DCOM connection you must specify the server computer. The name of the computer can be a standard UNC computer
name or a TCP/IP address. We'll ask the user to enter the computer name. Our example is a console application, so we'll use a simple input
stream (include
// Get the server name from user
char name[32];
cout << "Enter Server Name:" <<>
gets( name );
The server name will be loaded into a COSERVERINFO structure. This structure requires a pointer to a wide-character string for the server
name. We'll use _bstr_t copy constructor to convert the string. _bstr_t is a useful class when working with BSTRs and wide characters. Note
that the COSERVERINFO structure was initialized to zero with the memset() function.
// remote server info
COSERVERINFO cs;
// Init structures to zero
memset(&cs, 0, sizeof(cs));
// Allocate the server name in the COSERVERINFO struct
// use _bstr_t copy constructor
cs.pwszName = _bstr_t(name);
Specifying the Interface with MULTI_QI
Normally, we get an interface pointer by calling CoCreateInstance. For DCOM we need to use the extend version, CoCreateInstanceEx.
This extended function works perfectly well for local COM servers as well. CoCreateInstanceEx has several important differences. First, it lets
you specify the server name; and second, it allows you to get more than one interface in a single call.
We've already set up our COSERVERINFO structure. We'll pass it into CoCreateInstanceEx to specify the server. (If you leave this parameter
NULL, you'll get the local computer).
Unlike its predecessor, CoCreateInstanceEx returns more than one interface at a time. It does this by passing in an array of MULTI_QI
structures. Each element of the array specifies a single interface. CoCreateInstanceEx will fill in the data requested.
// structure for CoCreateInstanceEx
MULTI_QI qi[2];
// Array of structures
// set to zero
memset(qi, 0, sizeof(qi));
// Fill the qi with a valid interface
qi[0].pIID = &IID_IGetInfo;
qi[1].pIID = &IID_ISomeOtherInterface;
// get the interface pointer
hr = CoCreateInstanceEx(
CLSID_GetInfo, // clsid
NULL, // outer unknown
CLSCTX_SERVER, // server context
&cs, // server info
2, // size of qi
qi ); // MULTI_QI array
The MULTI_QI structure holds three pieces of information: a) a pointer to the IID, b) the returned interface pointer, and c) an HRESULT. Here's
the structure, as defined in OBJIDL.IDL:
typedef struct tagMULTI_QI
{
// pass this one in
const IID *pIID;
// get these out (must set NULL before calling)
IUnknown *pItf;
HRESULT hr;
} MULTI_QI;
The qi variable is an array of MULTI_QI structures. We start by setting the entire array to zero. This is done with the call to memset(). Next
we fill the pIID element with a pointer to the interface GUID (IID) of the interface we want to extract. In our example here, we're requesting a
pointer to IGetInfo and ISomeOtherInterface (ISomeOtherInterface is a fictional interface.). If we only need a single interface, we'd
make the array size 1 and skip the second element. The 5th parameter to CoCreateInstanceEx defines the size of the array.
Calling CoCreateInstanceEx will populate the MULTI_QI array. Like most COM API functions, CoCreateInstanceEx returns an HRESULT.
This function typically returns any of three HRESULTs.
l S_OK. All interfaces were returned.
l CO_S_NOTALLINTERFACES. At least one of the interfaces was returned, but some others failed.
l E_NOINTERFACE. None of the interfaces could be returned.
To determine which interface failed, you can check the HRESULT in the qi array.
if (SUCCEEDED(hr))
if (SUCCEEDED(qi[0].hr)) {
// pItf pointer is OK.
CoCreateInstanceEx Will Fail if There Are Network Problems.
Once you work out the initial bugs, your call to CoCreateInstanceEx will either succeed, or it will fail with an error like
RPC_S_SERVER_UNAVAILABLE. In my code, I usually only check the return status of CoCreateInstanceEx and abort the program if it isn't
S_OK. This is because I can't continue without all the requested interfaces. Note that you still have to call Release on all the interfaces that
were successfully returned.
The subject of error codes is extremely important for DCOM over the network. The lowly HRESULT contains more information then you might
suspect and this information is especially useful in tracking down network errors. I'll cover errors in more detail later in this article.
Once you've established that the interfaces were returned, you can use the interface pointers. In this case, I'm copying the pointer I requested
into a IGetInfo interface pointer for use in the program.
// pointer to interface
IGetInfo *pI;
if (SUCCEEDED(hr))
{
// Basic style string
BSTR bsName;
// Extract the interface from the MULTI_QI structure
pI = (IGetInfo*)qi[0].pItf;
// Call a method on the remote server
hr = pI->GetComputerName( &bsName );
pI->Release();
...
The rest of the code is just normal COM client code. There's nothing special about DCOM clients once you've connected to the server. There is
one big difference that we'll cover later - errors. You can expect a lot of problems when getting your client and server to work together over a
network for the first time. Many of those problems are related to server and proxy/stub registration.
http://devcentral.iticentral.com/articles/DCOM/intro_DCOM/part3/default.php (6 of 11) [7/9/2001 2:53:00 PM]
DevCentral - Understanding DCOM - Part III
Registering the Server and the Proxy/Stub
If you're working on a single machine, registration for DCOM is identical to standard COM. The server program will typically register itself when
you run it with the -REGSERVER switch. Standard Wizard-generated servers will have this code built into them. When the EXE is run with the -
REGSERVER switch, it registers itself in the system registry and exits.
C:\> remoteServer -regserver
In these examples we're using custom interfaces. This means that the proxy/stub DLL is required on the client machine. The proxy/stub is the
component that will send all information between the client and server over the network. To use a proxy/stub DLL, you need to register it.
C:\> REGSVR32 remoteserverps.dll
This registers the proxy/stub DLL on the client so DCOM can automatically activate it. If you're using an IDispatch (or dual) based automation
client, you won't have a proxy/stub DLL. In this case, you'll use a type library to register.
Copying the Client to Another Computer
You've probably built your client and server on the same computer. You probably also tested it on the same computer. Now it's time to copy
the client program to another computer and test it remotely. Copy both the client EXE and the proxy/stub DLL generated by the server. You
don't need to register the server on the client machine. This is because we've specified the server computer name in the COSERVERINFO
structure. The server does have to be registered on the server computer, or nothing will work.
On the client machine, create a new directory that will hold the client code and copy the client code into the directory. Then register the
proxy/stub. You might type something like this into the MS-DOS prompot on the client machine:
C:\> COPY \\Raoul\UnderCOM\RempteClient\Debug\Remoteclient.EXE
C:\> COPY \\Raoul\UnderCOM\RemoteServer\RemoteserverPS.DLL
C:\> REGSVR32 RemoteserverPS.DLL
Many people forget the proxy/stub DLL registration step on the third line. Without it, nothing will work.
Security is a Big Headache
Security is a really important issue, especially for network applications. Eventually, you will have to ensure that your DCOM application is
secure. Once you understand the concepts and get the server working, then you can add security to your application. My advice is to get things
working before you introduce any security.
Security varies between Win95/98 environments and Windows NT. Windows 95/98 offers some limited security features. You can go wild with
security on Windows NT.
You can manipulate security settings for both the client and sever in your program. This is done with the CoInitializeSecurity API call. You
can use this call to either add security or turn security off. You call this method immediately after calling CoInitialize.
// turn off security - overrides defaults
hr = CoInitializeSecurity(NULL, -1, NULL, NULL,
RPC_C_AUTHN_LEVEL_NONE,
RPC_C_IMP_LEVEL_IMPERSONATE,
NULL,
EOAC_NONE,
NULL);
CoInitializeSecurity takes quite a few parameters. All them are important and represent significant security concepts. You'll find extensive
(and challenging) material in the help files on CoInitializeSecurity and RPC security. (See the "Security in COM" article in MSDN).
It is important to note that you can also specify security information for a server using DCOMCNFG and OLEVIEW. These utilities save security
information in the registry. You can also specify security for the client program by explicitly calling CoInitializeSecurity (thus overriding the
default registry settings.)
An interesting aspect of CoInitializeSecurity is that it is called from both the client and server. Many of the parameters are specific to the
client and others to the server.
0 comments:
Post a Comment