我们的团队正在编写一些代码,要求我们与联网设备进行交互。该设备使用专有协议,制造商为我们提供了OCX控件形式的接口库(即ActiveX控件)。
我尝试使用ActiveX控件有几个错误的开始,例如使用C ++ / CLI包装的本机C ++(MFC),包含在C#中,我了解到我可以将控件拖放到winforms表单中,还有一些包装器代码将自动生成。因此,我将控件放在一个空的表单中,并将其方法和事件存在于表单上,并打算将此表单作为控件的代理/包装类。
我现在遇到的问题是设备每隔几秒钟就会发回一个数据包来报告其状态。这应该会导致ActiveX控件引发一个事件,但是如果表单在Application
内运行,则只会引发这些事件:
Application.Run(new Form());
为了在控制台应用程序或单元测试中使用表单类,我尝试过这样的事情:
Var proxy = new Form();
Task.Run(() => { Application.Run(proxy); };
proxy.SomeMethod();
但这引发了一个例外:跨线程操作无效:控制' Form1'从在
上创建的线程以外的线程访问由于代理类最终将在Windows服务中运行,因此这是一个交易破坏者。如何启用ActiveX控件来引发事件而不在应用程序中托管其表单?
答案 0 :(得分:3)
该片段提供的指导非常少,但不止一种方式肯定是错误的。它为了解决真正的问题而过早死亡。 "跨线程操作无效"异常是尝试在工作线程拥有的对象上调用SomeMethod()方法的简单结果。您必须使用Begin / Invoke()方法来避免这种情况发生。在尝试使用它之前,还必须确保工作线程正在运行并且控件已正确初始化。
更大的问题是该线程不适合支持ActiveX控件。该线程必须标记为STA(单线程单元),这是一个承诺,您将为非线程安全的代码提供一个好客的家。像任何ActiveX控件一样。实现STA合同需要抽取消息循环(Application.Run)并且永远不会阻塞线程。 Task
没有创建这样的线程,线程池线程不能标记为STA。您需要custom TaskScheduler或只需一个普通线程,以便您可以调用SetApartmentState() method。
一些帮助您开始使用的示例代码:
using System;
using System.Threading;
using System.Windows.Forms;
class ActiveXHost : Form {
public ActiveXHost(Control control, bool hidden = false) {
if (control.IsHandleCreated) throw new InvalidOperationException("Control already committed to wrong thread");
if (hidden) this.Opacity = 0;
this.ShowInTaskbar = false;
using (initDone = new ManualResetEvent(false)) {
thread = new Thread((_) => {
this.Controls.Add(control);
Application.Run(this);
});
thread.IsBackground = true;
thread.SetApartmentState(ApartmentState.STA);
thread.Start();
initDone.WaitOne();
}
}
public void Execute(Action action) {
this.BeginInvoke(action);
}
public TResult Execute<TResult>(Func<TResult> action) {
return (TResult)this.Invoke(action);
}
protected override void OnLoad(EventArgs e) {
base.OnLoad(e);
initDone.Set();
}
protected override void Dispose(bool disposing) {
if (disposing && thread != null) {
this.Invoke(new Action(() => {
base.Dispose(disposing);
Application.ExitThread();
thread = null;
}));
}
}
private Thread thread;
private ManualResetEvent initDone;
}
构造函数负责创建一个合适的STA线程并负责与该线程的互锁,确保在线程启动并运行并且ActiveX控件准备开始生成事件之前它不会完成。如果你得到InvalidOperationException,那么初始化控件的方式有些不对,通过订阅控件的HandleCreated事件来诊断它。
我添加了Execute()方法,让您快速正确地调用SomeMethod()。
使用Dispose()方法来销毁控件并终止线程。
从服务中,您通常会使用以下内容:
ActiveXHost host;
protected override void OnStart(string[] args) {
var ctl = SomeAxHostWrapper();
host = new ActiveXHost(ctl);
ctl.HasMessage += MessageReceived;
}
protected override void OnStop() {
host.Dispose();
host = null;
}
请记住,服务并不是ActiveX控件的好客环境。它们在会话0中运行,这是一个具有小型桌面堆的会话。这可能会使您的服务失败,并出现难以理解的0xC0000142异常。背景资料is here