Library Zone Articles
External Articles
Byte Size

Discovery Zone Catalogue
Diary
Links
Bookstore
Interactive Zone Ask the Gurus
Discussion Groups
Newsletters
Feedback
Etc Cartoons
Humour
COMpetition
Advertising
Site Builder ASP Web Ring ASP Web Ring

Click here
The Developer's Resource & Community Site
COM XML ASP Java & Misc. NEW: VS.NET
International This Week Forums Author Central Find a Job

A DCOM Client for Linux - Part 1: Remote Activation

Download print article

This article assumes you are familiar with COM and RPC.

Introduction

DCOM is known to be a Microsoft thing. This article shows how to code a DCOM client for Linux without using any Microsoft stuff. For this I used the OpenSource implementation of DCE RPC called FreeDCE [3], developed by Jim Doyle et al.

I will show how to remotely activate a COM server running on Windows NT 4.0 from Linux (RedHat 5.2). Once activated an object is created through the IClassFactory interface and methods of the newly created object are called.

My first surprise was that the DCOM specification [5] does not have the completeness of an average RFC. Very basic things that are left unspecified are:

  • How IRpcChannel calls are sent across the wire
  • Details of NTLM authentication
  • The RPC interfaces of well known interfaces such as IRemUnknown and IClassFactory

Because of this, I had to do some trial-and-error work, a great deal of network sniffing and posing questions on the DCOM mailing list [email protected]. I received excellent answers from, especially, Henk de Koning and Michael Nelson.

Remoting Interface Pointers

For completeness sake, I will spend a few words on the basics of the remoting architecure of DCOM. Because excellent stuff has already been written on this subject [1][2], I will keep it short. If you are familiar with ORPC, skip this or it will bore you.

A remote interface pointer consists of 2 things: 1. a bag of bytes, and 2. an agreement on how to interpret it. There are 2 kinds:

  1. Standard marshaling. Both the content of the bag and how to interpret it are written down by the COM specification.
  2. Custom marshaling. You decide what the bag looks like. You and the one at the other end of the wire must agree on how to interpret it.

If you do custom marshaling, an object that implements a number of interfaces knows how to marshal all interfaces and is responsible for it. For all DCOM cares you could be using pigeons.

Standard marshaling can be thought of as a special kind of custom marshaling (at least conceptually). The DCOM runtime provides a skeleton for both the proxy and the server. This skeleton hosts smaller pieces. Each of these pieces knows how to interpret and build the bag of bytes that travel across the wire for exactly one interface. If you have a custom interface without type library no one can ever know how to do this. You will have to write a custom proxy-stub factory. DCOM does provide you with a convenient channel between the client and the server: IRpcChannel. Whether this channel crosses apartment, process or machine boundaries is transparent.

Another type of marshaling is type library marshaling. The type library marshaler is part of the DCOM runtime. Given the type library of an interface, it will determine at run-time how the arguments should be marshaled and un-marshaled.

ID's

Identifiers that play a role in the remoting architecture are OXIDs, IPIDs, CIDs, OIDs and SETIDs. The IPID will be discussed in detail in this article. The OXID will be discussed in detail in part 2 of this article.

An OXID is a UUID that identifies an object exporter. An object exporter is an object that, by definition, implements the COM interface IRemUnknown. An OXID can be used to get the RPC string bindings of the object exporter. For this you would consult the OXID resolver (OR) which is a service that runs on each machine that supports DCOM. Client code that consults the OR will be presented in part 2 of this article.

An IPID is an (128 bit) interface pointer ID. An IPID is associated with each interface that is standardly marshaled. You can consider an IPID as a location-independent IUnknown interface pointer.

A CID is a causality identifier. It is created by the DCOM runtime when a first call is made across apartments. Any calls that are made on this call will have to send this identifier along. It is used to prevent deadlocks.

An OID is an (64-bit) object ID. This id is unique within the scope of an object exporter. Only one OID is associated with one remoted object. An object implements 1 or more interfaces. An object is the smallest unit that can be pinged, see SETID. A new OID is generated each time a machine reboots.

A SETID is used to ping a set of OIDs. Pinging is used to clean up references that are no longer valid due to clients that died without properly releasing their references. Without SETIDs, OIDs would have to be pinged separately. This would be an enormous burden on the network.

DCE RPC

Cross-machine COM calls are really ordinary RPC calls. Because of this, I was able to speak DCOM using a DCE RPC implementation that was developed without ever considering DCOM. This proofs that DCOM is a truly platform independent protocol.

With respect to plain DCE RPC, DCOM added the concept of an object, hence it is called Object RPC or ORPC. Actually, RPC does already have the notion of an object; if you make an RPC call you can specify an object ID. (In fact, DCOM exploits this, as we will see later.) The use of the object ID in RPC, however, is application specific. In DCOM, this is part of the protocol. Another important difference is that DCOM allows you to connect to a server component that is not already running. In classic RPC, the server must be running before the client can talk to it. Application specific steps are taken to launch the server before the client requests services. In DCOM, this is part of the protocol as well. Finally, DCOM allows you to query an object for other interfaces than the ones you already have. This will be demonstrated when I discuss the IRemUnknown interface in part 2 of this article.

The Server

The server that will be activated is registered on a Windows NT 4.0, SP5 machine. It is a straightforward out-of-process server. It is implemented as follows:

Generate the ATL Project

  1. Create a new project using the ATL COM AppWizard
  2. Specify 'Executable' as the type of server
  3. Insert a new ATL Object
  4. Select 'Simple Object'
  5. Select 'Custom Interface'
  6. Add method Sum( [in] long a, [in] long b, [out] long* sum );

As a result, the server has the following IDL description:


import "oaidl.idl";
import "ocidl.idl";

[
   object, 
   uuid(772552AD-E435-11D2-9440-004005512025), pointer_default(unique) 
]
interface IRocketScience : IUnknown
{ 
   HRESULT Sum( [in] long a, [in] long b, [out] long* sum ); 
};

[ 
  uuid(772552A1-E435-11D2-9440-004005512025), 
  version(1.0) 
]
library RocketScienceLib
{
  importlib("stdole32.tlb");
  importlib("stdole2.tlb");

  [ uuid(772552AE-E435-11D2-9440-004005512025 ]
  coclass RocketScience
  { 
    [default] interface IRocketScience; 
  };
};

In order to build and register the proxy-stub factory for this server, I add the following 'Post-build steps' to the project:

  1. nmake rocketscienceps.mk
  2. regsvr32 /s rocketscienceps.dll

Authentication

The DCE RPC implementation I used does not include authentication. Because of this I had to configure the server machine so that I can launch and access the server object without authentication. For this, I had to enable remote users to launch the server. I did this using DCOMCNFG.

Note that authentication is implemented inside the RPC runtime. Authentication protocols such as Kerberos, or even your custom ones, can be plugged into both the RPC and DCOM runtimes.

The NTLM authentication scheme which is used by Windows NT 4.0 is unfortunately a proprietary Microsoft protocol and undocumented. The good news is that Microsoft decided to switch to Kerberos as of Windows 2000. This protocol is standard and well-documented [4].

The Client

The client runs on Red Hat 5.2 with FreeDCE 1.1 installed. I used the gnu C++ compiler to build the client code. The code that I will discuss here is proof-of-concept code. It is easy to imagine how the code could be wrapped into familiar COM API's such as CoCreateInstanceEx().

Three steps will be described:

  1. A COM server is activated on NT.
  2. CreateInstance is called on the returned IClassFactory interface pointer.
  3. Sum(3, 4) is called on the IRocketScience interface as returned by CreateInstance.

These steps hide two important mechanisms: OXID resolution and obtaining interface pointers through the IRemUnknown interface. I will discuss these separately in part 2 of this article. Also not covered is pinging: In order to keep the activated server alive it should be pinged. If you don't, it will be considered as garbage and will be collected after approximately 6 minutes.

Activate the COM server

The first step is to activate a server on NT. For this, we must knock at the SCM's door. The SCM is a DCE RPC server that listens at port 135 and runs inside rpcss.exe. The RPC interface (not a COM interface) is called IRemoteActivation. It has exactly one method called RemoteActivation:


// remact/remact.idl 
HRESULT RemoteActivation(
  [in] handle_t                               hRpc,
  [in] ORPCTHIS                               *ORPCthis,
  [out] ORPCTHAT                              *ORPCthat,
  [in] GUID                                   *Clsid,
  [in, string, unique] WCHAR                  *pwszObjectName,
  [in, unique] MInterfacePointer              *pObjectStorage,
  [in] DWORD                                  ClientImpLevel,
  [in] DWORD                                  Mode,
  [in] DWORD                                  Interfaces,
  [in,unique,size_is(Interfaces)] IID         *pIIDs,
  [in] unsigned short                         cRequestedProtseqs,
  [in, size_is(cRequestedProtseqs)]
       unsigned short                         RequestedProtseqs[],
  [out] OXID                                  *pOxid,
  [out] DUALSTRINGARRAY                       **ppdsaOxidBindings,
  [out] IPID                                  *pipidRemUnknown,
  [out] DWORD                                 *pAuthnHint,
  [out] COMVERSION                            *pServerVersion,
  [out] HRESULT                               *phr,
  [out,size_is(Interfaces)] MInterfacePointer **ppInterfaceData,
  [out,size_is(Interfaces)] HRESULT           *pResults
  );

I copy-pasted the specification of IRemoteActivation from the DCOM specification [5] to my own idl-file. I ran the FreeDCE idl compiler over it, which produces a.o. two files: iremact_cstub.o and iremact.h. I will have to link the .o-file to the SCM client. The .h-file is included in client.c. I will now describe the code inside client.c.

The first thing I do is open an RPC connection to the SCM (all error handling is omitted):


rpc_binding_handle_t binding_h;
unsigned_char_t* string_binding;
unsigned32 status;

rpc_string_binding_compose(
  NULL,                                // UUID
  ( unsigned_char_t* ) "ncacn_ip_tcp", // protocol
  ( unsigned_char_t* ) argv[1],        // host
  ( unsigned_char_t* ) "135",          // port
  NULL,                                // network options
  &string_binding,                     // result
  &status );

rpc_binding_from_string_binding(
  string_binding,
  &binding_h,
  &status );

if ( !rpc_mgmt_is_server_listening( binding_h, &status ) )
  ; // error

Once this connection has successfully been opened, I can call RemoteActivation. Note that this method is declared in iremact.h, which has been generated by the idl compiler and is included in client.c. The client stub is defined in iremact_cstub.o. The following code shows how I setup the in-parameters of RemoteActivation:


ORPCTHIS_t orpcthis = {
   { 5, 2 },      /* COMVERSION */
   1,             /* flags */
   0,             /* reserved */
   0,             /* causality ID */
   0              /* extension array */
};

unsigned short RequestedProtseqs[1] = { 0x07 };

An interesting note is the fact that an idl compiler will never accept the definition of OBJREF. It will complain that a 'conformant array or structure is invalid within a union'. Introducing the type MInterfacePointer has solved this. This conformant byte array can be interpreted as an OBJREF.

I then call the method RemoteActivation.


RemoteActivation(
  binding_h,
  &orpcthis,
  &orpcthat,
  (CLSID_t*) &CLSID_HelloWorld,// in: remote class object
  NULL,                        // in: pwszObjectName
  NULL,                        // in: pObjectStorage
  RPC_C_IMP_LEVEL_IMPERSONATE, // in: level of trust
  MODE_GET_CLASS_OBJECT,       // in: Mode */
  1,                           // in: number of interfaces to query
  (IID_t*) &IID_IClassFactory, // in: IIDs of interfaces to query
  1,                           // in: cRequestedProtseqs
  RequestedProtseqs,           // in: short RequestedProtseqs[]
  &oxid,                       // out: pOXID
  &pdsaOxidBindings,
  (IPID_t*) &ipidRemUnknown,   // out: pipidRemUnknown
  &AuthnHint,
  &ServerVersion,              // out: COM version of server
  &act_hr,                     // out: result of activation request
  &pInterfaceData,             // out: queried interface pointers
  &qi_hr );                    // out: result of implicit queryinterface  

if ( pInterfaceData == NULL )
  ; // error
else
  fprintobjref( stderr, pInterfaceData[0].abData );

In order to call CreateInstance on the returned IClassFactory pointer, the only information I need is its IPID as returned in ipidRemUnknown. This is demonstrated in the next section.

Call IClassFactory::CreateInstance

What we have now is a marshaled interface pointer to the IClassFactory interface of IRocketScience's class factory. The next thing to do is call CreateInstance on this interface. This will create a new object that implements the interface IRocketScience.

This call has to be made across the wire. This means that at the other end of the wire an RPC server is listening. However, the RPC interface of this server isn't specified anywhere! I assumed the most obvious RPC equivalent of IClassFactory and verified this by making calls from a Windows 98 client to a Windows NT server and sniffing the network packets. As you can see, I included an [in] parameter of which the meaning is unknown to me. If I don't include it, the call will fail.


// createinst/createinst.idl
HRESULT_t CreateInstance
(
   [in]	handle_t hRpc,
   [in]	ORPCTHIS_t*  orpcthis,
   [out]	ORPCTHAT_t* orpcthat,
   [in]	IID_t* piid,                // interface to instantiate
   [in]	IID_t* puuidnil,            // purpose unknown
   [out] 	MInterfacePointer_t** ppmip // returned interface pointer
);

In the idl file, this method is preceded by 3 other methods that, by the way, will never be called: QueryInterface, AddRef and Release.

I compile this the same way as I did for IRemoteActivation and open the connection as I demonstrated before. This time however, I will set the object uuid of the connection to the IPID as returned by RemoteActivate. Note that this code is part of an executable to which the IPID is passed as the 3rd argument.


uuid_t objectid;
uuid_from_string( ( unsigned char* ) argv[3], &objectid, &status );
rpc_binding_set_object(
   binding_h,
   &objectid,
   &status );

The only notable thing about setting up the in-parameters is that I generate a random CID as part of ORPCTHIS as follows:


uuid_create( ( uuid_t* ) &orpcthis.cid, &status );

Finally, I call CreateInstance:


CreateInstance(
   binding_h,
   &orpcthis,
   &orpcthat,
   ( IID_t* ) &IID_IRocketScience, // in: requested interface
   ( IID_t* ) &uuidnil,            // purpose unknown
   &pInterfaceData,                // out: marshalled itp
   &status );
if ( rpc_s_ok != status )
  ; // error
else
{
  if ( NULL == pInterfaceData )
    ; // error
  else
    fprintobjref( stderr, pInterfaceData[0].abData );
}

The function fprintobjref does the obvious. The important element of the OBJREF structure is the IPID. It will be used in the next section in order to call methods of the newly created object.

Note: What we have by now, is what you normally achieve by simply calling CoCreateInstanceEx!


Call IRocketScience::Sum

Now that we have an object that implements IRocketScience, we can call its method Sum. Except for the following, its implementation is similar to that of IClassFactory::CreateInstance.


long sum;
Sum( binding_h, &orpcthis, &orpcthat, 3, 4, &sum, &status );
if ( rpc_s_ok != status )
   ; // error
else
   fprintf( stderr, "Sum( 3, 4 ) returned %d\n", ( int ) sum );

Conclusions

Given the FreeDCE implementation of DCE RPC for Linux, it is has been shown how to activate remote DCOM servers, create an instance of an object through the IClassFactory interface and invoke methods of the newly created object. The server runs on Windows NT 4.0, the client runs on RedHat 5.2. Authentication and keep-alive pinging have not been discussed. OXID resolution will be covered in part 2 of this article.

References

[1] "Inside OLE", 2nd ed, Kraig Brockschmidt
[2] "Inside DCOM", Guy and Henry Eddon
[3] https://www.bu.edu/~jrd/FreeDCE/
[4] RFC 1510, "The Kerberos Network Authentication Service (V5)"
[5] "Distributed Component Object Model Protocol -- DCOM/1.0", Charlie Kindel

Acknowledgements

First, I want to thank Jim Doyle for developing FreeDCE. Second, I want to thank Michael Nelson and Henk de Koning for responding to questions I sent to the DCOM mailing list ([email protected]).

Frank Rem, MSc
Software Engineer


What do you think of this article?

Have your say about the article. You can make your point about the article by mailing [email protected] (If you haven't allready joined, you can join by going to onelist.com/community/dev-com).

You can also write a review. We will publish the best ones here on this article. Send your review to [email protected].

Mail a question to the author!!

As part of the IDevResource commitment to Open Publishing, all of our authors are available to answer all of your trickiest questions at Author Central. For information about the authors, or to mail a question, visit them at Author Central.


Power your site with idr newswire

Contribute to IDR:

To contribute an article to IDR, a click here.

To contact us at IDevResource.com, use our feedback form, or email us.

To comment on the site contact our webmaster.

Promoted by CyberSavvy UK - website promotion experts

All content © Copyright 2000 IDevResource.com, Disclaimer notice

Join the Developers Webring

Visit the IDR Bookstore!

Visit the IDR Forums

WTL Introduction

Java COM integration