繁体   English   中英

启用ActiveX控件以在不在System.Windows.Forms.Application中运行的情况下引发事件

[英]Enable ActiveX control to raise events without running in a System.Windows.Forms.Application

我们的团队正在编写一些代码,要求我们与联网设备进行交互。 该设备使用专有协议,制造商为我们提供了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控件来引发事件而不在应用程序中托管其表单?

该片段提供了很少的指导,但它不止一种方式肯定是错误的。 它为了解决真正的问题而过早死亡。 “跨线程操作无效”异常是尝试在工作线程拥有的对象上调用SomeMethod()方法的简单结果。 必须使用Begin / Invoke()方法来避免这种情况发生。 在尝试使用它之前,还必须确保工作线程正在运行并且控件已正确初始化。

更大的问题是该线程不适合支持ActiveX控件。 该线程必须标记为STA(单线程单元),这是一个承诺,您将为非线程安全的代码提供一个好客的家。 像任何ActiveX控件一样。 实现STA合同需要抽取消息循环(Application.Run)并且永远不会阻塞线程。 一个Task没有创建这样的线程,线程池线程不能标记为STA。 您将需要一个自定义TaskScheduler或只需一个普通线程,以便您可以调用其SetApartmentState()方法

一些示例代码可帮助您开始:

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异常。 Backgrounder 就在这里

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM