没有窗体的ActiveX控件

21

我们需要使用第三方的ActiveX控件。

唯一的问题是,我们软件中的业务层没有访问窗口或表单的权限。此外,它运行在不支持单线程单元(STA)的不同线程上(并应该从任何线程工作)。

为了不破坏UI与业务逻辑的分离,我们采用了以下方法来使其正常工作:

Thread thread = new Thread((ThreadStart)
delegate
{
_myActiveX = new MyActiveXType();
_myActiveX.CreateControl();

//more initialize work

Application.Run();
});
thread.SetApartmentState(ApartmentState.STA);
thread.IsBackground = true;
thread.Start();

每当需要引用该控件时,我们调用 _myActiveX.BeginInvoke()Invoke()。在销毁该类(退出应用程序)时,我们释放控件并中止线程。

我的问题是,这种做法有什么问题吗?是否有更好的方法来处理这个问题?是否有更好的内置方法可以在未知的多线程环境中使用 ActiveX 控件?我们正在尝试以包装控件的方式编写我们的类,但希望它能够在任何线程中工作。

更新:正如一个答案所建议的那样,我们实际上更愿意使用标准的 COM 对象而不是使用控件。我们遇到的问题是我们会在调用 COM 对象的第一个方法或属性时收到错误 "(Exception from HRESULT: 0x8000FFFF (E_UNEXPECTED)"。这是一个相当通用的错误,在使用 ActiveX 时我们不会遇到这个问题,有什么想法吗?

更新:我们的 ocx 是 "CX25.ocx",使用 tlbimp.exe 我们得到 CX25Lib.dll。使用 aximp.exe,我们得到 AxCX25Lib.dll 和 CX25Lib.dll。CX25Lib.dll 在任何情况下都无法工作。AxCX25Lib.dll 可以工作。


我提到的上述代码是有效的。有人不得不在没有表单的情况下使用ActiveX控件吗?或者有人遇到了使用标准COM对象的问题,但注意到ActiveX可以工作吗? - jonathanpeppers
你找到解决方案了吗?我猜正确的方法取决于你是否期望从控件中得到回调/事件,因为这将依赖于Windows消息泵来处理它们。 - Tormod
1
是的,上面的代码可以正常工作。我们已经使用它一年多了,没有出现任何问题。我们还将其与多个COM库一起使用。最终,我们将其封装在一个类中,并实现了Invoke/BeginInvoke来与控件交互,这是必需的。 - jonathanpeppers
5个回答

18

我假设这是正确的方法。

我们在过去几周的测试环境中使用了上面我的代码,没有出现任何问题。

如果有人需要在没有表单的情况下使用ActiveX,我认为这是一种方法。

只需确保在ActiveX对象的构造函数之后直接调用“_yourActiveXControl.CreateControl()”。这简化了我们最初遇到的许多问题。


感谢您发布这些信息,看起来对某些人来说会非常有用。只是一个数据点,我自己有一个 ActiveX 控件,这个方法对我来说不起作用。我不得不将该控件放在一个在单独线程中运行的窗体上(至少不需要显示该窗体)。 - user12861
1
感谢 - 如果您正在从另一个表单/窗口启动表单,则需要执行此操作以使ActiveX控件正常工作。在我的情况下,Active X RDP客户端无法弹出重定向驱动器等框,因此它只会关闭表单。在控件的BeginInit()之后放置CreateControl()可以解决此问题。如果您已经嵌入了主表单,则不需要执行此操作。 - uNople
@jonathanpeppers 能否请您看一下我在这里的回答(https://dev59.com/Oq_la4cB1Zd3GeqPnhd-#52801094),并帮我看看是否能够在这个问题中使用您的方法? - LorneCash

4
如果您从业务层调用ActiveX控件,那么它必须能够在没有UI的情况下使用,例如通过调用其公共方法。为什么不直接为ActiveX控件类创建一个互操作RCW并直接调用其方法呢?

你是在说在.ocx控件上运行tlbimp吗?我们尝试过,但无法工作。每次我们从标准COM对象中尝试时,都会出现“灾难性失败(HRESULT:0x8000FFFF(E_UNEXPECTED))”。不知何故,ActiveX控件可以正常工作。 - jonathanpeppers
你尝试过使用 aximp.exe 而不是 tlbimp.exe 吗?它会创建两个程序集:一个普通的 RCW 程序集和一个 WinForms 代理程序集。你可能可以在代码中仅使用 RCW 程序集。免责声明 - 我没有尝试过这个方法,只是根据我的 tlbimp.exe 经验猜测的。 - Christian Hayter
您想要调用的方法是在实际的 UI 控件类上,还是在单独的帮助程序类上?如果它们在 UI 控件类上,那么我会期望它在 ActiveX 控件主机之外被实例化时抛出运行时错误。然而,我期望一个单独的帮助程序类能够在任何地方被实例化。 - Christian Hayter
在我看来,这两个类似乎具有相同的方法(除了AX具有来自System.Windows.Forms.Control的方法)。因此,我觉得我们没有做错任何事情。几乎似乎是在他们的ocx中发生了一些事情,使得.Net无法从AX主机外部使用它。 - jonathanpeppers
不是我想表达的意思,我会重新措辞。假设OCX公开了多个类,例如TheUIControlClass、AHelperClass、AHelperCollectionClass等。你的调用代码实际上需要实例化TheUIControlClass的实例来完成其工作吗?还是只需实例化其中一个帮助程序类就可以了?如果答案是(a),那么你就没什么办法了,因为它将期望被包含在像AxHost这样的适当主机中。 - Christian Hayter
显示剩余3条评论

2

我的解决方案是创建一个隐藏的winform来托管ActiveX控件。


我的解决方案似乎更加轻量级。使用离屏表单你能得到什么好处? - jonathanpeppers
我有一个第三方程序,它公开了一个ActiveX控件,没有简单的方法来控制该程序,于是我使用了ActiveX控件。实际上,我尝试了您的解决方案,似乎也可以工作。因此,我将采用您的解决方案。 - Benny
这是我正在使用的一个控件的唯一解决方案,所以感谢您发布它。 - user12861

2

我知道这是一个老帖子,但我建议在我们现代化的时代使用TPL。

使用任务并行库而不是旧的线程API更好,因为它具有异常处理、取消、继续和返回结果等功能。

以下是一个示例:

using (var sta = new StaTaskScheduler(1))
{    
    var taskResult = await Task.Factory.StartNew(() =>
    {
        var results = new List<ResultType>();

        using (var ax = new MyActiveXType())
        {
            // important to call this just after constructing ActiveX type
            ax.CreateControl();

            ax.SomeIterativeEvent += (s, e) => results.Add(e.SomeThing);

            // if applicable, you can tear down the message pump
            ax.SomeFinalEvent += (s, e) => Application.ExitThread();

            //more initialize work

            // start message pump
            Application.Run();

            return results;
        }
    }, CancellationToken.None, TaskCreationOptions.None, sta);

    return taskResult;
}

几点说明:

  1. StaTaskScheduler 是在 ParallelExtensionsExtras nuget 包中找到的一种类型。您需要使用它来安排任务在单线程公寓中执行。

  2. 我将数字 1 传递给 StaTaskScheduler 的构造函数,以便它仅为我创建一个单线程。

  3. 调用 Application.ExitThread() 来停止消息泵,这样就允许执行通过 Application.Run(),以便可以向调用方返回某些结果。


1
CreateControl()方法来自AxHost,需要System.Windows.Forms作为依赖项。如果您想在没有UI的情况下使用ActiveX,则可以直接使用本机调用创建ocx的COM对象。
   [DllImport("ole32.dll", PreserveSig = false)]
    [return: MarshalAs(UnmanagedType.Interface)]
    public static extern object CoCreateInstance([In] ref Guid clsid,[MarshalAs(UnmanagedType.Interface)] object punkOuter,int context, [In] ref Guid iid);


public object createComObject(){ 
    Guid IID_IUnknown = new Guid("{00000000-0000-0000-C000-000000000046}");

    var gid = "{6bf52a52-394a-11d3-b153-00c04f79faa6}"; //your ocx guid
    var clsid = new Guid(gid);

    object yourOCX = CoCreateInstance(ref clsid, (object)null, 1, ref IID_IUnknown);
  return yourOCX ;
}

你可以稍后将COM对象转换为所需的接口。
IOleObject iole = yourOCX as IOleObject;

IWMPCore iwmp =  yourOCX as IWMPCore;

我在这个链接上创建了一个没有UI或AxHost的C# Windows Media Player ActiveX。它可能对想要运行没有UI的ActiveX的人有所帮助。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接