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

0

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.

c#
multithreading
com
marshalling
mta
asked on Stack Overflow Feb 22, 2019 by Rajesh Subramanian • edited Mar 3, 2019 by Rajesh Subramanian

2 Answers

0

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.

answered on Stack Overflow Mar 3, 2019 by Rajesh Subramanian
-1

The problem is that code, run in a BackgroundWorker, runs on a thread in the thread pool, where the COM apartment model is not set.

I recommend you call your interface from a dedicated thread. Use Thread.SetApartmentState(ApartmentState.STA); before calling Thread.Start().

Also, the object that you pass to Initialize must be created in the same thread. If not, such objects need to be marshaled in a special was. Find more details here.

answered on Stack Overflow Feb 24, 2019 by Nick

User contributions licensed under CC BY-SA 3.0