2016-05-18 52 views
1

如何在ClassLibrary类型项目中使用ActiveX控件?如何在没有Winforms的情况下在ClassLibrary中使用ActiveX组件

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

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

回答

2

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

我花了很多天和时间来设计这种可重复使用的模式,所以我希望它能帮助别人。

相关问题