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

How to use ActiveX component in ClassLibrary without Winforms

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

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

在本例中,我要使用的ActiveX控件是一个视频分析组件。 此外,我希望我的组件在部署环境中自行注册。

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

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

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

ActiveXCore - class 包含声明为私有字段的 ActiveX 控件。在此 class 中,您可以像在 Winforms 中一样使用代码。

CoreAPI - 公开 ActiveXCore 方法的内部 API class。我发现用 [STAThreadAttribute] 标记方法很好,因为没有它我遇到了一些问题,尽管它可能仅针对这种情况。

PublicAPI - 我的主库 class 将在引用项目中调用。

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

[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 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();
    }
}

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

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

of 如果方法无效

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
}

我花了很多日日夜夜来设计这个可重用的模式,所以我希望它能帮助到别人。