Как обрабатывать события COM из консольного приложения? - PullRequest
12 голосов
/ 31 мая 2011

Я использую 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();
        }

Ответы [ 6 ]

5 голосов
/ 03 июня 2011

Если вы используете 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 голосов
/ 01 июня 2011

Как уже указывалось в других ответах, 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-компонента.

3 голосов
/ 31 мая 2011

IIRC, события COM требуют, чтобы цикл событий работал, что-то, что качает сообщения и вызывает функцию Win32 GetMessage.

Winforms делает это за вас, или вы можете эмулировать это с вызовами Win32. Этот вопрос / ответ имеет хороший пример, на котором вы можете построить .

1 голос
/ 01 июня 2011

Не могли бы вы попробовать это:

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(/*...*/)
    {
        // ...
    }
}
1 голос
/ 31 мая 2011

Вы определили модель квартиры потока?

    [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);         
    }

В потоке, просто подождите, пока событие не сообщит о его завершении.Пока поток ожидает события, я думаю, что он должен обрабатывать сообщения в цикле потока.

1 голос
/ 31 мая 2011

Я думаю, что должно работать следующее:

[STAThread]
Main(...)
{
    var comObject = new YourComObject();
    comObject.Event += EventHandler;
    Console.WriteLine("Press enter to exit.");
    Console.ReadLine();
}

void EventHandler(...)
{
    // Handle the event
}
...