我正在使用来自第三方库的COM对象来生成定期事件。当我使用Winforms应用程序中的库,将对象作为类成员并在主窗体线程中创建它时,一切正常。但是,如果我从另一个线程创建对象,我不会收到任何事件。
我的猜测是我需要在用于创建对象的同一个线程中有某种事件循环。
我需要在控制台应用程序中使用此对象。我想我可以使用Application.DoEvents,但我不想在控制台App中包含Winforms命名空间。
我该如何解决这个问题?
更新3(2011-06-15):供应商终于回答了。简而言之,他们说Application.Run创建的消息泵与Thread.Join创建的消息泵之间存在一些差异,但他们不知道这有什么区别。
我同意他们的意见;任何关于此事的光明都会非常感激。
更新:
从理查德评论到mdm回答:
如果其他组件是单线程并从MTA实例化,则Windows将创建工作线程+窗口+消息泵并执行必要的编组。
尝试遵循他的建议,我正在做以下事情:
更新2:
我改变了JoãoAngelo回答后的代码。
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();
}
答案 0 :(得分:5)
如果您正在使用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();
}
}
您对以前版本的代码有几点评论:您在 CreateObject和MycomObjectWrapper构造函数中调用了Wait();似乎你应该只有一个 - 如果你有两个,当调用m_Event.Set()时,只有其中一个会被释放,而另一个仍然在等待。另外,建议添加一些调试代码,以便了解您获得了多少。这样你至少可以告诉你是否从COM获得事件,并且单独地说,你是否成功地将它传回主线程。如果对象在注册表中标记为中性或两者都标记,那么从MTA创建它们应该没有问题。
答案 1 :(得分:3)
IIRC,COM事件需要一个事件循环才能工作,这会抽取消息并调用Win32 GetMessage
函数。
Winforms为您完成此操作,或者您可以使用Win32调用来模拟它。 This question/answer has a good example you can build on
答案 2 :(得分:3)
正如其他答案中所述,STA COM组件需要运行消息循环,以便将其他线程中发生的调用正确封送到拥有该组件的STA线程。
在Windows窗体中,您可以免费获得消息循环,但是在控制台应用程序中,您必须通过在拥有COM组件的线程上调用Thread.CurrentThread.Join
来显式地执行此操作,这可能也是应用程序的主线程。该主题必须是STA。
在Thread.Join
的MSDN条目中,您可以看到这是您想要的:
阻塞调用线程,直到线程终止,同时继续执行标准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 :(得分:1)
我认为以下内容应该有效:
[STAThread]
Main(...)
{
var comObject = new YourComObject();
comObject.Event += EventHandler;
Console.WriteLine("Press enter to exit.");
Console.ReadLine();
}
void EventHandler(...)
{
// Handle the event
}
答案 4 :(得分:1)
您是否定义了线程公寓模型?
[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);
}
在线程中,只需等待事件发出信号即可终止。当线程正在等待事件时,我认为它应该在线程循环上处理消息。
答案 5 :(得分:1)
你可以试试这个:
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(/*...*/)
{
// ...
}
}