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.