2016-05-18 16 views
1

Wie ist es möglich, ein ActiveX-Steuerelement in einem ClassLibrary-Projekt zu verwenden?Wie Verwenden von ActiveX-Komponente in ClassLibrary ohne Winforms

Ich beabsichtige, es später aus der WPF-Anwendung aufzurufen, aber ich möchte kein Steuerelement irgendwo auf dem Formular platzieren, also möchte ich nicht verwenden WindowsFormsHost; hauptsächlich, weil ich diese meine Bibliothek in der Konsolen-App und im Windows-Dienst verwenden möchte.

In diesem Fall ist das ActiveX-Steuerelement, das ich verwenden möchte, eine Videoanalysekomponente. Zusätzlich möchte ich, dass sich meine Komponente in der bereitgestellten Umgebung registriert.

Antwort

2

Ich denke, dass das allgemeine Wissen ist, dass Sie Winforms benötigen, um ActiveX-Steuerelement verwenden zu können. Nun, nicht ganz richtig. Sie benötigen Winforms-ähnliche Nachrichtenschleife und STAThread.

Beginnen wir damit, das Design meiner Lösung zu präsentieren. Ich bevorzuge es, den Code auf so viele Ebenen wie nötig zu verteilen, wenn man mit etwas Unbekanntem beschäftigt ist, so dass einige Schichten überflüssig sind. Ich ermutige Sie, mir zu helfen, die Lösung zu verbessern, um das Gleichgewicht zu finden.

Bitte denken Sie daran, dass bei Bedarf die Schnittstelle IDisposable in allen äußeren Schichten implementiert werden muss.

ActiveXCore - Klasse, die ein ActiveX-Steuerelement enthält, das als privates Feld deklariert ist. In dieser Klasse verwenden Sie nur Code wie in WinForms.

CoreAPI - eine interne API-Klasse, die die Methoden von ActiveXCore verfügbar macht. Ich fand heraus, dass es gut ist, die Methoden mit [STAThreadAttribute] zu markieren, da ich einige Probleme ohne es hatte, obwohl es nur für diesen Fall spezifisch sein kann.

PublicAPI - meine Hauptbibliothek Klasse, die in den referenzierenden Projekten aufgerufen wird.

Jetzt, in der ActiveXCore gibt es wirklich keine Richtlinien. In CoreAPI die Probe Methode wäre

[STAThreadAttribute] 
internal bool Init() 
{ 
    try 
    { 
     _core = new ActiveXCore(); 
     //... 

     return true; 
    } 
    catch (System.Runtime.InteropServices.COMException) 
    { 
     //handle the exception 
    } 
    return false; 
} 

der Lage sein, richtig diese Sie laufen würde wie folgt WinForms wie Nachrichtenschleife müssen (das Desing ist überhaupt nicht meine, ich geändert nur leicht den Code). Sie brauchen nicht die WinForms Projekttyp, aber Sie haben 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(); 
    } 
} 

verweisen Und dann können Sie Methoden zur Verfügung stellen, wie die

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

von, wenn das Verfahren nichtig wäre

public void InitLib() 
{ 
    MessageLoopApartment.Apartament.Run(() => 
    { 
     ca = new CoreAPI(); 
     ca.Init(); 
    }, CancellationToken.None).Wait(); 
} 

wie für die auto-Registrierung ich so etwas wie dieses entworfen (ich es von CoreAPI nennen)

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 
} 

Ich verbrachte viele Tage und Nächte, um dieses wiederverwendbare Muster zu entwerfen, also hoffe ich, dass es jemandem hilft.