[Okta Webinar] Learn how to a build a cloud-first strategyRegister Now

x
  • Status: Solved
  • Priority: Medium
  • Security: Public
  • Views: 1155
  • Last Modified:

Using a COM Object in different threads?

Hi all,

I'm writing a multi-tier application using a TRemoteDataModule in my app server.
In the client I use a TDComConnection to connect to the server's IAppServer interface.
So far all works fine when I call any method of the app server in my main thread.

But since some calls take a significant amount of time, I wanted to make these calls in
a separate thread. (At first I just displayed a little dialog with some "Please wait..." message
in it, so that I didn't need a separate thread. But this approach is not very satisfying for me)

So I wrote a new thread and placed the calls into it. After some time I was able to call the needed
methods from this thread (BTW: Why do I have to call CoInitialize in this thread again? )

The problem is now, I'm also using a TClientDataSet in my client. And this DataSet is controlled by the
main thread. Whenever anything (be it the ClientDataSet or some other method ) tries to call
the app server now, I get an error message telling me that this interface has already been marshalled
for another thread.

So it seems that I can only call the IAppServer interface from *either* the main thread *or* the
separate thread. Is there a workaround so I can call it from both threads?

regards,
Wyverex
 
0
Wyverex
Asked:
Wyverex
  • 2
1 Solution
 
Russell LibbySoftware Engineer, Advisory Commented:

Wyverex,

Yes, there is a way to call this interface from both (or more) threads. You have 2 choices to use (I will post code for the easier of the 2, but could provide source for the other as well). And yes, the second thread should call CoInitialize again.

First (Global Technique):

1.) Create an instance of IGlobalInterfaceTable in both thread A and thread B (The object returned will be the same).
2.) Thread A should call its IGlobalInterfaceTable::RegisterInterfaceInGlobal to register your object's interface which will allow all theads in the process to access this object. This will pass back a cookie (integer)
3.) Thread B would call its IGlobalInterfaceTable::GetInterfaceFromGlobal using the cookie to get a usable interface to the object that was placed there.

Second (One shot):
1.) Thread A calls CoMarshalInterThreadInterfaceInStream to return a stream interface usable by thread B
2.) Thread B calls CoGetInterfaceAndReleaseStream to get the interface and release the stream.

The first is a little easier to use, and it allows the interface to be marshalled into any number of threads any number of times. The second is more of a one shot deal where one thread marshalls the interface into a stream, stream is passed to second thread, and the second thread gets the marshalled interface.

Here is the code unit that i use for multithread com apps. This allows me to easily (and safely) marshall interfaces across threads, where i only have to pass the other thread an integer value to retrieve the interface.

The only functions you need to deal with are:

GitRegister - Called by the thread that "owns" the object. Registers the interface in the global interface table.

GitGetInterface - Can be called by any other thread. Only needs the integer value (dwCookie) that was passed back during GitRegister. Gets the interface.

GitRevoke - Can be called by any thread using the the integer value (dwCookie) that was passed back during GitRegister. Removes the interface from the global table.

simple example usage:

var
  ppv1:         IDispatch;
  ppv2:         IDispatch;
  dwCookie:  DWORD;
begin

// get the interface from where ever

if GitRegister(ppv, IDispatch, dwCookie) then
 if GetGetInterface(dwCookie, IDispatch, ppv2) then
  GetRevoke(dwCookie)

end;

---------------------

Hope this helps,
Russell




unit comgit;

interface

uses
  Windows, ComObj, ActiveX;

// CLSID for global interface table
const
  CLSID_GlobalInterfaceTable:   TGUID =  '{00000323-0000-0000-C000-000000000046}';

// IGlobalInterfaceTable definition
type
  IGlobalInterfaceTable   =  interface(IUnknown)
     ['{00000146-0000-0000-C000-000000000046}']
     function RegisterInterfaceInGlobal(pUnk: IUnknown; const riid: TIID; out dwCookie: DWORD): HResult; stdcall;
     function RevokeInterfaceFromGlobal(dwCookie: DWORD): HResult; stdcall;
     function GetInterfaceFromGlobal(dwCookie: DWORD; const riid: TIID; out ppv): HResult; stdcall;
  end;

// Thread safe wrapper functions for marshalling of interfaces.
function GitRegister(pUnk: IUnknown; riid: TIID; out dwCookie: DWORD): Boolean;
function GitGetInterface(dwCookie: DWORD; const riid: TIID; out Obj): Boolean;
function GitRevoke(dwCookie: DWORD): Boolean;

implementation

// Protected declarations
var
  bGitCreated:   Boolean;
  pvGit:         IGlobalInterfaceTable;
  csGit:         TRTLCriticalSection;

// Make sure that a reference to the global interface table is always kept alive
function GitCreate: HResult;
begin
  EnterCriticalSection(csGit);
  try
     if (bGitCreated and Assigned(pvGit)) then
        result:=S_OK
     else
        result:=CoCreateInstance(CLSID_GlobalInterfaceTable, nil, CLSCTX_ALL, IGlobalInterfaceTable, pvGIT);
     bGitCreated:=(result = S_OK);
  finally
     LeaveCriticalSection(csGit);
  end;
end;

function GitRegister(pUnk: IUnknown; riid: TIID; out dwCookie: DWORD): Boolean;
var  pvlGit:     IGlobalInterfaceTable;
begin
  EnterCriticalSection(csGit);
  try
     if (GitCreate = S_OK) then
     begin
        if (CoCreateInstance(CLSID_GlobalInterfaceTable, nil, CLSCTX_ALL, IGlobalInterfaceTable, pvlGIT) = S_OK) then
        begin
           result:=(pvlGit.RegisterInterfaceInGlobal(pUnk, riid, dwCookie) = S_OK);
           pvlGit:=nil;
        end
        else
           result:=False;
     end
     else
        result:=False;
  finally
     LeaveCriticalSection(csGit);
  end;
end;

function GitGetInterface(dwCookie: DWORD; const riid: TIID; out Obj): Boolean;
var  pvlGit:     IGlobalInterfaceTable;
begin
  EnterCriticalSection(csGit);
  try
     if (GitCreate = S_OK) then
     begin
        if (CoCreateInstance(CLSID_GlobalInterfaceTable, nil, CLSCTX_ALL, IGlobalInterfaceTable, pvlGIT) = S_OK) then
        begin
           result:=(pvlGit.GetInterfaceFromGlobal(dwCookie, riid, Obj) = S_OK);
           pvlGit:=nil;
        end
        else
           result:=False;
     end
     else
        result:=False;
  finally
     LeaveCriticalSection(csGit);
  end;
end;

function GitRevoke(dwCookie: DWORD): Boolean;
var  pvlGit:     IGlobalInterfaceTable;
begin
  EnterCriticalSection(csGit);
  try
     if (GitCreate = S_OK) then
     begin
        if (CoCreateInstance(CLSID_GlobalInterfaceTable, nil, CLSCTX_ALL, IGlobalInterfaceTable, pvlGIT) = S_OK) then
        begin
           result:=(pvlGit.RevokeInterfaceFromGlobal(dwCookie) = S_OK);
           pvlGit:=nil;
        end
        else
           result:=False;
     end
     else
        result:=False;
  finally
     LeaveCriticalSection(csGit);
  end;
end;

initialization

  InitializeCriticalSection(csGit);
  bGitCreated:=False;
  pvGit:=nil;

finalization

  pvGit:=nil;
  DeleteCriticalSection(csGit);

end.
0
 
Lee_NoverCommented:
nice :)

I needed the same thing .. but since I was using RemObjects I used it's DSnap pack
with RO in <2 hours I did what I couldn't do in 2 weeks with DataSnap (dcom server in a service - also tried svcom)
0
 
WyverexAuthor Commented:
I still have a problem with registering the interface in the global table.
As I said I'm using a TDComConnection. So I tried the following in the main thread:

   if( GitRegister( dcomConnection.AppServer, IAppServer, dwCookie ) ) then
       blah blah....

But then
    result:=(pvlGit.RegisterInterfaceInGlobal(pUnk, riid, dwCookie) = S_OK);

returns false.

I guess it has something to do with the TIID. My AppServer exposes the interface
IFMAppServer. So I tried to use this statement instead ( after importing the type lib ):

   if( GitRegister( dcomConnection.AppServer, IFMAppServer, dwCookie ) ) then
       blah blah....

But that doesn't work either. Perhaps it's worth mentioning that although in the help
it is said that I can write something like

   with dcomConnection.AppServer as IFMAppServer do
      blah blah....

I always get a compiler error saying that this operand is not usable with this operand type.
So perhaps dcomConnection.AppServer is not correctly converted to an interface.
It's all very confusing.
0
 
WyverexAuthor Commented:
OK, that problem has solved itself.... I had to set dcomConnection.Connected to true before
acquiring the interface :-}

Well, thanks for your help, rllibby! It all works fine now.

regards,
Wyverex
0

Featured Post

What does it mean to be "Always On"?

Is your cloud always on? With an Always On cloud you won't have to worry about downtime for maintenance or software application code updates, ensuring that your bottom line isn't affected.

  • 2
Tackle projects and never again get stuck behind a technical roadblock.
Join Now