Zitat:
Sieht gut aus oder ?
Nein, gar nicht.
Ein Event ist sowas wie eine Property. Man hat keinen Zugriff auf das Delegate, kann nur Handler hinzufügen oder entfernen (wenn man sie auch kennt!)
Der Hack auf der Seite führt macht doch das Deklarieren des Events als EVENT (nicht Property) komplett banane.
Das wirkt auf mich als hätte er solange rumprobiert, bis irgendwas ging...
Ein einfacher Event-Subscription-Mechanismus, der in .Net und anderen Umgebungen (wie Delphi) sehr gut funktioniert, sind Subscription wie man sie aus Java kennt:
http://www.delphipraxis.net/173317-%...ml#post1204018
Außerdem musst du IDispatch nehmen, also bekommst du weder Code-Completion, noch Compiler-Meldungen wenn du dich vertippst.
Ich bleibe mal bei deinem Hundebeispiel...
Hier die Interfaces:
Code:
[ComVisible(true),
InterfaceType(ComInterfaceType.InterfaceIsIUnknown),
Guid("22396704-484A-43CE-AA8D-0765DB317562")]
public interface INotifyEventHandler
{
void Invoke([MarshalAs(UnmanagedType.IUnknown)] object sender);
}
[ComVisible(true),
InterfaceType(ComInterfaceType.InterfaceIsIUnknown),
Guid("5B127B2D-4433-43DA-87A6-28B8CBF725D7")]
public interface INotifyEvent
{
void Add(INotifyEventHandler handler);
void Remove(INotifyEventHandler handler);
}
[ComVisible(true),
Guid("45A15623-E99C-466E-AE76-BFB4500CC900"),
InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
public interface IDog
{
INotifyEvent Bark { get; }
INotifyEvent Howl { get; }
INotifyEvent Eat { get; }
void PerformBark(int howOften);
}
Delphi-Quellcode:
INotifyEventHandler = interface(IUnknown)
['{22396704-484A-43CE-AA8D-0765DB317562}']
procedure Invoke(const sender : IUnknown); safecall;
end;
INotifyEvent = interface(IUnknown)
['{5B127B2D-4433-43DA-87A6-28B8CBF725D7}']
procedure Add(const handler: INotifyEventHandler); safecall;
procedure Remove(const handler: INotifyEventHandler); safecall;
end;
IDog = interface(IUnknown)
['{45A15623-E99C-466E-AE76-BFB4500CC900}']
function GetOnBark : INotifyEvent; safecall;
function GetOnHowl : INotifyEvent; safecall;
function GetOnEat : INotifyEvent; safecall;
property OnBark : INotifyEvent read GetOnBark;
property OnHowl : INotifyEvent read GetOnHowl;
property OnEat : INotifyEvent read GetOnEat;
procedure Bark(aHowOften : Integer); safecall;
end;
hier die Implmentierung in C#. Achte auf das "DllExport", um das zu kriegen musst du einen Rechtsklick auf dein Projekt machen, "manage Nuget Packages" und in dem Dialog suchst du nach dllexport (siehe screenie).
Code:
public class NotifyEvent : INotifyEvent
{
private readonly ISet<INotifyEventHandler> _Handlers = new HashSet<INotifyEventHandler>();
public void Add(INotifyEventHandler handler)
{
_Handlers.Add(handler);
}
public void Remove(INotifyEventHandler handler)
{
_Handlers.Remove(handler);
}
public void Invoke(object sender)
{
var handlersCopy = _Handlers.ToList();
handlersCopy.ForEach(h => h.Invoke(sender));
}
}
public class CSharpDog : IDog
{
public INotifyEvent Bark
{
get { return _Bark; }
}
public INotifyEvent Howl
{
get { return _Howl; }
}
public INotifyEvent Eat
{
get { return _Eat; }
}
readonly NotifyEvent _Bark = new NotifyEvent();
readonly NotifyEvent _Howl = new NotifyEvent();
readonly NotifyEvent _Eat = new NotifyEvent();
public void PerformBark(int howOften)
{
for (int i = 0; i < howOften; i += 1)
{
_Bark.Invoke(this);
}
}
[DllExport]
static void CreateDog([MarshalAs(UnmanagedType.Interface)]out IDog dog)
{
dog = new CSharpDog();
}
}
Auf der Delphi-Seit brauchen wir eine Implementierung für einen Eventhandler:
Delphi-Quellcode:
TNotifyEventHandler = class(TInterfacedObject, INotifyEventHandler)
private
fCallback : TProc<IUnknown>;
protected
procedure Invoke(const sender : IUnknown); safecall;
public
constructor Create(aCallBack: TProc<IUnknown>); overload;
constructor Create(aCallBack: TProc); overload;
end;
{ TNotifyEventHandler }
constructor TNotifyEventHandler.Create(aCallBack: TProc<IUnknown>);
begin
fCallback := aCallBack;
end;
constructor TNotifyEventHandler.Create(aCallBack: TProc);
begin
fCallback := procedure(sender : IUnknown) begin
aCallBack();
end;
end;
procedure TNotifyEventHandler.Invoke(const sender: IInterface);
begin
fCallback(sender);
end;
Und das war's dann fast schon. Im C# Projekt musst du noch die CPU-Plattform passend zu deinem Delphi-Projekt (wohl x86) wählen.
Delphi-Quellcode:
procedure CreateDog(out dog : IDog); stdcall; external 'DeineCSharpClassLibrary';
var
dog : IDog;
barkCounter : Integer;
begin
CoInitialize(nil);
CreateDog(dog);
barkCounter := 0;
dog.OnBark.Add(TNotifyEventHandler.Create(procedure begin
inc(barkCounter);
end));
dog.Bark(6);
dog.Bark(2);
Writeln('dog barked ', barkCounter, ' times...');
end.
Zitat von
Ouput:
dog barked 8 times...
In der Tat, ja. Dann bleiben Dir komplizierte Nicht-
COM Lösungen erspart.
Genau, weil
Com-Registrierungen auch immer problemlos funzen und jeder sich so gut mit SxS auskennt, dass auch wirklich jeder alles ohne globale Registrierung lösen kann...
Sicherlich ist
COM/Interop ein sehr guter Weg für viele, gerade komplexere, Szenarios. aber
COM ist auch saumäßig frickelig und kommt mit seinen eigenen Problemen daher. Zum Glück kann man fast alle
COM-Goodies aus .Net ohne
COM benutzen. Macht C++/CLI die meiste Zeit über auch nicht anders.
Einfach so zu behaupten, dass man keine RCWs über Reverse P/Invoke kriegt, oder es nur als Notlösung taugt, halte ich für ein Indiz einer sehr dogmatische Sichtweise. Ich kann mir kaum vorstellen, dass du wirklich so engstirnig bist.