Как получить доступ к C++ COM-объекту в C# Фоновый рабочий?
Я разрабатываю общий компонент C# (dll), который может быть использован любым клиентом COM. Как часть этого я выставляю исходящий интерфейс под названием ICallback. Клиент, который использует мою dll, реализует методы и предоставляет объект com через метод входящего интерфейса,
Например,
Ниже приведены 2 интерфейса, представленные моим C# dll
interface IMyInInterface
{
void Initialize(ICallback object);
}
interface ICallback
{
void doSomething();
}
мой C# dll реализует IMyInInterface .
Таким образом, клиентское приложение вызывает метод Initialize и передает указатель ICallback в реализацию IMyInInterface .
Мой клиент - C++ atl dll, у которого есть реализация для ICallback.
C++ Call:
....
/* Code to create callbackImpl goes here */
MyImplObj.Initialize(callbackImpl);
Мой C# dll - это wpf dll. Таким образом, когда к объекту ICallback обращаются в потоке пользовательского интерфейса, он может вызывать ICallback.Dosomething(). Он работает нормально.
Реализация C#:
class MyImpl : IMyInterface
{
...
ICallback _Mycallback = null;
void Initialize(ICallback callback)
{
_MyCallback = callback
}
Button_Click()
{
_Mycallback.DoSomething(); **// This works fine**
}
}
Но так как Dosomething () - это долгосрочное задание, я хотел выполнить эту работу в parllel, поэтому я использую 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**
}
}
это терпит неудачу с интерфейсом не найденным исключением.
Невозможно привести объект COM типа "System.__ComObject" к типу интерфейса "ICallback". Эта операция завершилась неудачно, поскольку вызов QueryInterface для компонента COM для интерфейса с IID '{3B7C00E3-C145-4195-B9B4-984EAAC8954D}' завершился ошибкой из-за следующей ошибки: такой интерфейс не поддерживается (Исключение из HRESULT: 0x80004002 (E_NOINTERFACE)),
Трассировки стека:
в System.StubHelpers.StubHelpers.GetCOMIPFromRCW(Объект objSrc, IntPtr pCPCMD, IntPtr& ppTarget, Boolean& pfNeedsRelease) в MyDLL.ICallback.DoSomething()
Как использовать com-объекты из Background worker? Даже я пытался сделать что-то из Thread с квартирой, установленной как STA. такая же ошибка происходит.
2 ответа
Я нашел причину этой проблемы, проблема связана с тем, что BackgroundWorker использует Appartment в качестве MTA. Когда я попытался получить состояние квартиры внутри Worker_DoWork с помощью метода System.Threading.Thread.CurrentThread.GetApartmentState(), он вернул MTA. Это основная причина проблемы,
Теперь я решил проблему с помощью Задачи, как показано ниже. Используя задачу, также достигнуто параллельное выполнение.
Button_Click()
{
Task workerTask = Task.Factory.StartNew(
() =>
{
DoWorkDelegate();
}
, CancellationToken.None
, TaskCreationOptions.None
, TaskScheduler.FromCurrentSynchronizationContext()
);
workerTask.wait();
}
void DoWorkDelegate()
{
_Mycallback.DoSomething(); // This works
}
Передача TaskScheduler.FromCurrentSynchronizationContext() решает проблему. Это гарантирует sychronizationContext из Task так же, как текущий контекст.
Проблема в том, что код, запущенный в BackgroundWorker
, запускается в потоке в пуле потоков, где модель квартиры COM не установлена.
Я рекомендую вам позвонить на ваш интерфейс из специальной темы. использование Thread.SetApartmentState(ApartmentState.STA);
перед звонком Thread.Start()
,
Кроме того, объект, который вы передаете Initialize
должен быть создан в той же теме. Если нет, то такие объекты нужно составлять в особом порядке. Найти более подробную информацию здесь.