Как обрабатывать COM-события из консольного приложения?
Я использую COM-объект из сторонней библиотеки, которая генерирует периодические события. Когда я использую библиотеку из приложения Winforms, имея объект как член класса и создавая его в потоке основной формы, все работает. Однако, если я создаю объект из другого потока, я не получаю никакого события.
Я предполагаю, что мне нужен какой-то цикл событий в том же потоке, который использовался для создания объекта.
Мне нужно использовать этот объект из консольного приложения. Я предполагаю, что я мог бы использовать Application.DoEvents, но я бы предпочел не включать пространство имен Winforms в консольное приложение.
Как я могу решить эту проблему?
Обновление 3 (2011-06-15): Поставщик наконец ответил. Короче говоря, они говорят, что существует некоторая разница между насосом сообщений, созданным Application.Run и тем, который был создан Thread.Join, но они не знают, что это за разница.
Я согласен с ними; любой свет, пролитый на этот вопрос, будет очень оценен.
Обновление:
От комментария Ричарда до ответа mdm:
если другой компонент является однопоточным и создается из MTA, тогда Windows создаст рабочий поток + окно + насос сообщений и выполнит необходимую сортировку.
Пытаясь следовать его совету, я делаю следующее:
Обновление 2:
Я изменил код после ответа Жоао Анджело.
using System;
namespace ConsoleApplication2
{
class Program
{
[STAThread]
static void Main(string[] args)
{
MyComObjectWrapper wrapper = new MyComObjectWrapper();
}
}
class MyComObjectWrapper
{
MyComObject m_Object;
AutoResetEvent m_Event;
public MyComObjectWrapper()
{
m_Event = new System.Threading.AutoResetEvent(false);
System.Threading.Thread t = new System.Threading.Thread(() => CreateObject());
t.SetApartmentState (System.Threading.ApartmentState.STA);
t.Start();
Wait();
}
void ObjectEvt(/*...*/)
{
// ...
}
void Wait()
{
m_Event.WaitOne();
}
void CreateObject()
{
m_Object = new MyComObject();
m_Object.OnEvent += ObjectEvt;
System.Threading.Thread.CurrentThread.Join();
}
}
}
Я также попробовал следующее:
public MyComObjectWrapper()
{
CreateObject();
}
Ответы
Ответ 1
Как уже указывалось в других ответах, компонентам STA COM требуется, чтобы цикл сообщений запускался для того, чтобы вызовы, происходящие в других потоках, были правильно распределены по потоку STA, которому принадлежит этот компонент.
В Windows Forms вы получаете бесплатный цикл сообщений, но в консольном приложении вы должны сделать это явно, вызвав Thread.CurrentThread.Join
в потоке, который владеет COM-компонентом, и, вероятно, также является основным потоком для приложения. Этот поток должен быть STA.
Из записи MSDN Thread.Join
вы можете видеть, что это то, что вы хотите:
Блокирует вызывающий поток до тех пор, пока поток не завершится, продолжая выполнять стандартные вызовы COM и SendMessage.
Если вы не хотите ничего делать в основном потоке консоли, вы просто ждете бесконечно, иначе вы можете делать другие вещи, периодически нажимая Thread.CurrentThread.Join
для передачи сообщений.
Боковое примечание: предполагается, что вы имеете дело с компонентом STA COM.
Упрощенный пример:
class Program
{
[STAThread]
static void Main(string[] args)
{
var myComObj = new MyComObject();
myComObj.OnEvent += ObjectEvt;
Thread.CurrentThread.Join(); // Waits forever
}
static void ObjectEvt(object sender, EventArgs e) { }
}
В этом примере консольное приложение будет находиться в бесконечном цикле, который не должен больше ничего реагировать на события из COM-компонента. Если это не сработает, вы должны попытаться получить поддержку от поставщика компонентов COM.
Ответ 2
Если вы используете STA, вам понадобится цикл сообщений так или иначе. Если вам не нужен цикл сообщений, MTA - это, пожалуй, самый простой способ, и он также подходит для приложения в стиле консоли.
Одна вещь, о которой нужно знать, это то, что с помощью MTA не имеет значения, какой поток создал объект; все объекты, созданные потоком MTA, одинаково относятся ко всем потокам MTA. (Или, говорят в COM, процесс имеет ровно одну многопоточную квартиру, в которой живут все потоки MTA.) Это означает, что если вы принимаете подход MTA, нет необходимости создавать отдельный поток вообще - просто создайте объект из основного потока. Но вам также нужно знать, что входящие события будут передаваться по "случайному" потоку, поэтому вам придется предпринять отдельные шаги для связи с основным потоком.
using System;
using System.Threading;
class Program
{
static MyComObject m_Object;
static AutoResetEvent m_Event;
[MTAThread]
static void Main(string[] args)
{
m_Event = new AutoResetEvent(false);
m_Object = new MyComObject();
m_Object.OnEvent += ObjectEvt;
Console.WriteLine("Main thread waiting...");
m_Event.WaitOne();
Console.WriteLine("Main thread got event, exiting.");
// This exits after just one event; add loop or other logic to exit properly when appropriate.
}
void ObjectEvt(/*...*/)
{
Console.WriteLine("Received event, doing work...");
// ... note that this could be on any random COM thread.
Console.WriteLine("Done work, signalling event to notify main thread...");
m_Event.Set();
}
}
Несколько комментариев к предыдущей версии кода, который у вас был: у вас были вызовы Wait() как в CreateObject, так и в конструкторе MycomObjectWrapper; кажется, у вас должен быть только один - если у вас их два, только один из них будет освобожден при вызове m_Event.Set(), а другой все равно будет ждать. Кроме того, предложите добавить в некоторый код отладки, чтобы вы знали, как далеко вы получаете. Таким образом, вы можете, по крайней мере, сказать, получаете ли вы событие от COM и отдельно, успешно ли вы передаете это обратно в основной поток. Если объекты отмечены как нейтральные или оба в реестре, то не должно возникнуть проблем с их созданием из MTA.
Ответ 3
События IIRC, COM требуют, чтобы цикл событий работал, что-то, что накачивает сообщения и вызывает функцию Win32 GetMessage
.
Winforms делает это для вас, или вы можете эмулировать его с помощью вызовов Win32. Этот вопрос/ответ имеет хороший пример, на котором вы можете построить.
Ответ 4
Я думаю, что следующее должно работать:
[STAThread]
Main(...)
{
var comObject = new YourComObject();
comObject.Event += EventHandler;
Console.WriteLine("Press enter to exit.");
Console.ReadLine();
}
void EventHandler(...)
{
// Handle the event
}
Ответ 5
Вы определили модель квартиры нити?
[STAThread]
static void Main(string[] args)
{
// Create the thread that will manage the COM component
Thread th = new Thread(...);
// Before starting the thread
th.SetApartmentState (ApartmentState.STA);
}
В потоке просто подождите, пока событие не сообщит о его завершении. Пока поток ожидает событие, я думаю, что он должен обрабатывать сообщения в цикле потока.
Ответ 6
Не могли бы вы попробовать:
static class Program
{
MyComObject m_Object;
[STAThread]
static void Main()
{
m_Object = new MyComObject();
m_Object.OnEvent += ObjectEvt;
System.Windows.Forms.Application.Run();
}
void ObjectEvt(/*...*/)
{
// ...
}
}