Welcome!
 
Last Changed:
1998-09-17
Using Delphi & VC++ together for COM/DCOM applications.
  Background

Having participated in a project using Visual C++ for server applications and Delphi for client applications I decided to just write down a few of the problems and solutions that we encountered.

Our primary usage of DCOM has been using a C++ server to get data from several database servers and feeding this to a Delphi client. There are a few alternatives when it comes to developing a com interface, namely doing an automation style interface or a custom interface with custom types.

Since doing a server that supports automation types is much more of a hassle than doing a straight forward custom interface, we wanted to use that approach with Delphi as a client as well. The reason? No sane person wants to do a gui with VC++.

As soon as you start examine this situation you find several problems:

  • How to use user defined structs/records
  • How does Delphi see Conformant arrays
  • Member alignment between Delphi & VC++
  • Delphis import of type libraries somewhat confusing.
  • Marshaling of enumerated types (enums).

Structs/Records

It is awfully convenient to do your own datatypes and sending them cross interfaces. To do this in IDL is just as easy as in C++ and Delphi.

What to remember is that out parameters must be allocated by CoTaskMemAlloc and destroyed on the recieving side.

CoTaskMemalloc and CoTaskMemFree is defined in the Delphi ActiveX unit.

Conformant arrays

Conformant arrays are a clever invention to help transfer dynamically sized arrays across an interface. The thing you do is that you tell the marshaler to look for the size of an array in another variable. Like this:

typedef struct UserRecordSearchResult
{ int resultSize;
[size_is(resultSize)] int result[];
} UserRecordSearchResult;

In this struct the size of the result array is pointed out by the resultSize variable, and when the marshaler is transferring data cross processes it knows where to look for the size.

The problem with using these conformant arrays (both in VC++ and Delphi) is that it involves pointer arithmetic. However, the pointer arithmetic is very simple.

Filling such an array from the C++ side is pretty simple, let's say that you have a method defined like:

STDMETHODIMP CAccessControl::FindUsersWithName(BSTR UserName,UserRecordSearchResult** URSResult)

Then the C++ code for filling that array would be (assuming that the result is in a vector<int> resultList):

int aSize=sizeof(int)*(1+resultList.size());
*URSResult=(UserRecordSearchResult*) CoTaskMemAlloc(aSize);
ZeroMemory(*URSResult,aSize);
(*URSResult)->resultSize=resultList.size();
for (int i=0;i<resultList.size();i++)
{
(*URSResult)->result[i]=resultList[i];
}

On the Delphi side the code to "unpack" such a result would be :

var
URSResult:^UserRecordSearchResult;
intpointer:^integer;
i:integer;
begin
anInterface.FindUsersWithName('Aname',URSResult);
if assigned(URSResult) then
begin
intpointer:=@URSResult^.result;
for i:=0 to URSResult^.resultSize-1 do
begin
// dereference the intpointer to use the integer passed
inc(intpointer);
end;
CoTaskMemFree(URSResult); //Remember to free the memory when done!
end;

Member Alignment

ATL (and VC) by default uses 8 byte member alignment which is grossly incompatible with Delphis "automatic" member alignment. The solution is to use the IDL and VC compatible :

#pragma pack(push,4)

and

#pragma pack(pop)

This will force the member alignment for that section to be 4 byte member alignment. This should be compatible with Delphis "automatic" member packing, however, if you use packed records (which the Typelib importer makes as default) you need to pack with 1 byte member alignment. This is slightly more inefficient though.

Importing Type libraries

When you import a type library into Delphi there are somethings that you should watch:

Conformant arrays in structs:

The array variable becomes a pointer type. This is confusing because you might think that this points to the first element. Instead to make a pointer to the first element you need to take the address of that variable, and when calculating the size of structs which have conformant arrays in them you should deduct the size of that pointer. Example:

Size to allocate= (size of Struct containing the conformant array)- sizeof(that pointer variable) + n * (the size of the array type).

Packed records

By default the type library importer makes all records packed, which should equal 1 byte member alignment (see above) from the other side. If you remove the packed for all the records, you can use 4 byte member alignment.

Irritating things...

The most annoying thing about the typelibrary import is that it defines pointer types to the defined types with PUserTypen where n is the nth number of the record in the type library. Why didn't they just add a P to the typename???

Marshaling enums

It is crucial that enums defined in the idl has a correct value when transfered across an interface, if not, the data will not be transfered!

Specially important since on the Delphi side the OLE enum type is translated as an integer with no checks for correct enum values.

ATL Gotchas!

ATL.DLL !

The normal ATL project needs ATL.DLL in order to execute. This can prove to be a stumble stone when you try to deploy your server. Put ATL_STATIC_REGISTRY at the beginning of your stdafx.h file in order to link to a static version of the contents of ATL.DLL.

Security

The default security for your ATL project can be to strong for Windows 95 and WORKGROUP environments. To completely take away security, use these parameters for the CoInitializeSecurity:

hr = CoInitializeSecurity(NULL, -1, NULL, NULL,
RPC_C_AUTHN_LEVEL_NONE, RPC_C_IMP_LEVEL_IMPERSONATE, NULL, EOAC_NONE, NULL);

Questions, corrections or comments, please email me!

Mail me at:mailto:christer_f@oocities.com

Number of visits:

Receive email when this page changes

Click Here
Powered by Netmind

   

GeoCities Rank My SiteTake A TourMy GuestbookChat
Pages Like MineSearchSend This PageForums
Email Me
SiliconValley