Thursday, December 11, 2008

DCOM - Topics

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 ) to get user input and display messages.

// 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:

Your Title