如何在没有Winforms的ClassLibrary中使用ActiveX组件

如何在ClassLibrary类型项目中使用ActiveX控件?

我打算稍后从WPF应用程序调用它,但我不想在窗体上的任何地方放置控件,所以我不想使用WindowsFormsHost ; 主要是因为我想在Console App和Windows Service中使用我的库。

在这种情况下,我想要使用的ActiveX控件是video分析组件。 另外,我希望我的组件在已部署的环境中注册自己。

我认为常识是你需要Winforms才能使用ActiveX控件。 嗯,不完全正确。 你需要类似winforms的消息循环和STAThread。

让我们首先介绍我的解决方案的设计。 在处理未知事物时,我更喜欢将代码分离到需要的层数,因此您可能会发现某些层多余。 我鼓励你帮助我改进解决方案以找到平衡点。

请记住,如果需要,需要在所有外层中实现IDisposable接口。

ActiveXCore – 包含声明为私有字段的ActiveX控件的类。 在这个类中,您只需使用Winforms中的代码。

CoreAPI – 一个公开ActiveXCore方法的内部API类。 我发现用[STAThreadAttribute]标记方法是好的,因为没有它我有一些问题,尽管它可能只针对这种情况。

PublicAPI – 将在引用项目中调用的主库类。

现在,在ActiveXCore中确实没有指南。 在CoreAPI ,样本方法将是

 [STAThreadAttribute] internal bool Init() { try { _core = new ActiveXCore(); //... return true; } catch (System.Runtime.InteropServices.COMException) { //handle the exception } return false; } 

为了能够正确地运行这些,你需要Winforms像这样的消息循环(desing根本不是我的,我只是略微修改了代码)。 您不需要Winforms项目类型,但您必须引用System.Windows.Forms程序集

 public class MessageLoopApartment : IDisposable { public static MessageLoopApartment Apartament { get { if (_apartament == null) _apartament = new MessageLoopApartment(); return _apartament; } } private static MessageLoopApartment _apartament; private Thread _thread; // the STA thread private TaskScheduler _taskScheduler; // the STA thread's task scheduler public TaskScheduler TaskScheduler { get { return _taskScheduler; } } /// MessageLoopApartment constructor public MessageLoopApartment() { var tcs = new TaskCompletionSource(); // start an STA thread and gets a task scheduler _thread = new Thread(startArg => { EventHandler idleHandler = null; idleHandler = (s, e) => { // handle Application.Idle just once Application.Idle -= idleHandler; // return the task scheduler tcs.SetResult(TaskScheduler.FromCurrentSynchronizationContext()); }; // handle Application.Idle just once // to make sure we're inside the message loop // and SynchronizationContext has been correctly installed Application.Idle += idleHandler; Application.Run(); }); _thread.SetApartmentState(ApartmentState.STA); _thread.IsBackground = true; _thread.Start(); _taskScheduler = tcs.Task.Result; } /// shutdown the STA thread public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } protected virtual void Dispose(bool disposing) { if (_taskScheduler != null) { var taskScheduler = _taskScheduler; _taskScheduler = null; // execute Application.ExitThread() on the STA thread Task.Factory.StartNew( () => Application.ExitThread(), CancellationToken.None, TaskCreationOptions.None, taskScheduler).Wait(); _thread.Join(); _thread = null; } } /// Task.Factory.StartNew wrappers public void Invoke(Action action) { Task.Factory.StartNew(action, CancellationToken.None, TaskCreationOptions.None, _taskScheduler).Wait(); } public TResult Invoke(Func action) { return Task.Factory.StartNew(action, CancellationToken.None, TaskCreationOptions.None, _taskScheduler).Result; } public Task Run(Action action, CancellationToken token) { return Task.Factory.StartNew(action, token, TaskCreationOptions.None, _taskScheduler); } public Task Run(Func action, CancellationToken token) { return Task.Factory.StartNew(action, token, TaskCreationOptions.None, _taskScheduler); } public Task Run(Func action, CancellationToken token) { return Task.Factory.StartNew(action, token, TaskCreationOptions.None, _taskScheduler).Unwrap(); } public Task Run(Func> action, CancellationToken token) { return Task.Factory.StartNew(action, token, TaskCreationOptions.None, _taskScheduler).Unwrap(); } } 

然后你可以提供这样的方法

 public bool InitLib() { return MessageLoopApartment.Apartament.Run(() => { ca = new CoreAPI(); bool initialized = ca.Init(); }, CancellationToken.None).Result; } 

如果该方法无效

 public void InitLib() { MessageLoopApartment.Apartament.Run(() => { ca = new CoreAPI(); ca.Init(); }, CancellationToken.None).Wait(); } 

至于自动注册,我设计了这样的东西(我从CoreAPI调用它)

 internal static class ComponentEnvironment { internal static void Prepare() { CopyFilesAndDeps(); if (Environment.Is64BitOperatingSystem) RegSvr64(); RegSvr32(); //you may notice no "else" here //in my case for 64 bit I had to copy and register files for both arch } #region unpack and clean files private static void CopyFilesAndDeps() { //inspect what file you need } #endregion unpack and clean files #region register components private static void RegSvr32() { string dllPath = @"xxx\x86\xxx.dll"; Process.Start("regsvr32", "/s " + dllPath); } private static void RegSvr64() { string dllPath = @"xxx\x64\xxx.dll"; Process.Start("regsvr32", "/s " + dllPath); } #endregion register components } 

我花了很多日日夜来设计这种可重复使用的模式,所以我希望它会帮助某人。