14

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();
        }
raven
  • 2,574
  • 2
  • 27
  • 49
  • What's the threading model of the 3rd party component? What's the threading model of your COM code receiving the events? What COM apartment are you running in? – Richard May 31 '11 at 09:44
  • @Richard: I'm running in STA. How can I check the model of the 3rd party component? and my COM code? What COM code? – raven May 31 '11 at 10:04
  • The threading model of the 3rd party component will be in the registry. "You're COM code": the implementation of the interface you pass to the 3rd party component for it to call back with events. – Richard May 31 '11 at 11:49
  • @Richard: I'm not sure. I have found references to the object under HKCR/CLSID/{UUID}/InProcServer32 with different UUIDs. Some of them have ThreadModel = "Neutral", some have ThreadModel = "both". Regarding my COM code, I still don't know what you are talking about. I just instantiate the object in my class and add an event handler, as in Daniel Hilgarth code... – raven May 31 '11 at 16:10

6 Answers6

6

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.

BrendanMcK
  • 14,252
  • 45
  • 54
  • thanks for your answer. I was using breakpoints to check for events; the ctor() wait call was only so the main thread didn't ended. Anyway I have copied you code and it gets stuck in "Main thread waiting...", the event isn't called. Meanwhile, I'm still waiting for an answer from the vendor. – raven Jun 03 '11 at 08:13
4

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.

João Angelo
  • 56,552
  • 12
  • 145
  • 147
  • João: Thank you very much. I find your answer very useful, but in this case it doesn't work; I guess it's not an STA. If that's the case, what can I do? – raven Jun 01 '11 at 10:35
  • 1
    I don't think it is a good idea to never return from the ctor of your class. You should call the `Wait` method from outside the ctor. – Daniel Hilgarth Jun 01 '11 at 11:09
  • @Daniel: You're right, but this was just a quick test to check if things work. – raven Jun 01 '11 at 11:29
  • 1
    This just can't work, you need to wait on a windows message loop, not wait "on nothing". – Simon Mourier Jun 01 '11 at 12:37
  • @Simon Mourier, you're waiting on Thread.Join which performs standard COM and SendMessage pumping. – João Angelo Jun 01 '11 at 12:50
  • 1
    @Simon Mourier, doesn't work for what the OP is trying to do and with the COM component he is using. If you have doubts about it I can give you an example of it working with Word automation. – João Angelo Jun 01 '11 at 13:00
  • @João, I'll try to get support from the vendor. I will update when I have an answer. Thank you a lot. – raven Jun 01 '11 at 13:51
  • I wasn't able to solve my problem, but I think this answers the question in a general way, so I'll accept it. – raven Nov 18 '14 at 13:07
  • 1
    Interesting that that MSDN page explicitly says not to do what you are doing ;-) "You should never call the Join method of the Thread object that represents the current thread from the current thread. This causes your app to hang because the current thread waits upon itself indefinitely, " – Mike Nov 01 '17 at 14:09
  • @Mike Well-noticed, Mike. Futhermore, how is one to follow OP's recommendation periodically to call `Thread.CurrentThread.Join()` if this call blocks until the termination of the thread? This answer is useful because it brings up `Thread.CurrentThread.Join()`, but incorrect... – Anton Shepelev Jan 27 '21 at 15:38
  • One solution: use time-limited [`Thread.Join(Int32)`](https://learn.microsoft.com/en-us/dotnet/api/system.threading.thread.join?view=net-5.0#System_Threading_Thread_Join_System_Int32_) in a loop. – Anton Shepelev Jan 27 '21 at 15:47
3

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.

Community
  • 1
  • 1
mdm
  • 12,480
  • 5
  • 34
  • 53
  • 2
    IIRC another option would be to do the work in another thread running in the MTA. In the MTA COM calls are made directly and don't require a Window, message queue and message pump. – Richard May 31 '11 at 11:50
  • Good call - but don't COM objects have to be built specifically to work in one mode or the other? What if the COM object cannot be changed by OP? – mdm May 31 '11 at 12:06
  • Which, BTW, cannot. Good to know anyway. – raven May 31 '11 at 16:05
  • 1
    @mdm: 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. – Richard May 31 '11 at 16:17
  • @Richard: I have tried to do what you say, with no luck. See updated question. – raven Jun 01 '11 at 08:39
  • @Jaime: If I had symbols for the 3rd party component I'd go in with a native debugger. Otherwise I'd be tempted to create a test component that matches its model and enough of a callback interface to understand what's going on. – Richard Jun 01 '11 at 12:41
  • @Richard, I'd rather don't go that route if I can avoid it. I'm getting in contact with the vendor, we'll see what they have to say. Thank you very much anyway. – raven Jun 01 '11 at 13:53
  • @Richard - sorry for being a necromancer, but can you please give a link to the automatic generation of the message pump in MTA console apps? I want to dig in further... – bavaza Jan 29 '14 at 15:11
  • @bavaza MTAs do not use message pumps. Each component has to be thread safe so is called directly without the intermediary of a message pump. – Richard Jan 30 '14 at 08:37
  • @Richard - thanks for reviving this. Then what did you mean by: "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."? Won't the CLR automagically create an STA with a message pump for me, once I instantiate the COM object from the main thread? – bavaza Jan 30 '14 at 08:41
  • @bavaza Depends. If the current (creator) thread is in the MTA and there is no other STA then COM will spin up a worker thread in a (new) STA with the neccessary infrastructure. If the current thread is in an STA then the new component is created in the local STA and it is up to the current thread to pump messages. As far as I am aware the CLR doesn't do anything extra here: if COM doesn't message pump then you must. (Comments are really too short for this, and your getting to details I've not worked with for a long time. You really need a new question fully detailed for your case.} – Richard Jan 30 '14 at 09:42
  • @Richard - there is a new question: http://stackoverflow.com/questions/21451313/an-mta-console-application-calling-an-sta-com-object-from-multiple-threads – bavaza Jan 30 '14 at 10:02
1

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
}
Daniel Hilgarth
  • 171,043
  • 40
  • 335
  • 443
1

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.

Haplo
  • 1,368
  • 9
  • 18
1

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(/*...*/)
    {
        // ...
    }
}
Simon Mourier
  • 132,049
  • 21
  • 248
  • 298
  • 1
    The OP is inclined not to add the `System.Windows.Forms` reference to the console application project and since you can pump messages without that reference I tend to agree with him. – João Angelo Jun 01 '11 at 09:53
  • 1
    @Simon, that works, but as João wrote, I'm trying to avoid the Winforms reference. Thank you anyway. – raven Jun 01 '11 at 10:25
  • @Jaime - If it just works and you only use this method which is precisely designed for this, I don't see the problem referencing Windows forms, since you already add references to COM objects which rely on windows infrastructure to work. PS: this is not the same as Application.DoEvents. – Simon Mourier Jun 01 '11 at 12:50
  • @Simon, this is what I'm doing for now. But what if I need to recreate the COM object? Wouldn't I need to terminate the Application.Run thread? How can I do that? – raven Jun 01 '11 at 14:00
  • @Jaime - Application.Run is not a thread, it's a modal windows message loop (PeekMessage, TranslateMessage, DispatchMessage), independent from objects that ensure windows message can flow in the app. You can exit the loop with a call to the PostQuitMessage windows function (you can use P/Invoke to declare it). What problem do you have (or think will have) with object re-creation? – Simon Mourier Jun 01 '11 at 14:22
  • @Simon, I know it's not a thread itself, but it *blocks* the calling thread, and I didn't know how to end the loop. I have been able to do it with PostQuitMessage, so I think all my problems are solved now, although the whole solution seems a bit *clumsy* to me; I'll have to add a Winforms timer to check periodically if I have to call PostQuitMessage, as I need to do it from the same thread that is 'blocked' under Application.Run. However, if I can't find a better solution, this is good enough. Thank you a lot! – raven Jun 01 '11 at 15:36
  • @Jaime - In terms of plain ole' native Windows programming, this does not seems so clumsy. With a regular Window/Winform, the PostQuitMessage is done when you click on the Close button. In your case, you just need to define *when and how* you want to call it. Glad it works anyway :-) – Simon Mourier Jun 01 '11 at 16:58
  • @Simon, you make it sound very simple, but unless I'm mistaken, after calling Application.Run, I don't have a way to tell the main thread "you, call PostQuitMessage"; the simplest method I can think of is having a timer, which is, at the very least, cumbersome and ugly. Maybe there's a simpler way and I'm just being obstinate. – raven Jun 03 '11 at 08:18
  • @Jaime - Sure, you have to call PQM from somewhere, but hmm... I still don't understand the problem. When is this console app supposed to terminate? What is the event that defines the process should terminate? – Simon Mourier Jun 03 '11 at 10:11
  • @Simon, it should be a windows service, so PQM would be call in the OnStop() method from the ServiceBase class, and perhaps from a WCF (or similar) service call. I don't think I can make assumptions about what thread the calls will be made on. – raven Jun 06 '11 at 14:14
  • @Jaime - Ok, I got it. So, yes, you can just do a PQM on the OnStop method. And yes, OnStop can be called from any thread, but PQM should work fine this way. – Simon Mourier Jun 06 '11 at 15:03
  • @Simon - thank you a lot for your efforts. There's still the WCF case. Maybe I can find a workaround, anyway. I'll tell you how it went when I check the call fom OnStop. – raven Jun 06 '11 at 16:32