c# wpf winforms com activex

c# - Cómo usar el componente ActiveX en ClassLibrary sin Winforms



wpf (1)

¿Cómo es posible usar un control ActiveX en un proyecto tipo ClassLibrary?

Tengo la intención de llamarlo más tarde desde la aplicación WPF, pero no quiero colocar un control en ningún lugar en el formulario, por lo que no quiero usar WindowsFormsHost ; principalmente porque me gustaría utilizar esta mi biblioteca en la aplicación de consola y el servicio de Windows.

En este caso, el control ActiveX que quiero usar es un componente de análisis de video. Además, quiero que mi componente se registre en un entorno desplegado.


Creo que el conocimiento común es que necesita Winforms para poder usar el control ActiveX. Bueno, no del todo cierto. Necesitas winforms-like loop de mensaje y STAThread.

Comencemos presentando el diseño de mi solución. Prefiero separar el código en tantas capas como sea necesario cuando se trata de algo desconocido, por lo que puede encontrar algunas capas redundantes. Te animo a que me ayudes a mejorar la solución para encontrar el equilibrio.

Recuerde la necesidad de implementar la interfaz IDisposable en todas las capas externas si es necesario.

ActiveXCore - clase que contiene un control ActiveX declarado como un campo privado. En esta clase usas solo código como lo harías en Winforms.

CoreAPI - una clase API interna que expone los métodos de ActiveXCore . Descubrí que es bueno marcar los métodos con [STAThreadAttribute] ya que tuve algunos problemas sin él, aunque puede ser específico solo para este caso.

PublicAPI - mi clase de biblioteca principal que se llamará en los proyectos de referencia.

Ahora, en ActiveXCore realmente no hay pautas. En CoreAPI el método de muestra sería

[STAThreadAttribute] internal bool Init() { try { _core = new ActiveXCore(); //... return true; } catch (System.Runtime.InteropServices.COMException) { //handle the exception } return false; }

Para poder ejecutarlos correctamente necesitarías Winforms como un ciclo de mensajes como este (el diseño no es mío para nada, simplemente modifiqué ligeramente el código). No necesita el tipo de proyecto Winforms, pero debe hacer referencia al ensamblado 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(); } }

Y luego puedes proporcionar métodos como ese

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

de si el método sería nulo

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

En cuanto al registro automático, diseñé algo así (lo llamo de 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 }

Pasé muchos días y noches para diseñar este patrón reutilizable, así que espero que ayude a alguien.