How to handle COM events from a console application?

扶醉桌前 提交于 2019-11-27 20:45:49

As already stated in other answers STA COM components require a message loop to be run in order for calls happening in other threads be correctly marshaled to the STA thread that owns the component.

In Windows Forms you get the message loop for free, but in a console application you must do it explicitly by calling Thread.CurrentThread.Join on the thread that owns the COM component and that is probably also the main thread for the application. This thread must be STA.

From the MSDN entry of Thread.Join you can see that this is what you want:

Blocks the calling thread until a thread terminates, while continuing to perform standard COM and SendMessage pumping.

If you don't want to do anything else in the main console thread you just wait indefinitely, otherwise you can do other stuff while periodically calling Thread.CurrentThread.Join to pump messages.

Side-note: This assumes you're dealing with a STA COM component.


A simplified example:

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) { }
}

In this example the console application will be in a never ending loop that should do nothing more then respond to events from the COM component. If this does not work you should try to get support from the COM component vendor.

If you're using STA, then you're going to need a message loop one way or another. If you don't otherwise need a message loop, MTA is perhaps the simplest way to go, and is also the best for for a console-style application.

One thing to be aware of is that with MTA, it doesn't matter which thread created the object; all objects created by an MTA thread belong equally to all MTA threads. (Or, in COM speak, a process has exactly one Multi-Threaded Apartment, in which all MTA threads live.) What this means is that if you're taking the MTA approach, there's no need to create a separate thread at all - just create the object from the main thread. But you also need to be aware that incoming events will be delivered on a 'random' thread, so you'll have to take separate steps to communicate back to the main thread.

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

Couple of comments on the previous version of the code you had: you had calls to Wait() in both CreateObject and in the MycomObjectWrapper constructor; seems you should only have one - if you have two of them, only one of them will get released when m_Event.Set() is called, and the other will still be waiting. Also, suggest adding in some debugging code so you know how far you are getting. That way you can at least tell if you are getting the event from COM, and separately, whether you are successfully communicating that back to the main thread. If the objects are marked neutral or both in the registry, then there should be no problem creating them from a MTA.

mdm

IIRC, COM events require an event loop to work, something that pumps messages and calls the Win32 GetMessage function.

Winforms does this for you, or you can emulate it with Win32 calls. This question/answer has a good example you can build on.

I think the following should work:

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

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

Have you defined the thread apartment model?

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

In the thread, just wait for an Event to signal its termination. While the thread is waiting on the event, I think that it should process messages on the thread loop.

Could you try this:

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(/*...*/)
    {
        // ...
    }
}
标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!