简体   繁体   English

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

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

Our team is writing some code that requires us to interface with a networked device. 我们的团队正在编写一些代码,要求我们与联网设备进行交互。 The device uses a proprietary protocol, and the manufacturer has provided us with an interface library in the form of an OCX control (ie, an ActiveX control). 该设备使用专有协议,制造商为我们提供了OCX控件(即ActiveX控件)形式的接口库。

I had several false starts trying to use the ActiveX control, for example using native C++ (MFC) wrapped in C++/CLI, wrapped in see C#, I learned that I could drag and drop the control into winforms form, and some wrapper code would be auto-generated. 我有几个错误的开始尝试使用ActiveX控件,例如使用C ++ / CLI包装的本机C ++(MFC),包含在C#中,我了解到我可以将控件拖放到winforms表单中,并且一些包装器代码会自动生成。 So I put the control in an otherwise empty form and stubbed its methods and events out on the form, with the intention that this form will be the proxy/wrapper class for the control. 所以我将控件放在一个空的表单中,并将其方法和事件存在于表单上,目的是该表单将成为控件的代理/包装类。

The problem I'm having now is that the device reports its status by sending a packet back every few seconds. 我现在遇到的问题是设备每隔几秒钟就会发回一个数据包来报告其状态。 This is supposed to cause the ActiveX control to raise an event, but these events only get raised if the form is running inside an Application : 这应该会导致ActiveX控件引发一个事件,但只有在表单在Application运行时才会引发这些事件:

Application.Run(new Form());

For the purpose of using the form class in a console app or unit test, I've tried something like this: 为了在控制台应用程序或单元测试中使用表单类,我尝试过这样的事情:

Var proxy = new Form();
Task.Run(() => { Application.Run(proxy); };
proxy.SomeMethod(); 

But this throws an exception: Cross-thread operation not valid: Control 'Form1' accessed from a thread other than the thread it was created on 但这引发了一个异常: 跨线程操作无效:控制'Form1'从一个线程访问,而不是在它创建的线程上

Since the proxy class will eventually be running in a Windows service this is a deal breaker. 由于代理类最终将在Windows服务中运行,因此这是一个交易破坏者。 how do I enable the ActiveX control to raise events without hosting its form inside of an application? 如何启用ActiveX控件来引发事件而不在应用程序中托管其表单?

The snippet gives very little guidance but it is certainly wrong in more than one way. 该片段提供了很少的指导,但它不止一种方式肯定是错误的。 It died too early to get to the real problem. 它为了解决真正的问题而过早死亡。 The "Cross-thread operation not valid" exception is a simple consequence of trying to call SomeMethod() method on an object that is owned by a worker thread. “跨线程操作无效”异常是尝试在工作线程拥有的对象上调用SomeMethod()方法的简单结果。 You must use the Begin/Invoke() method to avoid this from happening. 必须使用Begin / Invoke()方法来避免这种情况发生。 You also must ensure that the worker thread is running and the control properly initialized before you try to use it. 在尝试使用它之前,还必须确保工作线程正在运行并且控件已正确初始化。

The much bigger issue is that the thread is not suitable to support an ActiveX control. 更大的问题是该线程不适合支持ActiveX控件。 The thread must be marked as STA (single threaded apartment), a promise you make that you'll provide a hospitable home to code that is not thread-safe. 该线程必须标记为STA(单线程单元),这是一个承诺,您将为非线程安全的代码提供一个好客的家。 Like any ActiveX control. 像任何ActiveX控件一样。 Implementing the STA contract requires pumping a message loop (Application.Run) and never blocking the thread. 实现STA合同需要抽取消息循环(Application.Run)并且永远不会阻塞线程。 A Task does not create such a thread, threadpool threads cannot be marked STA. 一个Task没有创建这样的线程,线程池线程不能标记为STA。 You'll need a custom TaskScheduler or just a plain Thread so you can call its SetApartmentState() method . 您将需要一个自定义TaskScheduler或只需一个普通线程,以便您可以调用其SetApartmentState()方法

Some sample code to help you get going: 一些示例代码可帮助您开始:

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;
}

The constructor takes care of creating a suitable STA thread and takes care of interlocking with that thread, ensuring that it will not complete until the thread is up and running and the ActiveX control is ready to start generating events. 构造函数负责创建一个合适的STA线程,并负责与该线程互锁,确保在线程启动并运行并且ActiveX控件准备开始生成事件之前它不会完成。 If you get the InvalidOperationException then there is something amiss with the way you initialized the control, diagnose that by subscribing the control's HandleCreated event. 如果你得到InvalidOperationException,那么初始化控件的方式有些不对,通过订阅控件的HandleCreated事件来诊断它。

I added the Execute() methods to give you a shot at calling SomeMethod() correctly. 我添加了Execute()方法,以便您正确地调用SomeMethod()。

Use the Dispose() method to get the control destroyed and the thread terminated. 使用Dispose()方法来销毁控件并终止线程。

From a service, you'd typically use it something like this: 从服务中,您通常会使用以下内容:

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;
}

Do keep in mind that a service is not exactly a hospitable environment for ActiveX controls. 请记住,服务并不是ActiveX控件的好客环境。 They run in session 0, a session that has a small desktop heap. 它们在会话0中运行,这是一个具有小型桌面堆的会话。 This may fail your service with an inscrutable 0xC0000142 exception. 这可能会使您的服务失败,并出现难以理解的0xC0000142异常。 Backgrounder is here Backgrounder 就在这里

暂无
暂无

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

相关问题 System.Windows.Forms.Application&#39;不包含&#39;ExecutablePath的定义 - System.Windows.Forms.Application' does not contain a definition for 'ExecutablePath 错误:Application&#39;是&#39;System.Windows.Application&#39;和&#39;System.Windows.Forms.Application&#39;之间的模糊引用 - Error of: Application' is an ambiguous reference between 'System.Windows.Application' and 'System.Windows.Forms.Application' System.Windows.Forms.Application和System.Windows.Application之间的互操作性 - Interoperability between System.Windows.Forms.Application and System.Windows.Application 'Application' 是 'system.windows.forms.application' 和 'Microsoft.office.interop.word.application' 之间的模糊引用 - 'Application' is an ambiguous reference between 'system.windows.forms.application' and 'Microsoft.office.interop.word.application' How can i fix Application&#39; 是 &#39;System.Windows.Application&#39; 和 &#39;System.Windows.Forms.Application&#39; 之间的模棱两可的引用 - How can i fix Application' is an ambiguous reference between 'System.Windows.Application' and 'System.Windows.Forms.Application' Windows窗体:防止ActiveX获取鼠标事件 - Windows Forms: prevent ActiveX from getting mouse events 如何点击时用JavaScript编写用ActiveX编写的ActiveX控件? - How can I make an ActiveX control written with C# raise events in JavaScript when clicked? 使用javascript ActiveX对象触发System.Windows.Forms dll - Trigger System.Windows.Forms dll using javascript ActiveX object Windows窗体应用程序中的用户控件 - User control in windows forms application 在Windows窗体的Microsoft图表控件上启用滚动 - Enable Scrolling on the Microsoft Chart Control for Windows Forms
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM