Entwickler-Ecke
Basistechnologien - C# Com Interop mit Generics
jaenicke - Di 21.06.22 20:20
Titel: C# Com Interop mit Generics
Hallo,
ja, ich schon wieder. :D
Diesmal versuche ich generische Interfaces durch die Com-Interop Schnittstelle als nativ exportierte DLL-Funktion zu schleusen. Ich bekomme aber den Fehler, dass ein Marshalling für generische Interfaces nicht möglich ist.
Natürlich liegt das ganze wieder mit beiden Projekten und auch kompiliert bei, siehe Anhang.
Auf Delphi-Seite sieht mein Interface so aus:
Delphi-Quelltext
1: 2: 3: 4: 5:
| IDemo = interface ['{2CF84A17-7D31-46BE-AF3A-5977122C58BF}'] procedure TestIntList(Value: IInteropList<Integer>); safecall; procedure TestInteropList(Value: IInteropList<ITest>); safecall; end; |
Auf der C# Seite würde ich das nun gerne genauso schreiben:
C#-Quelltext
1: 2: 3: 4: 5: 6: 7: 8:
| [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] [GuidAttribute("2CF84A17-7D31-46BE-AF3A-5977122C58BF")] [ComVisible(true)] public interface IDemo { void TestIntList(IInteropList<int> Value); void TestInteropList(IInteropList<ITest> Value); } |
Leider bekomme ich dann beim Aufruf einer der Funktionen den Fehler:
Zitat: |
---------------------------
Project2
---------------------------
"parameter #1" kann nicht gemarshallt werden: Generische Typen können nicht gemarshallt werden.
---------------------------
OK
---------------------------
|
Nun habe ich eine Notlösung gefunden. Auf der Delphi-Seite bleibt alles generisch, aber auf der C# Seite deklariere ich das Interface so:
C#-Quelltext
1: 2: 3: 4: 5:
| public interface IDemo { void TestIntList(IIntList Value); void TestInteropList(ITestList Value); } |
Dabei deklariere ich die IIntList und ITestList 1:1 wie das generische Interface, nur mit konkreten Typen:
C#-Quelltext
1: 2: 3: 4: 5: 6: 7: 8:
| public interface ITestList { int GetCount(); ITest GetItem(int Index); void SetItem(int Index, ITest Value); int Add(ITest Value); void Remove(int Index); } |
Das ist natürlich nicht so schön. Gibt es irgendeine Möglichkeit das auf C# Seite hinzubekommen?
Casten scheint auch nicht zu gehen... (Ich habe z.B. versucht IntPtr als Typ des Parameters zu verwenden und dann hart auf das generische Interface zu casten.)
Vielen Dank schon einmal!
Viele Grüße
Sebastian Jänicke
Palladin007 - Mi 22.06.22 10:40
Mehr geraten, als gewusst - probier doch mal sowas:
C#-Quelltext
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14:
| public interface IInteropList<T> { int GetCount(); T GetItem(int index); void SetItem(int index, T value); int Add(T value); void Remove(int index); } public interface IInteropIntList : IInteropList<int> { } public interface IInteropTestList : IInteropList<ITest> { } |
Damit kannst Du mit dem "original"-Interface arbeiten, die Interop-Methoden bekommen aber die nicht generischen Varianten.
Etwas extra Schreibarbeit, aber nur wenig und der restliche Code kann generisch arbeiten.
Ansonsten habe ich auch nur die Aussage gefunden, dass generics nicht gehen.
jaenicke - Mi 22.06.22 11:20
Danke für eure Antworten!
Ich wollte ja um die Deklaration der IIntList und ITestList herum kommen und direkt ein generisches Interface nutzen. Auch die genannte Funktion hilft dabei leider nicht. Der Cast auf das generische Interface geht schief.
Ja, Arrays von einfachen Typen würden vielleicht gehen. Aber das reicht mir nicht. Ich verwende Listen von Interfaces, um so z.B. Ergebnisse von gesammelten Anfragen zurück liefern zu können.
Palladin007 hat folgendes geschrieben : |
C#-Quelltext 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14:
| public interface IInteropList<T> { int GetCount(); T GetItem(int index); void SetItem(int index, T value); int Add(T value); void Remove(int index); } public interface IInteropIntList : IInteropList<int> { } public interface IInteropTestList : IInteropList<ITest> { } |
Damit kannst Du mit dem "original"-Interface arbeiten, die Interop-Methoden bekommen aber die nicht generischen Varianten.
Etwas extra Schreibarbeit, aber nur wenig und der restliche Code kann generisch arbeiten. |
Das hatte ich schon versucht. Das geht leider nicht, da in der neuen leeren Interfacedefinition selbst keine Methoden drin sind. Die sind nur in dem geerbten Interface vorhanden, aber davon weiß die andere Seite nichts, wenn ich dort die generische Definition verwende.
Ich könnte das auf beiden Seiten so machen, dann würde es vermutlich gehen, aber nur weil es in C# nicht geht, möchte ich nicht auch noch auf der anderen Seite unnötigen Code hinzufügen. Da dupliziere ich lieber den Code des Interfaces unter C# komplett.
Schade, dass ich unter C# nicht einfach den Pointer des einen Interfaces in die Variable hinein bekomme oder anderweitig aus einem IntPtr ohne Prüfung eine Variable vom Typ IInteropList<ITest> füllen kann. Das würde ja schon reichen (vermute ich...), aber das würde wohl auch ggf. den GC durcheinanderbringen...
Th69 - Mi 22.06.22 13:59
Ich denke, du kannst niemals ein generisches Interface von Delphi nach .NET durchreichen (denn dazu müßte ja das interne Speicherlayout komplett identisch sein).
jaenicke - Mi 22.06.22 15:10
Der Witz daran ist, dass es funktioniert, nur dass ich eben auf C# Seite so tun muss, als wäre es kein generisches Interface. Das Interface-Layout wäre in C# schon passend zu dem in Delphi. Das ist ja auch kein Wunder, da das Interface-Design in Delphi für COM gebaut wurde und entsprechend kompatibel ist und ebenso bei C#.
Ich habe auch aus Delphi heraus in der CPU-Ansicht durch debuggt bis zu der Stelle in der C#-DLL und habe dort den Pointer manuell im Speicher geändert. Das funktioniert, nur dass es dann beim Beenden knallt (vielleicht durch den GC). Ich finde aber eben keinen Weg das möglichst sauber im Quelltext in C# zu machen...
Insofern ist nun meine Lösung aktuell wie geschrieben, dass ich die Interfacedeklaration kopiere und die leere Klasse von der generischen Klasse ableite, damit auch der C# Compiler damit klarkommt.
Th69 - Mi 22.06.22 16:02
Ich meinte das generelle Casten auf ein generisches Interface von C# aus. Daher eben der Umweg über ein in C# erstelltes nicht-generisches Interface (Generics sind nicht einfach nur, wie z.B. Templates in C++, Source Code-Kopien, sondern beinhalten auch noch Verwaltungsstruktur).
Daher ist auch das Wort "blittable" beim Marshalling so wichtig, d.h. nur Datentypen welche 1:1 im Speicher gelesen und geschrieben werden können, ohne ihren Wert zu ändern (beim Datenaustausch zwischen .NET und nativen Sprachen wie C, C++ oder eben auch Delphi).
jaenicke - Mi 22.06.22 23:18
Danke, ja, dann bleibe ich bei der Lösung.
Entwickler-Ecke.de based on phpBB
Copyright 2002 - 2011 by Tino Teuber, Copyright 2011 - 2024 by Christian Stelzmann Alle Rechte vorbehalten.
Alle Beiträge stammen von dritten Personen und dürfen geltendes Recht nicht verletzen.
Entwickler-Ecke und die zugehörigen Webseiten distanzieren sich ausdrücklich von Fremdinhalten jeglicher Art!