可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
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();
}
回答1:
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.
回答2:
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.
回答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.
回答4:
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
}
回答5:
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.
回答6:
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(/*...*/)
{
// ...
}
}