Implementierung eines COM-Nachrichtenfilters

Nachrichtenfilterung ist ein Mechanismus, mit dessen Hilfe Serveranwendungen entscheiden können, ob und wann ein eingehender Methodenaufruf sicher auf eines ihrer Objekte ausgeführt werden kann. COM kennt im Allgemeinen die Anforderungen bezüglich der Eintrittsinvarianz Ihrer Anwendung nicht und filtert demzufolge standardmäßig nicht die Nachrichten. Auch wenn die Nachrichtenfilterung nicht mehr so bedeutend ist, wie sie es mit 16-Bit-Anwendungen war, weil die Größe der Nachrichtenwarteschlange jetzt eigentlich unbegrenzt ist, sollten Sie immer noch Nachrichtenfilterung als Möglichkeit zur Lösung von Blockaden implementieren. COM wird Ihre Implementierung der IMessageFilter-Schnittstelle aufrufen, um herauszufinden, ob eine Anwendung (ein COM Server) blockiert, so dass Sie reagieren und die Situation behandeln können. Zum Beispiel, beim Zugriff auf TwinCAT XAE über COM wird die Visual Studio-Instanz weitere COM-Aufrufe abweisen, während noch ein früherer COM-Aufruf ausgeführt wird. Die Folge ist, dass die Client-Anwendung einen RPC_E_CALL_REJECTED-Fehler ausgibt und, ohne weitere Intervention, den Aufruf nicht wiederholen wird. Durch das Verfassen eines benutzerdefinierten Nachrichtenfilters hat der Programmierer die Möglichkeit, den COM-Aufruf zu wiederholen, wenn die Client-Anwendung die Notifizierung eines verweigerten COM-Aufrufs durch den COM-Server erhält.

Der folgende Screenshot zeigt eine typische Fehlerausgabe durch den Visual Studio COM-Server, wenn eine Instanz immer noch mit der Ausführung eines vorherigen COM-Aufrufs beschäftigt ist.

Implementierung eines COM-Nachrichtenfilters 1:

Um diese Situation zu vermeiden und einen Nachrichtenfilter zu implementierten, der auf diesen abgewiesenen COM-Aufruf reagiert, muss der Anwendungsingenieur die IMessageFilter-Schnittstelle implementieren. Diese Schnittstelle besteht aus drei Methoden:

Beachten Sie, dass Nachrichtenfilter nur auf STA-Threads angewendet werden können und dass nur ein Filter auf jeden Thread angewendet werden kann. Multithreaded Apartments, z.B. Konsolenanwendungen, können keine Nachrichtenfilter haben. Diese Anwendungen müssen in einem STA-Thread laufen, um Nachrichtenfilterung anzuwenden. Im Anhang dieser Dokumentation finden Sie weitere Informationen zum COM-Threading.

Der folgende Code-Ausschnitt zeigt ein Beispiel, wie die IMessageFilter-Schnittstelle in C# verwendet werden kann. Beachten Sie, dass dieser Code auch in vielen Beispielen in unserem Abschnitt Beispiele verwendet wird, und auch als getrennter Beispiel-Download zur Verfügung steht.

[ComImport(), Guid("00000016-0000-0000-C000-000000000046"), InterfaceTypeAttribute(ComInterfaceType.InterfaceIsIUnknown)] 
interface IOleMessageFilter
{

[PreserveSig]
int HandleInComingCall(int dwCallType, IntPtr hTaskCaller, int dwTickCount, IntPtr lpInterfaceInfo);


[PreserveSig]
int RetryRejectedCall(IntPtr hTaskCallee, int dwTickCount, int dwRejectType);


[PreserveSig]
int MessagePending(IntPtr hTaskCallee, int dwTickCount, int dwPendingType);
}

Die folgende Klasse implementiert diese Schnittstelle und fügt zwei weitere Methoden hinzu: Register() und Revoke().

public class MessageFilter : IOleMessageFilter 
{
public static void Register()
{
IOleMessageFilter newFilter = newMessageFilter();
IOleMessageFilter oldFilter = null;
int test = CoRegisterMessageFilter(newFilter, out oldFilter);

if (test != 0)
{
Console.WriteLine(string.Format("CoRegisterMessageFilter failed with error : {0}", test));
}
}


public static void Revoke()
{
IOleMessageFilter oldFilter = null;
int test = CoRegisterMessageFilter(null, out oldFilter);
}


int IOleMessageFilter.HandleInComingCall(int dwCallType, System.IntPtr hTaskCaller, int dwTickCount, System.IntPtr lpInterfaceInfo)
{
//returns the flag SERVERCALL_ISHANDLED.
return 0;
}


int IOleMessageFilter.RetryRejectedCall(System.IntPtr hTaskCallee, int dwTickCount, int dwRejectType)
{
// Thread call was refused, try again.
if (dwRejectType == 2)
// flag = SERVERCALL_RETRYLATER.
{
// retry thread call at once, if return value >=0 &
// <100.
return 99;
}
return -1;
}


int IOleMessageFilter.MessagePending(System.IntPtr hTaskCallee, int dwTickCount, int dwPendingType)
{
//return flag PENDINGMSG_WAITDEFPROCESS.
return 2;
}

// implement IOleMessageFilter interface.
[DllImport("Ole32.dll")]
private static extern int CoRegisterMessageFilter(IOleMessageFilter newFilter, outIOleMessageFilter oldFilter);

Ein Anwendungsingenieur muss nun lediglich die Methoden Register() und Revoke() aus einer anderen Klasse aufrufen, um den MessageFilter zu initialisieren und zu verwerfen. Die Folge ist, dass abgewiesene COM-Aufrufe wie in der RetryRejectedCall()-Methode festgelegt wiederholt werden.

Der folgende Code-Ausschnitt zeigt, wie diese Methoden in einer in C# geschriebenen Konsolenanwendung aufzurufen sind. Wie oben erwähnt laufen Konsolenanwendungen standardmäßig in einem MTA-Thread. Aus diesem Grund muss die Main()-Methode für die Ausführung in einem STA-Apartment konfiguriert werden, damit der Nachrichtenfilter angewendet werden kann.

[STAThread] 
static void Main(string[] args)
{
MessageFilter.Register();

/* =============================================================
* place COM calls for the Automation Interface here
* ...
* ...
* ============================================================= */

MessageFilter.Revoke();
}

Wenn Sie versuchen, einen Nachrichtenfilter auf eine im MTA-Apartment laufende Anwendung anzuwenden, wird der folgende Fehler ausgegeben, wenn versucht wird, die Methode CoRegisterMessageFilter() während der Laufzeit auszuführen:

Implementierung eines COM-Nachrichtenfilters 2:

Weitere Informationen über die verschiedenen COM Threading Modelle finden Sie im MSDN-Artikel Understanding and Using COM Threading models. Weitere ausführliche Informationen über die IMessageFilter-Schnittstelle finden Sie in der MSDN-Dokumentation bezüglich IMessageFilter.

Der folgende Code-Ausschnitt zeigt, wie ein COM MessageFilter für Windows Powershell implementiert wird, indem dieser als .NET-Typ (C#) in PowerShell eingebunden wird.

Code-Ausschnitt (Powershell):

AddMessageFilterClass('') # Call function
[EnvDteUtils.MessageFilter]::Register() # Call static Register Filter Method

$dte = New-Object -COMObject TcXaeShell.DTE.15.0
$dte.SuppressUI = $false
$dte.MainWindow.Visible = $true
$solution = $dte.Solution
# do stuff
$dte.Quit()

[EnvDTEUtils.MessageFilter]::Revoke()


function AddMessageFilterClass
{
$source = @‘
namespace EnvDteUtils
{
using System;
using System.Runtime.InteropServices;

public class MessageFilter : IOleMessageFilter
{
public static void Register()
{
IOleMessageFilter newFilter = new MessageFilter();
IOleMessageFilter oldFilter = null;
CoRegisterMessageFilter(newFilter, out oldFilter);
}

public static void Revoke()
{
IOleMessageFilter oldFilter = null;
CoRegisterMessageFilter(null, out oldFilter);
}

int IOleMessageFilter.HandleInComingCall(int dwCallType, System.IntPtr hTaskCaller, int dwTickCount, System.IntPtr lpInterfaceInfo)
{
return 0;
}

int IOleMessageFilter.RetryRejectedCall(System.IntPtr hTaskCallee, int dwTickCount, int dwRejectType)
{
if (dwRejectType == 2)
{
return 99;
}
return -1;
}

int IOleMessageFilter.MessagePending(System.IntPtr hTaskCallee, int dwTickCount, int dwPendingType)
{
return 2;
}

[DllImport("Ole32.dll")]
private static extern int CoRegisterMessageFilter(IOleMessageFilter newFilter, out IOleMessageFilter oldFilter);
}

[ComImport(), Guid("00000016-0000-0000-C000-000000000046"), InterfaceTypeAttribute(ComInterfaceType.InterfaceIsIUnknown)]
interface IOleMessageFilter
{
[PreserveSig]
int HandleInComingCall(int dwCallType, IntPtr hTaskCaller, int dwTickCount, IntPtr lpInterfaceInfo);

[PreserveSig]
int RetryRejectedCall(IntPtr hTaskCallee, int dwTickCount, int dwRejectType);

[PreserveSig]
int MessagePending(IntPtr hTaskCallee, int dwTickCount, int dwPendingType);
}
}
‘@
Add-Type -TypeDefinition $source
}