Marshalling

 

by

 

Peter Petersen (elvis@daimi.aau.dk)

And

Kåre Kjelstrøm (hardcore@daimi.aau.dk)

 

 

16/11 1997

Content

Abstract
Introduction
Location Transparency
Type Library/Automation Marshalling
Standard Marshalling
Custom Marshalling
Problems
Evaluation
Appendix A: Type Library Server
    Pascal Type Library Wrapper
    Form Unit
    Implementation Unit
Appendix B: Type Library Client
    Form Unit
    Dialog Box Unit
Appendix C: Standard Server
    IDL Definition
    Pascal Type Library Wrapper
    DLL .DEF File
    Makefile
    Form Unit
    Implementation Unit
Appendix D: Standard Client
    Form Unit

Abstract

This document discusses the process of passing parameters between applications and components in Microsoft’s (D)COM, an action called marshalling. We treat 2 of the 3 kinds of marshalling in depth by providing illustrative code fragments from a number of working examples, whilst covering the last by a description only. Finally a discussion of the problems involved in working with marshalling and an evaluation is given.

Introduction

Marshalling can be done in different ways, and in order to illustrate the differences between these, and the steps involved, a couple of examples in Delphi have been created.

COM offers 3 different ways of marshalling parameters across process or network boundaries, namely automation/type library, standard, and custom, listed here in order of simplicity from the developer’s point of view. Alas, the simplest to implement is also the least flexible, as we shall see.

Figure 1: Type Library Marshalling Client and Server windows

The example applications, which can be found in their entirety in the appendices, use type library and standard marshalling, respectively, to pass a data structure across a network. Since automation has been treated thoroughly in a previous document, and because type library marshalling is equivalent to the way automation servers marshal their parameters, no applications for automation have been developed. Since custom marshalling burdens the developer enormously, examples for this kind of parameter passing have likewise been omitted.

The first example uses type library marshalling to pass a safearray of safearrays, strings and integers from a client to a server. The data sent is information about a person and his/her children. The structure allows an arbitrary amount of children.

In the upper part of the client, are a number of edit boxes that will accept data about the parent. Below this is a list box containing information about the associated children. New children can be added via the "Add" button and the entire data structure can be transmitted to the server by clicking on "Send PersonalData". When "Add" is pushed, a dialogue box pops up, and allows you to enter data about a child.

The server merely contains a memo box, in which it will print the received data. It is implemented as an executable, i.e. using the out-of-process convention. The source code for the first example is available in appendices A and B, while the server form, the client’s main form and the dialogue box are all shown on Figure 1.

Figure 2: Standard Marshalling Client and Server windows

In the second example, a somewhat simpler data structure is marshalled. This time, only parental data is sent, and it is not possible to add any children. The client thus only contains 3 edit boxes for the data and a "Submit" button for sending it. The server resembles the first server, in that it prints the received data in a memo box. Client and server screen shots can be seen on Figure 2, and the source code is listed in appendices C and D.

Location Transparency

The basic idea that makes COM a convenient way of distributing objects is the notion of getting an interface corresponding to some globally unique identifier. The client of the object need not be aware of either implementation or physical location of the server object. In the case of the server object being implemented as in-process, communication with the client is fairly trivial: the client’s invocations of server functions are simply translated into ordinary procedure calls through some indirection mechanism, usually a v-table.

Procedure calls on out-of-process servers are somewhat more involved. These use the Microsoft version of DCE RPC to bundle the parameters, send them across the process or network boundary and de-bundle them again on the receiving side. This marshalling process is entirely transparent to the client, which only knows of the interface and the functions it can call on it.

In order to obtain this kind of transparency in the out-of-process case, special code for packing up and restoring the parameters is needed. On the server side, you will therefor need a DLL usually known as a stub. The stub will receive RPC packets from another process or the network and translate them into actual procedure calls, which it will then invoke on the running server. Any response from the server will be sent back to the client using the same RPC mechanism. On the client side another DLL, known as the proxy, will translate procedure calls on the interface into RPC packets and ship them across process or network boundaries to the server stub. Furthermore the proxy will translate any response from the server into the correct data type or structure. Being almost identical in functionality, the proxy and stub will often reside in the same DLL, henceforth referred to as the proxy/stub DLL.

COM allows you to use pointers to structures and simple data types as parameters to interface methods. In the in-process case this is not a problem, but when parameters have to be marshalled, a pointer structure involves keeping different instances of the same data synchronised. When a pointer is marshalled, a deep copy of the data pointed to is transferred. The scenario resembles that of passing parameters by value, in which case the value is simply copied and sent.

Type Library/Automation Marshalling

COM the easy way is to implement a server that uses an IDispatch or a Dual interface as calling mechanism, because you don’t need to supply the proxy/stub DLL. The reason for this is that every Windows NT 4.0 and Windows 95 contain 2 DLLs, Ole32.dll and Oleaut32.dll that will act as proxy/stub when marshalling data to and from an automation server. The catch is that automation restricts the programmer to use the types that can be put into an OLE variant, as described in "Microsoft's OLE Automation" by the authors.

In order to use an automation server on the same machine as it is registered, you only need the ProgID of the server application. When your automation server resides on another machine, your registry will need an entry under the HKEY_CLASSES_ROOT\AppID\{.. object CLSID..} key called "RemoteServerName". With this in place, invocation of the server object is similar to the local case, since the SCM will attempt to launch the remote server automatically.

The fact that the automation proxy/stub DLLs are always present, can be utilised by non-IDispatch interfaces to marshal parameters without having to provide a specialised proxy/stub. By using the automation DLLs, the programmer will restrict himself to using only the OLE compatible types, and since the interface is not derived from IDispatch, he will also have to provide a type library for the client to know the interfaces, hence the name type library marshalling.

When you want to use structures that are just a little more complex than the basic types, in this setting you will have to create a safearray of values. Safearrays can hold other safearrays, and it is thus possible to build arbitrarily deep structures. In the first example, a safe array is used to hold a list of safearrays, which in turn contains 2 strings for the name of a child and an integer for the age. Filling such an array involves allocation and assignment as shown on Figure 3.

theKids := VarArrayCreate([0, (Kids.Count div 3)-1], varVariant);

 

i := 0;

while(i < Kids.Count) do

begin

Billy := VarArrayCreate([0, 2], varVariant);

Billy[0] := Kids.Strings[i];

Billy[1] := Kids.Strings[i+1];

Billy[2] := Kids.Strings[i+2];

 

theKids[i div 3] := Billy;

 

i := i + 3;

end;

Figure 3: Allocating and filling a safearray

The Delphi version of the safearray allocation routine is quite similar to the API equivalent. In Delphi you specify a range and which type the allocated cells should have, whereas in the API function SafeArrayCreate, you pass the type, the number of elements and the destination buffer. The Delphi function, of course, is nothing more than a wrapper. The API function is shown on Figure 4.

HRESULT SafeArrayCreate(VARTYPE vt, unsigned int cDims, SAFEARRRAYBOUND FAR* rgsabound);

Figure 4: API function to allocate a safearray

Standard Marshalling

Sometimes the restrictions of type library marshalling are next to unacceptable. This is the case when you need more sophisticated structures than can be provided with safearrays or when you are trying to turn legacy code (e.g. a huge C library J ) into a COMponent.

In this case you will have to provide your own proxy/stub DLL for marshalling the structures. The traditional way of defining an interface is to use IDL and compile the file with Microsoft’s IDL compiler, MIDL. Based on the interface description, MIDL can generate a type library (.TLB), and the necessary C files for producing the proxy/stub DLL.

In Delphi 3.x, a type library is defined using a graphical tool, known as the Type Library Editor. This will automatically generate a type library, Pascal wrappers for the interface definition – akin to those listed in the appendices - and a skeleton implementation of the associated CoClass. Unfortunately, neither IDL structs or unions are supported in the editor.

You can open an existing type library with structures or unions and view it, but you may not add or delete structs from the editor. When viewing an existing type library that was not defined from within the editor, it will only generate the Pascal wrapper for the type library and neatly turn a struct into a record, but the skeleton code for the CoClass is for the programmer to write.

In order to implement standard marshalling using Delphi, you will for the described reasons, need at least MIDL, a linker and a C compiler. The steps involved in building all the necessary files can be summarised as follows:

  1. Write the interface definition in IDL and save it.
  2. Write a DEF file for defining the functions in the resulting DLL. This file will be equivalent to the one shown in appendix C.
  3. Compile the IDL file with MIDL to generate the proxy/stub C files and the type library.
  4. Compile the C files and link them into the proxy/stub DLL.
  5. Register the DLL with the registry using the operating system supplied tool, regsvr32.exe. The steps 3 – 5 can be put into a makefile, which can be compiled with Microsoft’s nmake utility. A makefile for the standard marshalling server proxy/stub DLL is listed in appendix C.
  6. Open Delphi and open the type library. Delete the definition of IUnknown since Delphi will automatically include this interface, and the definition here conflicts with the Delphi version. Set the parent interface of your interface to IUnknown.
  7. Push the "Register" button to update the registry and generate the Pascal version of the interface.
  8. Create a new unit and write the code for the CoClass. Make sure to include the creation of a ClassFactory for your interface in the initialization section of the unit.
  9. Compile and run the server to register it with the registry.
  10. Write the client using the type library to get the definition of structures and interfaces
  11. Copy the proxy/stub and the type library to the client machine and register both.
  12. Copy the client executable to the client machine and run it.

When writing the IDL file, you have to consider carefully how you specify the interfaces and the library keyword that tells MIDL you want a type library as well. Generally the following rules apply to MIDL:

In the IDL code shown in Figure 5, which is a stripped version of the IDL code from appendix C, we generate both type library and C files from the same IDL file. Notice how the interface definition comes first outside the library definition, which in turn contains 2 references to IMarshalStruct: one for the CoClass and one for the library.

[…]

interface IMarshalStruct : IUnknown {…};

 

/* Type library definition */

[…]

library prjMarshalStruct

{

[…]

coclass MarshalStruct {

[default] interface IMarshalStruct;

};

 

interface IMarshalStruct;

};

Figure 5: IDL code for type library and proxy/stub

When the type library has been imported and the corresponding Delphi wrapper has been generated, you have a Delphi version of your custom structs and unions. In our case the IDL struct as shown on Figure 6 translates into the Pascal record type shown below the IDL version.

typedef struct _Person {

[string] wchar_t *firstname;

[string] wchar_t *lastname;

int age;

} Person;

 

_Person = record

firstname: PWideChar;

lastname: PWideChar;

age: SYSINT;

end;

Figure 6: IDL and Pascal versions of the same structure

This record is now quite simple to use, e.g. with an event handler for a button, which sends the structure to the server as shown on Figure 7. Notice that the IDL char pointer translates into a PWideChar Delphi type, which is a pointer to an array of 16 bit characters. This is the motivation for using the function StringToOleStr in order to convert the 8 bit native Delphi strings into OLE types.

procedure TForm1.btnSubmitClick(Sender: TObject);

var

data: _Person;

begin

data.firstname := StringToOleStr(editFirstName.Text);

data.lastname := StringToOleStr(editLastName.Text);

data.age := StrToInt(editAge.Text);

 

OleCheck(iMarsh.GetPerson(data));

end;

Figure 7: Sending a struct to the server

Custom Marshalling

Whenever you want complete control over everything that happens in the entire communication process, you should use custom marshalling. With this scheme, nothing comes for free and you will have to implement the marshalling process yourself in its entirety. You could want to use custom marshalling, if you will be using some other transport mechanism for communication than the usual RPC mechanism, e.g. named pipes, HTTP or raw TCP/IP.

With standard marshalling, COM provides you with an implementation of the IMarshal interface, but with custom marshalling, IMarshal with its 6 member functions is – apart from the proxy/stub DLL and CoClass implementation - what you will have to implement yourself. The functions of IMarshal will be called by the COM API function CoMarshalInterface in order to package up the parameters and get them on their way.

In reality, implementing custom marshalling is probably too much work to be practically useful. This is also the main reason why we have not dealt with this kind of marshalling.

Problems

During the process of implementing standard marshalling, a number of problems were encountered. First of all, we spent quite an amount of time figuring out how to do this in Delphi. The documentation is quite vague on the limitations of the type library editor, and mentions almost nothing of how to integrate existing type libraries with Delphi.

When we realised that we had to use MIDL, the problems really began. Being novice IDL programmers, we succeeded in writing IDL code that produced an illegal type library. This contained 2 structures, of which the one contained an array of the other. When we tried to open the type library in Delphi, the order of the structures was reversed so that the containing struct could not "see" the contained struct that it had references to. Since the type library editor will not allow you to edit structures, there was no way to remedy this problem from within Delphi.

We then turned to the OLE-COM viewer, which ships with Visual C++, to view the IDL of the type library, and this turned out to be illegal as well. When saved to a file and attempted compiled with MIDL, the compiler would halt with an error because it also had problems with the order of the structures. Conclusion: MIDL can generate a type library, the IDL code of which it cannot itself compile.

Another problem with MIDL turned out to be that even if you specify a forward reference to an interface, define the type library before the interfaces, and even though MIDL will compile this into the C files needed for the proxy/stub, the C compiler complained of an illegal syntax. This was not solved until we moved the definition of the type library below the definition of the interfaces and deleted the forward references. You have to tread carefully when dancing with MIDL.

Evaluation

In the course of working with the different marshalling methods, we have come to find that even though type library marshalling is the easier to implement, standard marshalling is not all too hard work with either. In the case of converting legacy code into components, standard marshalling or custom marshalling can even be a must. The extra overhead of having to register the proxy/stub DLL is minimal, because you have to register the type library on the client machine anyhow.

Working with MIDL can at times be a little tedious. It seems that the compiler is sensitive to the order of the elements involved in the interface definition. In the case of structures that use other structures, this is no different from C or Pascal, but the fact that MIDL doesn’t recognise the dependencies, and joyfully swaps the order of the structures into something illegal, is a problem.

Our original intention was to define the standard interface in a manner equivalent to the safearray structure in the type library server’s interface, i.e. with an array of children as well. This turned out to be a little more involved than we had anticipated. First of all, the IDL syntax for writing an array of structs with dynamic length, so called conformant arrays, was not supported by Delphi, which appears to accept only static arrays. We have made an attempt to send a structure with a statically allocated array of structs, but so far without success.

Finally it appears that the Delphi type library editor needs some work before it can be said to be finished and support all kinds of type libraries.

 

Appendix A: Type Library Server

This server demonstrates type library marshalling. It consists of 3 files, viz. the pascal wrapper version of the type library, a unit containing the visual form declaration and the implementation of the CoClass.

Pascal Type Library Wrapper

unit prjTypeLibMarshal_TLB;

 

{ This file contains pascal declarations imported from a type library.

This file will be written during each import or refresh of the type

library editor. Changes to this file will be discarded during the

refresh process. }

 

{ prjTypeLibMarshal Library }

{ Version 1.0 }

 

interface

 

uses Windows, ActiveX, Classes, Graphics, OleCtrls, StdVCL;

 

const

LIBID_prjTypeLibMarshal: TGUID = '{5F9F4A20-5E81-11D1-B030-0020AF3BC782}';

 

const

 

{ Component class GUIDs }

Class_ITypeLibMarshal: TGUID = '{5F9F4A22-5E81-11D1-B030-0020AF3BC782}';

 

type

 

{ Forward declarations: Interfaces }

IITypeLibMarshal = interface;

 

{ Forward declarations: CoClasses }

ITypeLibMarshal = IITypeLibMarshal;

 

{ Dispatch interface for ITypeLibMarshal Object }

 

IITypeLibMarshal = interface(IUnknown)

['{5F9F4A21-5E81-11D1-B030-0020AF3BC782}']

procedure GetPersonalData(Data: OleVariant); safecall;

end;

 

{ ITypeLibMarshalObject }

 

CoITypeLibMarshal = class

class function Create: IITypeLibMarshal;

class function CreateRemote(const MachineName: string): IITypeLibMarshal;

end;

 

 

 

implementation

 

uses ComObj;

 

class function CoITypeLibMarshal.Create: IITypeLibMarshal;

begin

Result := CreateComObject(Class_ITypeLibMarshal) as IITypeLibMarshal;

end;

 

class function CoITypeLibMarshal.CreateRemote(const MachineName: string): IITypeLibMarshal;

begin

Result := CreateRemoteComObject(MachineName, Class_ITypeLibMarshal) as IITypeLibMarshal;

end;

 

 

end.

 

Form Unit

unit unitForm;

 

interface

 

uses

Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,

StdCtrls;

 

type

TForm1 = class(TForm)

Memo1: TMemo;

private

{ Private declarations }

public

{ Public declarations }

end;

 

var

Form1: TForm1;

 

implementation

 

{$R *.DFM}

 

end.

 

Implementation Unit

unit unitImpl;

 

interface

 

uses

ComObj, ActiveX, prjTypeLibMarshal_TLB, unitForm, SysUtils;

 

type

TITypeLibMarshal = class(TAutoObject, IITypeLibMarshal)

protected

procedure GetPersonalData(Data: OleVariant); safecall;

end;

 

implementation

 

uses ComServ;

 

{----------------------------------------------------------------------

- Function: Retrieve family data from client and display in memofield

----------------------------------------------------------------------}

procedure TITypeLibMarshal.GetPersonalData(Data: OleVariant);

var

cCount, i: Integer;

begin

if VarIsArray(data) then

begin

with Form1.Memo1.Lines do

begin

Add('* Person *****************');

Add(Data[0] + ' ' + Data[1] + ' (' + IntToStr(Data[2]) + ')');

 

if(VarIsArray(Data[3])) then

begin

cCount := VarArrayHighBound(Data[3], 1);

i := 0;

Add('- Children ----------');

while (i <= cCount) do

begin

Add(IntToStr(i) + ': ' +

Data[3][i][0] + ' ' +

Data[3][i][1] + ' (' +

IntToStr(Data[3][i][2]) + ')');

inc(i);

end

end;

Add('**************************');

end;

end;

end;

 

initialization

TAutoObjectFactory.Create(ComServer, TITypeLibMarshal, Class_ITypeLibMarshal, ciMultiInstance);

end.

 

 

Appendix B: Type Library Client

The type library marshalling client consists of 2 files, one for the main form, which has a function for sending input data to the server, and one for a dialogbox that will accept child data.

Form Unit

unit unitForm;

 

interface

 

uses

Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,

StdCtrls, ComObj, prjTypeLibMarshal_TLB;

 

type

TfrmClient = class(TForm)

btnSendData: TButton;

GroupBox1: TGroupBox;

editName: TEdit;

editLastName: TEdit;

editAge: TEdit;

Label1: TLabel;

Label2: TLabel;

Label3: TLabel;

GroupBox2: TGroupBox;

listKids: TListBox;

btnAddKid: TButton;

procedure btnSendDataClick(Sender: TObject);

procedure FormCreate(Sender: TObject);

procedure btnAddKidClick(Sender: TObject);

procedure FormDestroy(Sender: TObject);

private

{ Private declarations }

public

{ Public declarations }

end;

 

var

frmClient: TfrmClient;

Class_DispSafeArray: TGUID = '{5F9F4A22-5E81-11D1-B030-0020AF3BC782}';

Server: ITypeLibMarshal; // Server Interface reference

Kids: TStringList; // Internal representation of children data

 

implementation

 

uses unitKidDlg;

 

{$R *.DFM}

{----------------------------------------------------------------------

- Function: Create SafeArray of parent and child data and send it to

- the server

----------------------------------------------------------------------}

procedure TfrmClient.btnSendDataClick(Sender: TObject);

var

theParent, theKids, Billy: Variant;

i: Integer;

begin

theKids := VarArrayCreate([0, (Kids.Count div 3)-1], varVariant);

 

i := 0;

while(i < Kids.Count) do

begin

Billy := VarArrayCreate([0, 2], varVariant);

Billy[0] := Kids.Strings[i];

Billy[1] := Kids.Strings[i+1];

Billy[2] := Kids.Strings[i+2];

 

theKids[i div 3] := Billy;

 

i := i + 3;

end;

 

theParent := VarArrayCreate([0, 3], varVariant);

theParent[0] := editName.Text;

theParent[1] := editLastName.Text;

theParent[2] := StrToInt(editAge.Text);

theParent[3] := TheKids;

 

Server.GetPersonalData(theParent);

end;

 

{----------------------------------------------------------------------

- Function: Get a reference to server interface (on adam)

----------------------------------------------------------------------}

procedure TfrmClient.FormCreate(Sender: TObject);

begin

Server := CoITypeLibMarshal.CreateRemote('\\adam');

Kids := TStringList.Create;

end;

 

{----------------------------------------------------------------------

- Function: Add a child. Pops up a dialog box and saves data i stringlist

----------------------------------------------------------------------}

procedure TfrmClient.btnAddKidClick(Sender: TObject);

begin

if(frmKidDlg.ShowModal = mrOK) then

begin

listKids.Items.Add(frmKidDlg.editName.Text + ' ' +

frmKidDlg.editLastName.Text + ' (' +

frmKidDlg.editAge.Text + ')');

 

Kids.Add(frmKidDlg.editName.Text);

Kids.Add(frmKidDlg.editLastName.Text);

Kids.Add(frmKidDlg.editAge.Text);

end;

end;

 

{----------------------------------------------------------------------

- Function: Deallocate resources

----------------------------------------------------------------------}

procedure TfrmClient.FormDestroy(Sender: TObject);

begin

Kids.Destroy;

end;

 

end.

Dialog Box Unit

unit unitKidDlg;

 

interface

 

uses

Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,

StdCtrls, Buttons;

 

type

TfrmKidDlg = class(TForm)

GroupBox1: TGroupBox;

Label1: TLabel;

Label2: TLabel;

Label3: TLabel;

editName: TEdit;

editLastName: TEdit;

editAge: TEdit;

BitBtn1: TBitBtn;

BitBtn2: TBitBtn;

private

{ Private declarations }

public

{ Public declarations }

end;

 

var

frmKidDlg: TfrmKidDlg;

 

implementation

 

{$R *.DFM}

 

end.

 

 

Appendix C: Standard Server

This server, which consists of 6 files, uses standard marshalling to retrieve a structure with 3 properties. The first file is the IDL interface definition, which when compiled with MIDL will yield 3 .c-files and a .h-file for the proxy/stub code along with a .tlb typelibrary file. Next is the pascal wrapper version of this typelibrary, then a .def file used for building the proxy/stub dll and the makefile that makes life a little bit easier. Last is the visual form and the actual implementation of the CoClass.

IDL Definition

typedef struct _Person {

[string] wchar_t *firstname;

[string] wchar_t *lastname;

int age;

} Person;

 

[

odl,

uuid(581171D5-5B61-11D1-B029-0020AF3BC782),

version(1.0),

helpstring("IUnknown Interface for MarshalStruct Object"),

hidden,

object

]

interface IMarshalStruct : IUnknown {

import "unknwn.idl";

HRESULT _stdcall GetPerson([in] Person *data);

};

 

/* Type library definition */

[

uuid(581171D4-5B61-11D1-B029-0020AF3BC782),

version(1.0),

helpstring("prjMarshalStruct Library")

]

library prjMarshalStruct

{

[

uuid(581171D6-5B61-11D1-B029-0020AF3BC782),

version(1.0),

helpstring("MarshalStructObject")

]

coclass MarshalStruct {

[default] interface IMarshalStruct;

};

 

interface IMarshalStruct;

};

 

Pascal Type Library Wrapper

unit prjMarshalStruct_TLB;

 

{ This file contains pascal declarations imported from a type library.

This file will be written during each import or refresh of the type

library editor. Changes to this file will be discarded during the

refresh process. }

 

{ prjMarshalStruct Library }

{ Version 1.0 }

 

interface

 

uses Windows, ActiveX, Classes, Graphics, OleCtrls, StdVCL;

 

const

LIBID_prjMarshalStruct: TGUID = '{581171D4-5B61-11D1-B029-0020AF3BC782}';

 

const

 

{ Component class GUIDs }

Class_MarshalStruct: TGUID = '{581171D6-5B61-11D1-B029-0020AF3BC782}';

 

type

 

{ Forward declarations: Interfaces }

IMarshalStruct = interface;

 

{ Forward declarations: CoClasses }

MarshalStruct = IMarshalStruct;

 

_GUID = record

Data1: UINT;

Data2: Word;

Data3: Word;

Data4: array[0..7] of Byte;

end;

 

_Person = record

firstname: PWideChar;

lastname: PWideChar;

age: SYSINT;

end;

 

{ IUnknown Interface for MarshalStruct Object }

 

IMarshalStruct = interface(IUnknown)

['{581171D5-5B61-11D1-B029-0020AF3BC782}']

function GetPerson(var data: _Person): HResult; stdcall;

end;

 

{ MarshalStructObject }

 

CoMarshalStruct = class

class function Create: IMarshalStruct;

class function CreateRemote(const MachineName: string): IMarshalStruct;

end;

 

 

 

implementation

 

uses ComObj;

 

class function CoMarshalStruct.Create: IMarshalStruct;

begin

Result := CreateComObject(Class_MarshalStruct) as IMarshalStruct;

end;

 

class function CoMarshalStruct.CreateRemote(const MachineName: string): IMarshalStruct;

begin

Result := CreateRemoteComObject(MachineName, Class_MarshalStruct) as IMarshalStruct;

end;

 

 

end.

DLL .DEF File

LIBRARY Proxy.dll

DESCRIPTION 'Proxy/Stub DLL'

EXPORTS DllGetClassObject @1 PRIVATE

DllCanUnloadNow @2 PRIVATE

GetProxyDllInfo @3 PRIVATE

DllRegisterServer @4 PRIVATE

DllUnregisterServer @5 PRIVATE

Makefile

clean:

del *.c

del *.obj

del *.h

 

doidl : marshalstruct.idl

midl marshalstruct.idl

 

dlldata.obj : dlldata.c

cl /c /DWIN32 /DREGISTER_PROXY_DLL dlldata.c

 

marshalstruct_p.obj : marshalstruct_p.c

cl /c /DWIN32 /DREGISTER_PROXY_DLL marshalstruct_p.c

 

marshalstruct_i.obj : marshalstruct_i.c

cl /c /DWIN32 /DREGISTER_PROXY_DLL marshalstruct_i.c

 

PROXYSTUBOBJS = dlldata.obj \

marshalstruct_p.obj \

marshalstruct_i.obj

 

PROXYSTUBLIBS = kernel32.lib \

rpcndr.lib \

rpcns4.lib \

rpcrt4.lib \

uuid.lib

 

proxy.dll : $(PROXYSTUBOBJS) proxy.def

link /dll /out:proxy.dll /def:proxy.def \

$(PROXYSTUBOBJS) $(PROXYSTUBLIBS)

regsvr32 /s proxy.dll

Form Unit

unit unitForm;

 

interface

 

uses

Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,

StdCtrls;

 

type

TForm1 = class(TForm)

Memo1: TMemo;

Label1: TLabel;

private

{ Private declarations }

public

{ Public declarations }

end;

 

var

Form1: TForm1;

 

implementation

 

{$R *.DFM}

 

end.

Implementation Unit

unit unitImpl;

 

interface

 

uses

Windows, SysUtils, ComObj, prjMarshalStruct_TLB, unitForm;

 

type

TMarshalStruct = class(TAutoObject, IMarshalStruct)

protected

function GetPerson(var data: _Person): HResult; stdcall;

end;

 

implementation

 

uses ComServ;

 

{----------------------------------------------------------------------

- Function: Implementation of IMarshalStruct.GetPerson. Add the data

- to the memobox.

----------------------------------------------------------------------}

function TMarshalStruct.GetPerson(var data: _Person): HResult;

begin

Form1.Memo1.Lines.Add('GetPerson');

Form1.Memo1.Lines.Add(WideCharToString(data.firstname) + ' ' +

WideCharToString(data.lastname) + ' aged '+

IntToStr(data.age));

 

result := S_OK; // Always return OK.

end;

 

initialization

TAutoObjectFactory.Create(ComServer, TMarshalStruct, Class_MarshalStruct, ciMultiInstance);

end.

 

Appendix D: Standard Client

The standard marshalling client is contained within the unit that defines the visual form.

Form Unit

unit prjClient;

 

interface

 

uses

Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,

prjMarshalStruct_TLB, StdCtrls, ComObj;

 

type

TForm1 = class(TForm)

btnSubmit: TButton;

editFirstName: TEdit;

editLastName: TEdit;

Label1: TLabel;

Label2: TLabel;

Label3: TLabel;

editAge: TEdit;

procedure btnSubmitClick(Sender: TObject);

procedure FormCreate(Sender: TObject);

private

{ Private declarations }

public

{ Public declarations }

end;

 

var

Form1: TForm1;

iMarsh: IMarshalStruct;

 

implementation

 

{$R *.DFM}

 

{----------------------------------------------------------------------

- Function: Send data to server

----------------------------------------------------------------------}

procedure TForm1.btnSubmitClick(Sender: TObject);

var

data: _Person;

begin

data.firstname := StringToOleStr(editFirstName.Text);

data.lastname := StringToOleStr(editLastName.Text);

data.age := StrToInt(editAge.Text);

 

OleCheck(iMarsh.GetPerson(data));

end;

 

{----------------------------------------------------------------------

- Function: Connect to server and set default values

----------------------------------------------------------------------}

procedure TForm1.FormCreate(Sender: TObject);

begin

editFirstName.Text := 'Palle';

editLastName.Text := 'Pophår';

editAge.Text := '12';

iMarsh := CoMarshalStruct.Create;

end;

 

end.