【问题标题】:How to use ActiveX component in ClassLibrary without Winforms如何在没有 Winforms 的 ClassLibrary 中使用 ActiveX 组件
【发布时间】:2016-09-15 13:52:20
【问题描述】:

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

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

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

【问题讨论】:

    标签: c# wpf winforms com activex


    【解决方案1】:

    我认为常识是您需要 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(设计根本不是我的,我只是稍微修改了代码)。不需要 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
    }
    

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

    【讨论】:

    • 如果 ActiveX 对象触发事件,并且需要在 CoreAPI 中注册事件处理程序怎么办?
    • 您应该像包装方法一样包装事件。我用这个模型做了一个基于事件的实现,效果很好。
    • 你介意提供一个代码示例来演示如何,我试过但它不起作用。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-05-20
    • 2010-12-04
    • 1970-01-01
    • 1970-01-01
    • 2011-02-26
    相关资源
    最近更新 更多