如何在 ClassLibrary 类型的项目中使用 ActiveX 控件?
How is it possible to use an ActiveX control in a ClassLibrary type project?
我打算稍后从 WPF 应用程序调用它,但我不想在窗体上的任何位置放置控件,所以我不想使用 WindowsFormsHost
;主要是因为我想在控制台应用程序和 Windows 服务中使用我的库.
I intend to call it later from WPF application but I don't want to place a control anywhere on the form, so I don't want to use WindowsFormsHost
; mainly because I would like to use this my library in Console App and Windows Service.
在这种情况下,我要使用的 ActiveX 控件是一个视频分析组件.此外,我希望我的组件在部署的环境中注册自己.
In this case, the ActiveX control I want to use is a video analysis component. Additionally I want my component to register itself in deployed environment.
我想大家的常识是你需要Winforms才能使用ActiveX控件.嗯,不完全正确.您需要类似 winforms 的消息循环和 STAThread.
I think that the common knowledge is that you need Winforms to be able to use ActiveX control. Well, not entirely true. You need winforms-like message loop and STAThread.
让我们从介绍我的解决方案的设计开始.在处理未知事物时,我更喜欢根据需要将代码分离到尽可能多的层,因此您可能会发现某些层是多余的.我鼓励你帮助我改进解决方案以找到平衡.
Let's start by presenting the design of my solution. I prefer to seperate code to as many layers as needed when dealing with something unknown so you may find some layers redundant. I encourage you to help me improve the solution to find the equilibrium.
如果需要,请记住需要在所有外层实现 IDisposable
接口.
Please remember about the need to implement the IDisposable
interface in all outer layers if needed.
ActiveXCore
- 包含声明为私有字段的 ActiveX 控件的类.在这个类中,您只使用代码,就像在 Winforms 中一样.
ActiveXCore
- class containing an ActiveX control declared as a private field. In this class you use just code like you would in Winforms.
CoreAPI
- 一个公开 ActiveXCore
方法的内部 API 类.我发现用 [STAThreadAttribute]
标记方法很好,因为没有它我会遇到一些问题,尽管它可能仅适用于这种情况.
CoreAPI
- an internal API class that exposes the methods of ActiveXCore
. I found out that it is good to mark the methods with [STAThreadAttribute]
as I had some problems without it, though it may be specific to this case only.
PublicAPI
- 我将在引用项目中调用的主库类.
PublicAPI
- my main library class that will be called in the referencing projects.
现在,在 ActiveXCore
中确实没有指南.在 CoreAPI
中,示例方法是
Now, in the ActiveXCore
there are really no guidelines.
In CoreAPI
the sample method would be
[STAThreadAttribute]
internal bool Init()
{
try
{
_core = new ActiveXCore();
//...
return true;
}
catch (System.Runtime.InteropServices.COMException)
{
//handle the exception
}
return false;
}
为了能够正确运行这些,您需要像这样的消息循环这样的 Winforms(设计根本不是我的,我只是稍微修改了代码).不需要 Winforms 项目类型,但必须引用 System.Windows.Forms 程序集
To be able to properly run these you would need Winforms like message loop like this (the desing is not mine at all, I just slightly modified the code). You don't need the Winforms project type, but you have to reference System.Windows.Forms assembly
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; } }
/// <summary>MessageLoopApartment constructor</summary>
public MessageLoopApartment()
{
var tcs = new TaskCompletionSource<TaskScheduler>();
// 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;
}
/// <summary>shutdown the STA thread</summary>
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;
}
}
/// <summary>Task.Factory.StartNew wrappers</summary>
public void Invoke(Action action)
{
Task.Factory.StartNew(action,
CancellationToken.None, TaskCreationOptions.None, _taskScheduler).Wait();
}
public TResult Invoke<TResult>(Func<TResult> 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<TResult> Run<TResult>(Func<TResult> action, CancellationToken token)
{
return Task.Factory.StartNew(action, token, TaskCreationOptions.None, _taskScheduler);
}
public Task Run(Func<Task> action, CancellationToken token)
{
return Task.Factory.StartNew(action, token, TaskCreationOptions.None, _taskScheduler).Unwrap();
}
public Task<TResult> Run<TResult>(Func<Task<TResult>> action, CancellationToken token)
{
return Task.Factory.StartNew(action, token, TaskCreationOptions.None, _taskScheduler).Unwrap();
}
}
然后你可以提供类似的方法
And then you can provide methods like that
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
调用它)
As for the auto registering I designed something like this (I call it from 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 = @"xxxx86xxx.dll";
Process.Start("regsvr32", "/s " + dllPath);
}
private static void RegSvr64()
{
string dllPath = @"xxxx64xxx.dll";
Process.Start("regsvr32", "/s " + dllPath);
}
#endregion register components
}
我花了很多日日夜夜来设计这个可重复使用的模式,所以我希望它对某人有所帮助.
I spent many days and nights to design this reusable pattern so I hope it will help someone.
这篇关于如何在没有 Winforms 的 ClassLibrary 中使用 ActiveX 组件的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持跟版网!