Autor Beitrag
jaenicke
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starofftopic star
Beiträge: 19110
Erhaltene Danke: 1705

W11 x64 (Chrome, Edge)
Delphi 11 Pro, Oxygene, C# (VS 2022), JS/HTML, Java (NB), PHP, Lazarus
BeitragVerfasst: Di 21.06.22 20:20 
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:
ausblenden 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:
ausblenden 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:
ausblenden 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:
ausblenden 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
Einloggen, um Attachments anzusehen!
Th69
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starontopic star
Moderator
Beiträge: 4641
Erhaltene Danke: 1014

Win10
C#, C++ (VS 2015/17/19)
BeitragVerfasst: Mi 22.06.22 10:10 
Hallo,

so etwas habe ich bisher auch noch nicht gemacht, aber probiere mal Marshal.GetTypedObjectForIUnknown(IntPtr, Type) (ich weiß aber nicht, ob das auch für Schnittstellen funktioniert oder nur für konkrete Typen). Dabei müssen dann aber in deinem Fall IIntList und ITestList (importierte) COM-Typen sein.

Zu der Nichtunterstützung von generischen Interfaces habe ich noch Marshal should be able to handle generic types gefunden. Dies ginge wohl, aber nur für "blittable types": Updating the JIT to support marshaling blittable generics

Für diesen Beitrag haben gedankt: jaenicke
Palladin007
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starofftopic star
Beiträge: 1269
Erhaltene Danke: 176

Windows 10 x64 Home Premium
C# (Visual Studio Preview)
BeitragVerfasst: Mi 22.06.22 10:40 
Mehr geraten, als gewusst - probier doch mal sowas:

ausblenden 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.

Für diesen Beitrag haben gedankt: jaenicke
jaenicke Threadstarter
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starofftopic star
Beiträge: 19110
Erhaltene Danke: 1705

W11 x64 (Chrome, Edge)
Delphi 11 Pro, Oxygene, C# (VS 2022), JS/HTML, Java (NB), PHP, Lazarus
BeitragVerfasst: Mi 22.06.22 11:20 
Danke für eure Antworten!

user profile iconTh69 hat folgendes geschrieben Zum zitierten Posting springen:
probiere mal Marshal.GetTypedObjectForIUnknown(IntPtr, Type) (ich weiß aber nicht, ob das auch für Schnittstellen funktioniert oder nur für konkrete Typen). Dabei müssen dann aber in deinem Fall IIntList und ITestList (importierte) COM-Typen sein.
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.

user profile iconTh69 hat folgendes geschrieben Zum zitierten Posting springen:
Zu der Nichtunterstützung von generischen Interfaces habe ich noch Marshal should be able to handle generic types gefunden. Dies ginge wohl, aber nur für "blittable types": Updating the JIT to support marshaling blittable generics
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.

user profile iconPalladin007 hat folgendes geschrieben Zum zitierten Posting springen:
ausblenden 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
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starontopic star
Moderator
Beiträge: 4641
Erhaltene Danke: 1014

Win10
C#, C++ (VS 2015/17/19)
BeitragVerfasst: 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 Threadstarter
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starofftopic star
Beiträge: 19110
Erhaltene Danke: 1705

W11 x64 (Chrome, Edge)
Delphi 11 Pro, Oxygene, C# (VS 2022), JS/HTML, Java (NB), PHP, Lazarus
BeitragVerfasst: 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
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starontopic star
Moderator
Beiträge: 4641
Erhaltene Danke: 1014

Win10
C#, C++ (VS 2015/17/19)
BeitragVerfasst: 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 Threadstarter
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starofftopic star
Beiträge: 19110
Erhaltene Danke: 1705

W11 x64 (Chrome, Edge)
Delphi 11 Pro, Oxygene, C# (VS 2022), JS/HTML, Java (NB), PHP, Lazarus
BeitragVerfasst: Mi 22.06.22 23:18 
Danke, ja, dann bleibe ich bei der Lösung.