繁体   English   中英

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

[英]How to use ActiveX component in ClassLibrary without Winforms

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

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

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

我认为常识是你需要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; } }

    /// <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;
}

如果该方法无效

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
}

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM