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





5.00/5 (3投票s)
本技巧的目的是分享在 .NET 应用程序(后端和前端)中使用 ActiveX 的知识和经验。
引言
ActiveX 技术经常被第三方公司用于分发它们的 API。通常,它以 Windows Forms 控件的形式出现。Visual Studio 可以非常轻松地将此类控件集成到你的 Windows Forms 应用程序中。但如果你想借鉴 ActiveX 控件的功能并在控制台应用程序或类库中使用它呢?
在本技巧中,我将展示如何在后端应用程序中使用 ActiveX。此外,我还会描述使用 ActiveX 时可能遇到的最常见问题及其解决方案。
注册/注销 ActiveX
首先,ActiveX 组件必须在目标计算机上注册。否则,你将收到以下异常:
通常,ActiveX 组件由一个 ".ocx" 文件和几个 ".dll" 文件组成。要安装,请在 CMD 中运行以下命令:
regsvr32 "path_to_activex\activex_name.ocx"
要卸载,请在 CMD 中运行以下命令:
regsvr32 /u "path_to_activex\activex_name.ocx"
如果你不是系统管理员,你可能会收到以下错误:
当你在 Windows 7 上注册或注销文件时,如果启用了用户帐户控制 (UAC),可能会出现此消息。用户帐户控制可能会限制或阻止当前 Windows 用户帐户没有权限执行的某些任务。
如果安全设置不允许从 Windows 运行对话框注册或注销文件,则必须从提升的 Windows 命令提示符中注册或注销该文件。请按照以下步骤从提升的命令提示符中注册或注销必要的文件:
- 关闭所有程序。
- 单击“开始”>“所有程序”>“附件”。
- 右键单击“命令提示符”并选择“以管理员身份运行”。
- 执行必要的 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.
当我调用宿主窗体已处置的 ActiveX 的某些方法时,就会发生此问题。
我遇到的另一个问题与非托管资源分配有关(我没有深入研究具体发生了什么)。我的应用程序大约每 2 小时崩溃一次(+/- 15 分钟),出现以下从 Windows 事件日志中截取的异常:
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()
以及类似这样:
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 日 - 第一个版本