如何在 C# Background worker 中访问 C++ COM 对象?
How to access C++ COM Object in C# Background worker?
我正在开发一个通用的 c# COM 组件 (dll),它可以被任何 COM 客户端使用。作为其中的一部分,我公开了一个名为 ICallback.The 的传出接口,客户端使用我的 dll 将实现这些方法并通过传入接口方法
提供 com 对象
例如,
下面是我的 c# dll 公开的 2 个接口
interface IMyInInterface
{
void Initialize(ICallback object);
}
interface ICallback
{
void doSomething();
}
我的 c# dll 实现了 IMyInInterface 。
因此客户端应用程序调用 Initialize 方法并将 ICallback 指针传递给 IMyInInterface 实现。
我的客户端是 C++ atl dll,它具有 ICallback 的实现。
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**
}
}
失败并出现未找到接口异常。
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)).
堆栈跟踪:
at System.StubHelpers.StubHelpers.GetCOMIPFromRCW(Object objSrc,
IntPtr pCPCMD, IntPtr& ppTarget, Boolean& pfNeedsRelease) at
MyDLL.ICallback.DoSomething()
如何使用后台工作者的com对象?甚至我也尝试从线程中将公寓设置为 STA 来做一些事情。发生同样的错误。
问题是代码 运行 在 BackgroundWorker
中,运行 位于线程池中的线程上,其中未设置 COM 单元模型。
我建议您从专用线程调用您的界面。在调用 Thread.Start()
.
之前使用 Thread.SetApartmentState(ApartmentState.STA);
此外,您传递给 Initialize
的对象必须在 相同的 线程中创建。如果不是,则此类对象需要在特殊的 was 中编组。查找更多详细信息 here。
我找到了这个问题的原因,这个问题是由于 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 与当前上下文相同。
我正在开发一个通用的 c# COM 组件 (dll),它可以被任何 COM 客户端使用。作为其中的一部分,我公开了一个名为 ICallback.The 的传出接口,客户端使用我的 dll 将实现这些方法并通过传入接口方法
提供 com 对象例如,
下面是我的 c# dll 公开的 2 个接口
interface IMyInInterface
{
void Initialize(ICallback object);
}
interface ICallback
{
void doSomething();
}
我的 c# dll 实现了 IMyInInterface 。
因此客户端应用程序调用 Initialize 方法并将 ICallback 指针传递给 IMyInInterface 实现。
我的客户端是 C++ atl dll,它具有 ICallback 的实现。
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**
}
}
失败并出现未找到接口异常。
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)).
堆栈跟踪:
at System.StubHelpers.StubHelpers.GetCOMIPFromRCW(Object objSrc, IntPtr pCPCMD, IntPtr& ppTarget, Boolean& pfNeedsRelease) at MyDLL.ICallback.DoSomething()
如何使用后台工作者的com对象?甚至我也尝试从线程中将公寓设置为 STA 来做一些事情。发生同样的错误。
问题是代码 运行 在 BackgroundWorker
中,运行 位于线程池中的线程上,其中未设置 COM 单元模型。
我建议您从专用线程调用您的界面。在调用 Thread.Start()
.
Thread.SetApartmentState(ApartmentState.STA);
此外,您传递给 Initialize
的对象必须在 相同的 线程中创建。如果不是,则此类对象需要在特殊的 was 中编组。查找更多详细信息 here。
我找到了这个问题的原因,这个问题是由于 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 与当前上下文相同。