如何在没有 Winforms 的 ClassLibrary 中使用 ActiveX 组件 [英] How to use ActiveX component in ClassLibrary without Winforms

查看:17
本文介绍了如何在没有 Winforms 的 ClassLibrary 中使用 ActiveX 组件的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

如何在 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 组件的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆