Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to handle COM events from a console application?

I'm using a COM object from a third party library that generates periodic events. When I use the library from a Winforms app, having the object as a class member and creating it in the main form thread, everything works. However, if I create the object from another thread, I don't receive any event.

My guess is that I need to have some kind of event loop in the same thread used to create the object.

I need to use this object from a console application. I guess I could use Application.DoEvents, but I'd rather not include the Winforms namespace in a console App.

How can I solve this problem?

Update 3 (2011-06-15): The vendor has answered at last. In short, they say there is some difference between the message pump created by Application.Run and the one created by Thread.Join, but they don't know what that difference is.

I agree with them; any light shed on this matter would be very appreciated.

Update:

From Richard comment to mdm answer:

if there other component is single threaded and instantiated from an MTA then Windows will create the worker thread + window + message pump and do the necessary marshalling.

Trying to follow his advice, I'm doing the following:

Update 2:

I'm changed the code following João Angelo answer.

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

I have also tried the following instead:

        public MyComObjectWrapper()
        {
            CreateObject();
        }
like image 555
raven Avatar asked May 31 '11 09:05

raven


2 Answers

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.

like image 135
BrendanMcK Avatar answered Oct 10 '22 15:10

BrendanMcK


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.

like image 37
João Angelo Avatar answered Oct 10 '22 15:10

João Angelo