Solved

Using a COM Object in different threads?

Posted on 2003-12-11
4
1,117 Views
Last Modified: 2010-05-18
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
Comment
Question by:Wyverex
  • 2
4 Comments
 
LVL 26

Accepted Solution

by:
Russell Libby earned 250 total points
ID: 9920852

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
 
LVL 12

Expert Comment

by:Lee_Nover
ID: 9926399
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
 

Author Comment

by:Wyverex
ID: 9927820
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
 

Author Comment

by:Wyverex
ID: 9927942
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

Better Security Awareness With Threat Intelligence

See how one of the leading financial services organizations uses Recorded Future as part of a holistic threat intelligence program to promote security awareness and proactively and efficiently identify threats.

Join & Write a Comment

Introduction The parallel port is a very commonly known port, it was widely used to connect a printer to the PC, if you look at the back of your computer, for those who don't have newer computers, there will be a port with 25 pins and a small print…
Have you ever had your Delphi form/application just hanging while waiting for data to load? This is the article to read if you want to learn some things about adding threads for data loading in the background. First, I'll setup a general applica…
Polish reports in Access so they look terrific. Take yourself to another level. Equations, Back Color, Alternate Back Color. Write easy VBA Code. Tighten space to use less pages. Launch report from a menu, considering criteria only when it is filled…
You have products, that come in variants and want to set different prices for them? Watch this micro tutorial that describes how to configure prices for Magento super attributes. Assigning simple products to configurable: We assigned simple products…

744 members asked questions and received personalized solutions in the past 7 days.

Join the community of 500,000 technology professionals and ask your questions.

Join & Ask a Question

Need Help in Real-Time?

Connect with top rated Experts

11 Experts available now in Live!

Get 1:1 Help Now