如何在C#后台工作程序中访问C ++ COM对象?

时间:2019-02-22 18:07:04

标签: c# multithreading com marshalling mta

我正在开发一个通用的c#COM组件(dll),任何COM客户端都可以使用它。在此过程中,我将公开一个名为ICallback的传出接口。使用我的dll的客户端将实现这些方法并通过传入的接口方法

提供com对象。

例如,

下面是我的C#dll公开的2个接口

interface IMyInInterface 
 {
      void Initialize(ICallback object);
 }

 interface ICallback
{
      void doSomething();
}

我的c#dll实现IMyInInterface。

因此,客户端应用程序调用Initialize方法,并将ICallback指针传递给IMyInInterface实现。

我的客户端是具有ICallback实现的C ++ atl dll。

C ++调用:

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

         MyImplObj.Initialize(callbackImpl);

我的c#dll是wpf dll。因此,当在UI线程中访问ICallback对象时,它可以调用ICallback.Dosomething()。它工作正常。

C#实现:

class MyImpl : IMyInterface
{

...
            ICallback _Mycallback = null;

            void Initialize(ICallback callback)
             {
                 _MyCallback = callback         
             }

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

但是,由于Dosomething()是一项长期运行的任务,因此我想并行执行此工作,因此我正在为此使用BackgroundWorker。

当从后台工作人员调用ICallback.Dosomething()时,如下所示,由于未找到接口,我得到了异常,

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**
                  }
    }    

它失败,并且找不到接口异常。

  

无法将类型为'System .__ ComObject'的COM对象转换为接口   输入“ ICallback”。此操作失败,因为QueryInterface   调用带有IID的接口的COM组件   “ {3B7C00E3-C145-4195-B9B4-984EAAC8954D}”由于以下原因而失败   错误:不支持此类接口(HRESULT的异常:0x80004002   (E_NOINTERFACE)。

堆栈跟踪:

  

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

如何使用Background Worker中的com对象?甚至我也尝试通过Thread将单元设置为STA来做某事。发生相同的错误。

2 个答案:

答案 0 :(得分:0)

我找到了此问题的原因,该问题是由于BackgroundWorker使用Appartment作为MTA引起的。当我尝试使用System.Threading.Thread.CurrentThread.GetApartmentState()方法获取Worker_DoWork内的公寓状态时,它返回MTA。这是问题的根本原因,

现在我已经使用Task如下解决了这个问题,使用Task也可以实现并行执行。

       Button_Click()
       {  
           Task workerTask = Task.Factory.StartNew(
           () =>
           {
               DoWorkDelegate();
           }
           , CancellationToken.None
           , TaskCreationOptions.None
           , TaskScheduler.FromCurrentSynchronizationContext()
           ); 

           workerTask.wait();
       }

       void DoWorkDelegate()
       {
        _Mycallback.DoSomething();     // This works 
       }

传递TaskScheduler.FromCurrentSynchronizationContext()可解决此问题。这样可以确保Task的sychronizationContext与当前上下文相同。

答案 1 :(得分:-1)

问题在于,在filter(val => { const equal = isEqual(cachedValue, val); cachedValue = val; return !equal; }) 中运行的代码在未设置COM公寓模型的线程池中的线程上运行。

我建议您从专用线程调用接口。调用BackgroundWorker之前,先使用Thread.SetApartmentState(ApartmentState.STA);

此外,必须在相同线程中创建传递给Thread.Start()的对象。如果没有,则需要将这些对象编组为特殊的对象。查找更多详细信息here