65.9K
CodeProject 正在变化。 阅读更多。
Home

ActiveX 在 .NET 应用程序中的应用

starIconstarIconstarIconstarIconstarIcon

5.00/5 (3投票s)

2015 年 9 月 9 日

CPOL

6分钟阅读

viewsIcon

27919

本技巧的目的是分享在 .NET 应用程序(后端和前端)中使用 ActiveX 的知识和经验。

引言

ActiveX 技术经常被第三方公司用于分发它们的 API。通常,它以 Windows Forms 控件的形式出现。Visual Studio 可以非常轻松地将此类控件集成到你的 Windows Forms 应用程序中。但如果你想借鉴 ActiveX 控件的功能并在控制台应用程序或类库中使用它呢?

在本技巧中,我将展示如何在后端应用程序中使用 ActiveX。此外,我还会描述使用 ActiveX 时可能遇到的最常见问题及其解决方案。

注册/注销 ActiveX

首先,ActiveX 组件必须在目标计算机上注册。否则,你将收到以下异常:

System.Runtime.InteropServices.COMException (0x80040154): Class not registered (Exception from HRESULT: 0x80040154 (REGDB_E_CLASSNOTREG))

通常,ActiveX 组件由一个 ".ocx" 文件和几个 ".dll" 文件组成。要安装,请在 CMD 中运行以下命令:

regsvr32 "path_to_activex\activex_name.ocx"

要卸载,请在 CMD 中运行以下命令:

regsvr32 /u "path_to_activex\activex_name.ocx"

如果你不是系统管理员,你可能会收到以下错误:

The module "C:\[file being registered]" was loaded but the call to DllRegisterServer failed with error code 0x80040200.
The module "C:\[file being registered]" was loaded but the call to DllUnregisterServer failed with error code 0x80040200.?

当你在 Windows 7 上注册或注销文件时,如果启用了用户帐户控制 (UAC),可能会出现此消息。用户帐户控制可能会限制或阻止当前 Windows 用户帐户没有权限执行的某些任务。

如果安全设置不允许从 Windows 运行对话框注册或注销文件,则必须从提升的 Windows 命令提示符中注册或注销该文件。请按照以下步骤从提升的命令提示符中注册或注销必要的文件:

  1. 关闭所有程序。
  2. 单击“开始”>“所有程序”>“附件”。
  3. 右键单击“命令提示符”并选择“以管理员身份运行”。
  4. 执行必要的 regsvr32 命令以注册或注销相应的 DLL 或 OCX 文件。

在类库中使用 ActiveX

本质上,ActiveX 被设计用于 GUI 应用程序。这就是为什么大多数 ActiveX 组件需要 STA 线程、应用程序消息循环以及 Windows Forms 项目中存在但类库项目中不存在的许多其他内容。接下来,我将展示如何通过创建 ActiveX 对象包装器来避免所有这些麻烦。

首先,我们需要在 Visual Studio 中创建一个类库项目。如果 ActiveX 是 32 位应用程序编译的,请确保你的项目也使用 x86 平台设置进行编译。

下一步是将 Windows 窗体添加到你的项目。我们称之为“SdkWrapperForm”。这个窗体的唯一目的是托管我们的 ActiveX 控件。因此,你需要在窗体上添加它:工具箱 -> 右键单击 -> 选择项... -> COM 组件。选择目标 ActiveX 并按确定。新控件将出现在工具箱\通用中。将其拖放到窗体上。Visual Studio 会自动将两个 DLL 添加到你的项目中:AxInterop.activex_name, Interop.activex_name。这两个 DLL 应与你的项目输出 DLL 一起分发。

现在转到“SdkWrapperForm.cs”并添加以下代码:

public partial class SdkWrapperForm : Form
    {
        public SdkWrapperForm()
        {
            InitializeComponent();
        }

        public activex_type SDK
        {
            get
            {
                return activex_obj;
            }
        }
    }

此属性允许我们从其他类访问放置在窗体上的 ActiveX 对象。

现在,我们准备编写包装器的主要部分。向你的项目添加一个名为 'SdkWrapper' 的新类。粘贴以下代码:

public class SdkWrapper : IDisposable
    {
        #region Private members

        private Thread _uiThread;
        private SdkWrapperForm _form;

        #endregion

        #region Constructor

        public SdkWrapper()
        {
            ManualResetEvent initFinished = new ManualResetEvent(false);

            _uiThread = new Thread(() =>
            {
                _form = new SdkWrapperForm();
                //TODO: implement specific initialization here
                //example:
                //_form.SDK.some_method();
                //_form.SDK.some_event += SDK_some_event;

                initFinished.Set();

                while (true)
                {
                    try
                    {
                        Application.Run();
                    }
                    catch (ThreadAbortException)
                    {
                        //exit loop
                        return;
                    }
                    catch (Exception ex)
                    {
                        //log exception
                    }
                }
            }
            );
            _uiThread.SetApartmentState(ApartmentState.STA);
            _uiThread.IsBackground = true;
            _uiThread.Start();

            initFinished.WaitOne(1000);
        }

        public void Dispose()
        {
            _uiThread.Abort();
            _uiThread = null;
            Application.Exit();
        }

        #endregion
    }

此代码在标记为 STA 属性的线程中创建我们 'SdkWrapperForm' 的实例,并运行应用程序消息循环。请注意,应在应用程序关闭前调用 Dispose

现在,如果我们想使用 ActiveX 对象中的一些方法,我们需要为它们创建包装器。这里有一些例子:

public void AX_connect(string url, string username, string password)
        {
            if (_form.InvokeRequired)
            {
                _form.Invoke(new Action(() => { AX_connect(url, username, password); }));
            }
            else
            {
                _form.SDK.TS_connect(url, username, password);
            }
        }
 public string AX_get_version()
        {
            if (_form.InvokeRequired)
            {
                return (string)_form.Invoke(new Func<string>(() => { return AX_get_version(); }));
            }
            else
            {
                return _form.SDK.AX_get_version();
            }
        }
public string AX_get_some_info(short id)
        {
            if (_form.InvokeRequired)
            {
                return (string)_form.Invoke(new Func<string>(() => { return AX_get_some_info(id); }));
            }
            else
            {
                return _form.SDK.AX_get_some_info(id);
            }
        }

如你所见,ActiveX 对象的方法应该从创建 ActiveX 的同一个线程中调用。

基本上就是这样!'SdkWrapper' 类可以在你的代码的任何地方使用。

关注点

在将 ActiveX 组件集成到我的应用程序时,我遇到了一些问题。

第一个问题与不正确的处置有关。我收到了以下错误:

COM object that has been separated from its underlying RCW cannot be used.

at System.RuntimeType.InvokeDispMethod(String name, BindingFlags invokeAttr, Object target, Object[] args, Boolean[] byrefModifiers, Int32 culture, String[] namedParameters) at System.RuntimeType.InvokeMember(String name, BindingFlags bindingFlags, Binder binder, Object target, Object[] providedArgs, ParameterModifier[] modifiers, CultureInfo culture, String[] namedParams) at System.RuntimeType.ForwardCallToInvokeMember(String memberName, BindingFlags flags, Object target, Int32[] aWrapperTypes, MessageData& msgData)

当我调用宿主窗体已处置的 ActiveX 的某些方法时,就会发生此问题。

我遇到的另一个问题与非托管资源分配有关(我没有深入研究具体发生了什么)。我的应用程序大约每 2 小时崩溃一次(+/- 15 分钟),出现以下从 Windows 事件日志中截取的异常:

Application: my_app.exe Framework Version: v4.0.30319 Description: The process was terminated due to an unhandled exception. Exception Info: System.AccessViolationException Stack: at System.Windows.Forms.UnsafeNativeMethods.PeekMessage(MSG ByRef, System.Runtime.InteropServices.HandleRef, Int32, Int32, Int32) at System.Windows.Forms.Application+ ComponentManager.System.Windows.Forms.UnsafeNativeMethods.IMsoComponentManager.
FPushMessageLoop(IntPtr, Int32, Int32) at System.Windows.Forms.Application+ThreadContext.RunMessageLoopInner(Int32, System.Windows.Forms.ApplicationContext) at System.Windows.Forms.Application+ThreadContext.RunMessageLoop(Int32, System.Windows.Forms.ApplicationContext) at System.Windows.Forms.Application.Run() at my_app.SdkWrapper+<>c__DisplayClassf.<.ctor>b__6() at System.Threading.ThreadHelper.ThreadStart_Context(System.Object) at System.Threading.ExecutionContext.RunInternal(System.Threading.ExecutionContext, System.Threading.ContextCallback, System.Object, Boolean) at System.Threading.ExecutionContext.Run(System.Threading.ExecutionContext, System.Threading.ContextCallback, System.Object, Boolean) at System.Threading.ExecutionContext.Run(System.Threading.ExecutionContext, System.Threading.ContextCallback, System.Object) at System.Threading.ThreadHelper.ThreadStart()

以及类似这样:

Application: my_app.exe Framework Version: v4.0.30319 Description: The process was terminated due to an unhandled exception. Exception Info: System.Reflection.TargetInvocationException Stack: at System.RuntimeMethodHandle.InvokeMethod(System.Object, System.Object[], System.Signature, Boolean) at System.Reflection.RuntimeMethodInfo.UnsafeInvokeInternal(System.Object, System.Object[], System.Object[]) at System.Reflection.RuntimeMethodInfo.UnsafeInvoke(System.Object, System.Reflection.BindingFlags, System.Reflection.Binder, System.Object[], System.Globalization.CultureInfo) at System.Delegate.DynamicInvokeImpl(System.Object[]) at System.Delegate.DynamicInvoke(System.Object[]) at System.Windows.Forms.Control.InvokeMarshaledCallbackDo(ThreadMethodEntry) at System.Windows.Forms.Control.InvokeMarshaledCallbackHelper(System.Object) at System.Threading.ExecutionContext.RunInternal(System.Threading.ExecutionContext, System.Threading.ContextCallback, System.Object, Boolean) at System.Threading.ExecutionContext.Run(System.Threading.ExecutionContext, System.Threading.ContextCallback, System.Object, Boolean) at System.Threading.ExecutionContext.Run(System.Threading.ExecutionContext, System.Threading.ContextCallback, System.Object) at System.Windows.Forms.Control.InvokeMarshaledCallback(ThreadMethodEntry) at System.Windows.Forms.Control.InvokeMarshaledCallbacks() at System.Windows.Forms.Control.WndProc(System.Windows.Forms.Message ByRef) at System.Windows.Forms.ScrollableControl.WndProc(System.Windows.Forms.Message ByRef) at System.Windows.Forms.ContainerControl.WndProc(System.Windows.Forms.Message ByRef) at System.Windows.Forms.Form.WndProc(System.Windows.Forms.Message ByRef) at my_app.MainForm.WndProc(System.Windows.Forms.Message ByRef) at System.Windows.Forms.Control+ControlNativeWindow.OnMessage(System.Windows.Forms.Message ByRef) at System.Windows.Forms.Control+ControlNativeWindow.WndProc(System.Windows.Forms.Message ByRef) at System.Windows.Forms.NativeWindow.Callback(IntPtr, Int32, IntPtr, IntPtr) at System.Windows.Forms.UnsafeNativeMethods.DispatchMessageW(MSG ByRef) at System.Windows.Forms.Application+ ComponentManager.System.Windows.Forms.UnsafeNativeMethods.IMsoComponentManager.
FPushMessageLoop(IntPtr, Int32, Int32) at System.Windows.Forms.Application+ThreadContext.RunMessageLoopInner(Int32, System.Windows.Forms.ApplicationContext) at System.Windows.Forms.Application+ThreadContext.RunMessageLoop(Int32, System.Windows.Forms.ApplicationContext) at System.Windows.Forms.Application.Run(System.Windows.Forms.Form) at my_app.MainForm.Main(System.String[])

我发现这种情况发生是因为我的应用程序在运行时创建和删除 ActiveX 对象。创建一定数量的对象后,我就会收到这些异常。可能是因为我没有完全删除这些对象,一些非托管资源仍然保留在内存中。作为一种变通方法,我创建了一个 ActiveX 对象池。应用程序首先查找池中可用的对象;如果池为空,则创建一个新对象;如果池不为空,则重用现有对象。对我来说,这效果很好,因为我的应用程序同时使用的对象最多为 16 个。

历史

  • 2015 年 9 月 9 日 - 第一个版本
© . All rights reserved.