Saturday, January 14, 2006

Free Threaded Marshaler

When writing a DataSnap appserver, you should probably try to make it thread-safe and register it to use the multi-threaded apartment (MTA) model (ThreadingModel = tmFree) because it gives you total control over synchronization and optimization of your code. But writing thread-safe code also requires more work. In some cases, it may be useful to register your appserver to use the single-threaded apartment (STA) model (ThreadingModel = tmApartment) and rely on COM to serialize calls from different threads into one thread "apartment" - the same thread which was used to create the instance.

Recently, after reviewing an external library I'm using in my appserver, and coming to suspect that it might not be thread-safe, I've chosen to use the STA model as a temporary workaround until I fix the problem.

Soon enough, I hit a problem - in the STA model, interface pointers cannot be freely passed from thread to thread - they need to be marshaled across thread boundaries, otherwise the call will fail with RPC_E_WRONG_THREAD error.
To marshal interface pointers across threads, you can use CoMarshalInterThreadInterfaceInStream . Another, very easy and convenient way is to aggregate the Free Threaded Marshaler.

Delphi makes aggregation a piece of cake! ;-) Here's a simple class which you can derive your appserver from (instead of deriving from TRemoteDataModule directly):

unit DataBkrEx;

interface

uses
Classes, ActiveX,
DataBkr;

type
TRemoteDataModuleEx = class(TRemoteDataModule, IMarshal)
private
FMarshal: IUnknown;
function GetMarshal: IMarshal;
property Marshal: IMarshal read GetMarshal implements IMarshal;
public
constructor Create(AOwner: TComponent); override;
destructor Destroy; override;
end;

implementation

uses ComObj;

{ TRemoteDataModuleEx private }

function TRemoteDataModuleEx.GetMarshal: IMarshal;
begin
if not Assigned(FMarshal) then
OleCheck(CoCreateFreeThreadedMarshaler(Self as IUnknown, FMarshal));
Result := FMarshal as IMarshal;
end;

{ TRemoteDataModuleEx public }

constructor TRemoteDataModuleEx.Create(AOwner: TComponent);
begin
inherited Create(AOwner);
FMarshal := nil;
end;

destructor TRemoteDataModuleEx.Destroy;
begin
FMarshal := nil;
inherited Destroy;
end;

end.
Note that I'm not checking for a controller object (in case the appserver itself is aggregated) but that's normally not used in DataSnap appservers. The correct implementation would also require a replacement for TComponentFactory in the VclCom unit.

No comments: