Search code examples
c#multithreadingcommarshallingmta

How to access C++ COM Object in C# Background worker?


I am developing a common c# COM component (dll) which can be consumed by any COM client. As part of this I m exposing a outgoing interface called ICallback.The client which is using my dll will implement the methods and provide the com object via incoming interface method,

For example,

Below are 2 interfaces exposed by my c# dll

interface IMyInInterface 
 {
      void Initialize(ICallback object);
 }

 interface ICallback
{
      void doSomething();
}

my c# dll implements IMyInInterface .

So client application calls Initialize method and passes ICallback pointer to IMyInInterface implementation .

My client is C++ atl dll which has implementation for ICallback.

C++ Call:

         .... 
         /* Code to create callbackImpl goes here */

         MyImplObj.Initialize(callbackImpl);

My c# dll is wpf dll. So when ICallback object is accessed in UI thread then it can able to call ICallback.Dosomething().It works fine.

C# Implementation:

class MyImpl : IMyInterface
{

...
            ICallback _Mycallback = null;

            void Initialize(ICallback callback)
             {
                 _MyCallback = callback         
             }

              Button_Click()
              { 
                 _Mycallback.DoSomething();       **// This works fine**
              } 
}

But Since Dosomething() is a long running task, I wanted to do this work in parllel, so I am using BackgroundWorker for this.

When the ICallback.Dosomething() is called from background worker as below, I am getting exception as the Interface not found,

class MyImpl : IMyInterface
    {

    ...
                ICallback _Mycallback = null;

                void Initialize(ICallback callback)
                 {
                     _MyCallback = callback         
                 }

                  Button_Click()
                  { 
                     //Code to create Background worker //

                    BackgroundWorker bkgrwkr = new BackgroundWorker();
                    bkgrwkr.WorkerReportsProgress = true;
                    bkgrwkr.WorkerSupportsCancellation = true;            
                    bkgrwkr.DoWork += new DoWorkEventHandler(Worker_DoWork);

                    .....

                  } 

                  void Worker_DoWork()
                  {
                        _Mycallback.DoSomething();       **// This Fails**
                  }
    }    

it fails with interface not found exception.

Unable to cast COM object of type 'System.__ComObject' to interface type 'ICallback'. This operation failed because the QueryInterface call on the COM component for the interface with IID '{3B7C00E3-C145-4195-B9B4-984EAAC8954D}' failed due to the following error: No such interface supported (Exception from HRESULT: 0x80004002 (E_NOINTERFACE)).

Stack Trace:

at System.StubHelpers.StubHelpers.GetCOMIPFromRCW(Object objSrc, IntPtr pCPCMD, IntPtr& ppTarget, Boolean& pfNeedsRelease) at MyDLL.ICallback.DoSomething()

How to use com objects from Background worker? Even I tried to do something from Thread with appartment set as STA. the same error occurs.


Solution

  • I found the cause for this issue, The issue is due to BackgroundWorker uses Appartment as MTA. When I tried to get apartment state inside the Worker_DoWork using System.Threading.Thread.CurrentThread.GetApartmentState() method, it returned MTA. This is root cause for the issue,

    Now I have resolved the issue using Task as below, Using task the parallel execution also achieved.

           Button_Click()
           {  
               Task workerTask = Task.Factory.StartNew(
               () =>
               {
                   DoWorkDelegate();
               }
               , CancellationToken.None
               , TaskCreationOptions.None
               , TaskScheduler.FromCurrentSynchronizationContext()
               ); 
    
               workerTask.wait();
           }
    
           void DoWorkDelegate()
           {
            _Mycallback.DoSomething();     // This works 
           }
    

    Passing TaskScheduler.FromCurrentSynchronizationContext() resolves the issue. This ensures sychronizationContext of Task as same as current context.