PInvoke - eigene Ref-Types ("class") als Parameter (ala SafeHandle)?



  • Ja, klar, Wrapper kann man immer schreiben.
    Ist aber recht aufwendig, und je mehr man dabei beachten muss desto lästiger wird das Schreiben dieser Wrapper.

    Davon abgesehen interessiert mich das Thema, und daher würde ich gern mehr darüber wissen was man da alles machen kann und wie.

    Was mir nach meinen Recherchen bisher z.B. immer noch nicht klar ist, ist wie SafeHandle funktioniert. Also wie das geht dass da der gekapselte IntPtr als normaler Parameter übergeben wird, und nicht ein Zeiger auf die gepinnte SafeHandle Klasse.
    Bzw. wie die es schaffen dass wenn man SafeHandle als Returnwert verwendet automatisch ne neue Instanz angelegt wird, und diese dann mit dem retournierten Objekt "befüllt" wird.

    Nimm ein class Image und darin hast du halt ein struct NativeImage.

    Das geht gut, wenn man wenige Datentypen hat, die von wenigen Funktionen erzeugt/verwendet werden.
    Wenn man wenige Datentypen hat die von verdammt vielen Funktionen erzeugt/verwendet werden, dann ist es nicht mehr so lustig.
    Bzw. dann zahlt es sich aus, zu gucken dass man die PInvoke Bereiche selbst so stressfrei & angenehm wie möglich gestalten kann.


  • Administrator

    Aus Interesse habe ich mich mal selbst ein wenig schlauer gemacht. SafeHandle kann man wunderbar mit P/Invoke verwenden. Ein SafeHandle wird vom Marshaller automatisch auf einen IntPtr oder umgekehrt gemappt.

    Aber es ist eben ein IntPtr . Heisst man hat keinen Zugriff auf die Daten in der Struktur oder du müsstest die Marshal Klasse dafür verwenden ( PtrToStructure ).

    Das Ganze funktioniert auch relativ einfach:

    #include <iostream>
    
    struct Image
    {
      int width;
      int height;
    };
    
    #define MY_EXPORT extern "C" __declspec(dllexport)
    
    MY_EXPORT void CreateImage(int width, int height, Image** img)
    {
      *img = new Image();
    
      (*img)->width = width;
      (*img)->height = height;
    }
    
    MY_EXPORT int GetWidth(Image* img)
    {
      return img->width;
    }
    
    MY_EXPORT int GetHeight(Image* img)
    {
      return img->height;
    }
    
    MY_EXPORT int DestroyImage(Image* img)
    {
      std::cout << "Bumm!\n";
    
      delete img;
      return 0;
    }
    
    using System;
    using System.Runtime.InteropServices;
    
    namespace SharpTest
    {
      [StructLayout(LayoutKind.Sequential)]
      struct Image
      {
        private readonly int m_width;
        private readonly int m_height;
    
        public int Width { get { return m_width; } }
        public int Height { get { return m_height; } }
      };
    
      static class NativeMethods
      {
        [DllImport("NativeLibTest.dll", CallingConvention = CallingConvention.Cdecl)]
        public static extern void CreateImage(int width, int height, out SafeImageHandle img);
    
        [DllImport("NativeLibTest.dll", CallingConvention = CallingConvention.Cdecl)]
        public static extern int GetWidth(SafeImageHandle img);
    
        [DllImport("NativeLibTest.dll", CallingConvention = CallingConvention.Cdecl)]
        public static extern int GetHeight(SafeImageHandle img);
    
        [DllImport("NativeLibTest.dll", CallingConvention = CallingConvention.Cdecl)]
        public static extern int DestroyImage(IntPtr img);
      }
    
      class SafeImageHandle
        : SafeHandle
      {
        public override bool IsInvalid
        {
          get { return handle == IntPtr.Zero; }
        }
    
        public SafeImageHandle()
          : base(IntPtr.Zero, true)
        {
        }
    
        protected override bool ReleaseHandle()
        {
          return NativeMethods.DestroyImage(handle) == 0;
        }
      }
    
      public class Program
      {
        public static void Main(string[] args)
        {
          SafeImageHandle handle;
          NativeMethods.CreateImage(100, 200, out handle);
    
          var image = (Image)Marshal.PtrToStructure(handle.DangerousGetHandle(), typeof(Image));
    
          Console.WriteLine("Native Width:  {0}", NativeMethods.GetWidth(handle));
          Console.WriteLine("Managed Width: {0}", image.Width);
          Console.WriteLine();
          Console.WriteLine("Native Height:  {0}", NativeMethods.GetHeight(handle));
          Console.WriteLine("Managed Height: {0}", image.Height);
    
          handle.Dispose();
    
          Console.ReadKey(true);
        }
      }
    }
    

    Referenzen:
    http://msdn.microsoft.com/en-us/library/vstudio/system.runtime.interopservices.safehandle.aspx
    http://msdn.microsoft.com/en-us/library/system.runtime.interopservices.marshal.aspx
    http://blogs.msdn.com/b/bclteam/archive/2005/03/15/396335.aspx

    hustbaer schrieb:

    Speziell wäre schön wenn man sowas schreiben könnte:

    // C: void CreateImage(int w, int h, Image* image);
    [DllImport(...)]
    [return: MarshalAs(...)]
    MyImageClassThing CreateImage(int w, int h);
    

    Bei meinen Versuchen bisher bekomme ich leider immer irgend eine Exception 😞
    Mit MyImageStructThing dagegen funktioniert es.

    Ich nehme hier mal an, dass du die folgende C Signature gemeint hast:

    void CreateImage(int w, int h, Image** image);
    //                                   ^
    

    Diese Umwandlung ist mir nur von COM bekannt mit HRESULT Rückgabewerte. Schau dir dazu DllImport.PreserveSig an. Ich wüsste nicht, wie man sowas sonst automatisch erreichen kann. Man kann natürlich selber einen Wrapper für diese Funktion schreiben, aber das dürfte dir klar sein 🙂

    Grüssli



  • Dravere schrieb:

    Aus Interesse habe ich mich mal selbst ein wenig schlauer gemacht. SafeHandle kann man wunderbar mit P/Invoke verwenden. Ein SafeHandle wird vom Marshaller automatisch auf einen IntPtr oder umgekehrt gemappt.

    Ja, P/Invoke behandelt SafeHandle so als ob es nur der gekapselte IntPtr wäre, und nicht so wie andere Klassen (die als Zeiger auf ihren "Inhalt" gemarshalt werden).
    Nur weiss ich nicht wieso der Marshaler das macht -- woher weiss er dass er SafeHandle so zu behandeln hat?

    Dravere schrieb:

    Ich nehme hier mal an, dass du die folgende C Signature gemeint hast:

    void CreateImage(int w, int h, Image** image);
    //                                   ^
    

    Naja, eben nicht, ich meine schon einen einfachen Zeiger.
    Ich meine so Funktionen wie IPropertyStore::GetValue.
    Also welche wo der Aufrufer den Speicher für die Struktur zur Verfügung stellen muss, und die Funktion da dann einfach nur Werte reinschreibt.

    Wäre auch ganz einfach mit ner struct zu machen. Bloss gibt es leider sehr viele Variant-Typen die "Cleanup" benötigen. Und da ein struct keinen Finalizer haben kann, und noch dazu frei rumkopiert werden kann, ist das dann nicht so gut.

    Jetzt hab ich mir gedacht...
    ...da der PROPVARIANT* Parameter von GetValue "out" ist, darf man da sowieso nie einen "bestehenden" PROPVARIANT übergeben, da der ja dann nicht korrekt freigegeben würde. Es wäre also schön, wenn man dem .NET Marshaler beibringen könnte, dass er dort immer ein neues Objekt einer bestimmten Klasse erzeugen soll, und die Adresse dieses Objekts an GetValue übergeben.

    DllImport.PreserveSig ist mir auch bekannt. Mit COM-Interop gehen sogar noch viel coolere Sachen, da kann man z.B. den letzten Output-Parameter einfach als Returnwert deklarieren, und der Marshaler ist schlau genug das richtige zu machen.
    Beispielsweise IRdcLibrary::CreateGenerator kann man folgendermassen abbilden:

    [ComImport, ComVisible(false)]
    	[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
    	[Guid("96236A78-9DBC-11DA-9E3F-0011114AE311")]
    	[CoClass(typeof(RdcLibrary))]
    	interface IRdcLibrary
    	{
    		// ...
    
    		//HRESULT CreateGenerator(
    		//  [in]   ULONG depth,
    		//  [in]   IRdcGeneratorParameters *iGeneratorParametersArray[depth],
    		//  [out]  IRdcGenerator **iGenerator
    		//);
    
    		[return: MarshalAs(UnmanagedType.Interface)]
    		IRdcGenerator CreateGenerator(
    			[In] uint depth,
    			[In, MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 0)] IRdcGeneratorParameters[] iGeneratorParametersArray);
    
    		// ...
    
    	}
    

  • Administrator

    hustbaer schrieb:

    Nur weiss ich nicht wieso der Marshaler das macht -- woher weiss er dass er SafeHandle so zu behandeln hat?

    Weil das von der CLR her so definiert ist? Der Marshaler ist ja Teil der CLR. Die CLR sieht somit eine Funktion, welche einen Parameter hat, dessen Typ von SafeHandle erbt. Dementsprechend reagiert die CLR und führt das korrekte Verhalten aus.
    SafeHandle besitzt intern ein Member vom Typ IntPtr . Statt also SafeHandle als Klasse zu behandeln, weiss die CLR, dass sie sich hierbei nur um diesen IntPtr kümmern und somit dieser überreicht werden muss.

    Ich verstehe nicht so recht, wo dein Problem darin liegt. Bei allen nicht Blittable Typen führt die CLR zusätzliche Aktionen aus.

    hustbaer schrieb:

    Naja, eben nicht, ich meine schon einen einfachen Zeiger.
    Ich meine so Funktionen wie IPropertyStore::GetValue.
    Also welche wo der Aufrufer den Speicher für die Struktur zur Verfügung stellen muss, und die Funktion da dann einfach nur Werte reinschreibt.

    Wäre auch ganz einfach mit ner struct zu machen. Bloss gibt es leider sehr viele Variant-Typen die "Cleanup" benötigen. Und da ein struct keinen Finalizer haben kann, und noch dazu frei rumkopiert werden kann, ist das dann nicht so gut.

    Wieso nimmst du dann keine Klasse?

    MY_EXPORT void SetWidth(Image* img, int width)
    {
      img->width = width;
    }
    
    [StructLayout(LayoutKind.Sequential)]
    class Image2
    {
      private readonly int m_width;
      private readonly int m_height;
    
      public int Width { get { return m_width; } }
      public int Height { get { return m_height; } }
    }
    
    // ...
    
    [DllImport("NativeLibTest.dll", CallingConvention = CallingConvention.Cdecl)]
    public static extern int SetWidth([Out] Image2 img, int width);
    //                                ^^^^^
    
    // ...
    
    var image = (Image2)Marshal.PtrToStructure(handle.DangerousGetHandle(), typeof(Image2));
    
    // ...
    
    NativeMethods.SetWidth(image, 400);
    

    Eine Klasse wird immer als Zeiger "germarshaled" (ich brauche eine vernünftige Übersetzung), wenn man den Parameter trotzdem als "Out" kennzeichnen möchte, ohne dadurch einen Doppelzeiger zu erhalten, dann gibt es dafür das OutAttribute . Welches du hoffentlich bereits kennst, da du ja bereits das InAttribute verwendest.

    Das mit dem "automatisch Objekt erstellen und übergeben" wird wohl nicht möglich sein. Mir fällt zumindest keine Möglichkeit ein, wie man sowas automatisiert machen kann.

    Meine Erfahrung mit Interop über P/Invoke oder C++/CLI ist sowieso, dass man um das Schreiben von gewissem Wrappercode nicht herumkommt. Und es scheint mir, dass das dein Hauptziel ist, möglichst keinen Wrappercode zu schreiben 🙂

    Grüssli



  • Dravere schrieb:

    Statt also SafeHandle als Klasse zu behandeln, weiss die CLR, dass sie sich hierbei nur um diesen IntPtr kümmern und somit dieser überreicht werden muss.

    Und woher weiss sie das? Willst du damit sagen dass die CLR hardcoded eine Spezialbehandlung für SafeHandle eingebaut hätte? Das ist möglich, allerdings wird sowas meistens über irgendwelche Attribute, Interfaces oder sonstwas gemacht. Damit es auch möglich ist andere Klassen zu schreiben die ähnlich funktionieren.

    Ich verstehe nicht so recht, wo dein Problem darin liegt. Bei allen nicht Blittable Typen führt die CLR zusätzliche Aktionen aus.

    Na, ich will wissen wie das funktioniert. Was gibts da nicht zu verstehen?

    Dravere schrieb:

    Wieso nimmst du dann keine Klasse?

    MY_EXPORT void SetWidth(Image* img, int width)
    {
      img->width = width;
    }
    
    [StructLayout(LayoutKind.Sequential)]
    class Image2
    {
      private readonly int m_width;
      private readonly int m_height;
    
      public int Width { get { return m_width; } }
      public int Height { get { return m_height; } }
    }
    
    // ...
    
    [DllImport("NativeLibTest.dll", CallingConvention = CallingConvention.Cdecl)]
    public static extern int SetWidth([Out] Image2 img, int width);
    //                                ^^^^^
    
    // ...
    
    var image = (Image2)Marshal.PtrToStructure(handle.DangerousGetHandle(), typeof(Image2));
    
    // ...
    
    NativeMethods.SetWidth(image, 400);
    

    Ich will aber nicht vorher ein Objekt erzeugen müssen, ich will

    Image image = NativeMethods.CreateImage();
    

    schreiben können.
    Weil CreateImage eben ein neues Objekt erzeugt (=den alten Inhalt der Struktur deren Adresse übergeben wird einfach überschreibt, ohne zu gucken was vorher drin stand). Wenn ich dagegen

    Image image = new Image();
    NativeMethods.CreateImage(image);
    

    schreiben muss, birgt das immer die Gefahr dass jemand schludert und das übergebene Bild nicht "leer" ist. Dann würde der alte Inhalt von CreateImage nicht freigegeben, das Resultat wäre ein Resource-Leak.

    Dravere schrieb:

    Eine Klasse wird immer als Zeiger "germarshaled" (ich brauche eine vernünftige Übersetzung),

    Ja. Bis auf SafeHandle lustigerweise, aber das Thema hatten wir ja schon.

    Dravere schrieb:

    wenn man den Parameter trotzdem als "Out" kennzeichnen möchte, ohne dadurch einen Doppelzeiger zu erhalten, dann gibt es dafür das OutAttribute . Welches du hoffentlich bereits kennst, da du ja bereits das InAttribute verwendest.

    Ja, ist mir auch bekannt. Wobei das [Out] in deinem Beispiel IMO auch falsch ist, SetWidth wird ja nicht ein neues Objekt erzeugen, also müsste es wenn dann schon [In, Out] sein.

    Dravere schrieb:

    Das mit dem "automatisch Objekt erstellen und übergeben" wird wohl nicht möglich sein. Mir fällt zumindest keine Möglichkeit ein, wie man sowas automatisiert machen kann.

    Ich weigere mich erstmal das zu glauben. Irgendwie kann man anscheinend auch custom-marshaling machen, bloss hab' ich dummerweise noch kein vernünftiges Beispiel dafür gefunden.

    Dravere schrieb:

    Meine Erfahrung mit Interop über P/Invoke oder C++/CLI ist sowieso, dass man um das Schreiben von gewissem Wrappercode nicht herumkommt. Und es scheint mir, dass das dein Hauptziel ist, möglichst keinen Wrappercode zu schreiben 🙂

    Kann man so sagen. Ich will einfach dass man beim Schreiben von Code der mit P/Invoke arbeitet möglichst wenig Fehler machen kann.


  • Administrator

    hustbaer schrieb:

    Willst du damit sagen dass die CLR hardcoded eine Spezialbehandlung für SafeHandle eingebaut hätte?

    Ja.

    Ich leite das aus den oben angegebenen Referenzen ab. Zum Beispiel daraus:
    http://msdn.microsoft.com/en-us/library/system.runtime.interopservices.safehandle.aspx

    MSDN schrieb:

    With SafeHandle, the CLR's platform invoke marshaling layer will store the handle into the SafeHandle object in an atomic fashion.

    Die CLR schreibt in das SafeHandle Objekt.

    Auch die Basisklasse von SafeHandle erhält eine Spezialbehandlung von der CLR:
    http://msdn.microsoft.com/en-us/library/system.runtime.constrainedexecution.criticalfinalizerobject.aspx

    MSDN schrieb:

    In classes derived from the CriticalFinalizerObject class, the common language runtime (CLR) guarantees that all critical finalization code will be given the opportunity to execute, provided the finalizer follows the rules for a CER, even in situations where the CLR forcibly unloads an application domain or aborts a thread. If a finalizer violates the rules for a CER, it might not successfully execute. In addition, the CLR establishes a weak ordering among normal and critical finalizers: for objects reclaimed by garbage collection at the same time, all the noncritical finalizers are called before any of the critical finalizers. For example, a class such as FileStream, which holds data in the SafeHandle class that is derived from CriticalFinalizerObject, can run a standard finalizer to flush out existing buffered data.

    hustbaer schrieb:

    Das ist möglich, allerdings wird sowas meistens über irgendwelche Attribute, Interfaces oder sonstwas gemacht. Damit es auch möglich ist andere Klassen zu schreiben die ähnlich funktionieren.

    Bei weitem nicht alles. Oder nenn mir z.B. mal, wie du einen String als UTF-8 kodiert einer Funktion per P/Invoke übergeben kannst. Du kannst den Marshaler anweisen, den String als Ansi oder Unicode (heisst UTF-16) zu übergeben. Sobald du eine andere Kodierung willst, musst du das selber machen und Wrappercode schreiben.

    hustbaer schrieb:

    Dravere schrieb:

    wenn man den Parameter trotzdem als "Out" kennzeichnen möchte, ohne dadurch einen Doppelzeiger zu erhalten, dann gibt es dafür das OutAttribute . Welches du hoffentlich bereits kennst, da du ja bereits das InAttribute verwendest.

    Ja, ist mir auch bekannt. Wobei das [Out] in deinem Beispiel IMO auch falsch ist, SetWidth wird ja nicht ein neues Objekt erzeugen, also müsste es wenn dann schon [In, Out] sein.

    Stimmt, ich müsste "In" angeben, aber aus einem anderen Grund. "Out" bedeutet hier nur die Richtung, in welche Daten geschoben werden. Heisst es ist egal, welche Daten reingehen, es ist aber wichtig, welche Daten rauskommen. Heisst der Marshaler muss sich nur um veränderte Daten vom Callee zum Caller kümmern. Das Problem, welches hier aber nun auftauchen könnte, wäre das der Marshaler eine neue Struktur anlegt mit uninitialisierten Werten und diese der Funktion übergibt. Und wenn die Funktion fertig ist, kopiert er die Daten der Struktur in das Managed Objekt rein. Wodurch der Wert Height einen uninitialisierten Wert erhalten würde. Passierte hier aber nicht, weil die Struktur blittable ist und dadurch hat der Marshaler einfach die Managed Struktur während des Aufrufes gepinnt. "Out" bedeutet hier aber nicht, dass ein neues Image Objekt von SetWidth erzeugt wird. Es bedeutet nur, dass die Daten bei diesem Parameter vom Callee zum Caller fliessen. Heisst die Daten in der Struktur werden verändert und müssen zurück kopiert werden.

    hustbaer schrieb:

    Ich weigere mich erstmal das zu glauben. Irgendwie kann man anscheinend auch custom-marshaling machen, bloss hab' ich dummerweise noch kein vernünftiges Beispiel dafür gefunden.

    Custom Marshaling ist soweit ich das bisher verstanden habe nur für COM Schnittstellen. Und dient dazu eine Rückwärtskompatibilität zur gewährleisten.
    Custom Marshaling
    HOW TO: Write a Custom Marshaler for COM Interoperation by Using Visual C# .NET (2003)
    Create a Custom Marshaling Implementation Using .NET Remoting and COM Interop (2003)

    Falls du über diesen Weg eine Möglichkeit findest, wie du dein gewünschtes Verhalten implementieren kannst, wäre ich sehr daran interessiert 😉

    Grüssli



  • Dravere schrieb:

    hustbaer schrieb:

    Dravere schrieb:

    wenn man den Parameter trotzdem als "Out" kennzeichnen möchte, ohne dadurch einen Doppelzeiger zu erhalten, dann gibt es dafür das OutAttribute . Welches du hoffentlich bereits kennst, da du ja bereits das InAttribute verwendest.

    Ja, ist mir auch bekannt. Wobei das [Out] in deinem Beispiel IMO auch falsch ist, SetWidth wird ja nicht ein neues Objekt erzeugen, also müsste es wenn dann schon [In, Out] sein.

    Stimmt, ich müsste "In" angeben, aber aus einem anderen Grund. "Out" bedeutet hier nur die Richtung, in welche Daten geschoben werden. Heisst es ist egal, welche Daten reingehen, es ist aber wichtig, welche Daten rauskommen. Heisst der Marshaler muss sich nur um veränderte Daten vom Callee zum Caller kümmern. Das Problem, welches hier aber nun auftauchen könnte, wäre das der Marshaler eine neue Struktur anlegt mit uninitialisierten Werten und diese der Funktion übergibt. Und wenn die Funktion fertig ist, kopiert er die Daten der Struktur in das Managed Objekt rein. Wodurch der Wert Height einen uninitialisierten Wert erhalten würde. Passierte hier aber nicht, weil die Struktur blittable ist und dadurch hat der Marshaler einfach die Managed Struktur während des Aufrufes gepinnt. "Out" bedeutet hier aber nicht, dass ein neues Image Objekt von SetWidth erzeugt wird. Es bedeutet nur, dass die Daten bei diesem Parameter vom Callee zum Caller fliessen. Heisst die Daten in der Struktur werden verändert und müssen zurück kopiert werden.

    Ne 🙂
    Etwas das nicht vom Caller zum Callee kopiert wurde, kann nicht vom Callee verändert werden. Der Callee kann maximal einen neuen Satz Daten erzeugen.
    Verändern impliziert dass vorher ein (sinnvoller) Zustand besteht, der dann halt geändert wird. Was ohne [In] eben nicht gegeben ist.

    Wenn nur [Out] gesetzt ist, wird, wie du richtig geschrieben hast, nur "zurückkopiert". Semantisch heisst das das ein neues "Ding" wurde erstellt. (Wobei ich mit "Ding" jetzt nicht ein CLR-Objekt meine, sondern das logische "Ding"/"Objekt" das aus den gemarshalten Werten besteht.)

    Dravere schrieb:

    Custom Marshaling ist soweit ich das bisher verstanden habe nur für COM Schnittstellen.

    Naja, ja, dafür gedacht auf jeden Fall.
    Es scheint aber so als ob man es für andere Dinge misbrauchen könnte. Zumindest gibt es ein paar Beispiele im Netz wo das so gemacht wird.
    Hab aber keine Ahnung ob das ne gute Idee ist 🙂

    Dravere schrieb:

    Falls du über diesen Weg eine Möglichkeit findest, wie du dein gewünschtes Verhalten implementieren kannst, wäre ich sehr daran interessiert 😉

    Mach ich 🙂


  • Administrator

    hustbaer schrieb:

    Etwas das nicht vom Caller zum Callee kopiert wurde, kann nicht vom Callee verändert werden. Der Callee kann maximal einen neuen Satz Daten erzeugen.
    Verändern impliziert dass vorher ein (sinnvoller) Zustand besteht, der dann halt geändert wird. Was ohne [In] eben nicht gegeben ist.

    Wenn nur [Out] gesetzt ist, wird, wie du richtig geschrieben hast, nur "zurückkopiert". Semantisch heisst das das ein neues "Ding" wurde erstellt. (Wobei ich mit "Ding" jetzt nicht ein CLR-Objekt meine, sondern das logische "Ding"/"Objekt" das aus den gemarshalten Werten besteht.)

    Ok, wir meinen, glaube ich, das Gleiche, ich störe mich nur an den Wörtern "erzeugen" und "erstellen". Es wird ja kein Objekt erstellt. Es wird wenn schon vollständig initialisiert oder beschrieben.

    Grüssli



  • Dravere schrieb:

    Ok, wir meinen, glaube ich, das Gleiche, ich störe mich nur an den Wörtern "erzeugen" und "erstellen".

    Ja, wir meinen wohl das gleiche.

    Dravere schrieb:

    Es wird ja kein Objekt erstellt. Es wird wenn schon vollständig initialisiert oder beschrieben.

    Naja, vom logischen Standpunkt her doch. Da ist für mich zumindest das Initialisieren (Konstruktor) das was das Objekt "erstellt", nicht das Besorgen des Speichers (operator new).
    Aber egal, du weisst jetzt was ich gemeint habe 🙂

    Das mit Custom-Marshaler war leider ein Schuss ins Ofenrohr. Das Ding macht ganz komische Dinge wenn man es zusammen mit nicht-COM-Interfaces verwendet, und noch komischere wenn man [Out] Parameter ohne out verwendet...
    -> Was ich wollte geht damit nicht.

    Dafür könnte ich das verwenden wofür die Custom-Marshaler eigentlich gedacht waren. Nämlich an den Stellen wo ich nen IPropertyStore zurückbekomme diesen in einen gemenagten PropertyStore verbasteln, ohne dass ich dazu gleich das ganze Interface wrappen muss dass den IPropertyStore zurückgibt.


  • Administrator

    Ich hatte gerade eine "Idee". Bevor du weiterliest, möchte ich dich mit zwei Dingen konfrontieren:
    1. Wie weit bist du bereit zu gehen?
    2. Der nachfolgende Code ist ein Proof Of Concept und soll nur eine Lösung skizzieren. Aber der Code funktioniert.

    Ich habe eine funktionierende Lösung gefunden. Sie ist allerdings krank und bringt eine Menge anderer Nachteile mit. Aber wer weiss ...

    struct Image
    {
      int width;
      int height;
    };
    
    #define MY_EXPORT extern "C" __declspec(dllexport)
    
    MY_EXPORT void InitImage(int width, int height, Image* img)
    {
      img->width = width;
      img->height = height;
    }
    
    using System;
    using System.Dynamic;
    using System.Runtime.InteropServices;
    
    namespace SharpTest
    {
      [StructLayout(LayoutKind.Sequential)]
      class Image
      {
        private readonly int m_width;
        private readonly int m_height;
    
        public Image()
        {
          m_width = 0;
          m_height = 0;
        }
    
        public int Width { get { return m_width; } }
        public int Height { get { return m_height; } }
      }
    
      static class NativeMethods
      {
        [DllImport("NativeLibTest.dll", EntryPoint = "InitImage", CallingConvention = CallingConvention.Cdecl)]
        public static extern void CreateImage(int width, int height, [Out] Image img);
      }
    
      class NativeCaller
        : DynamicObject
      {
        public override bool TryInvokeMember(InvokeMemberBinder binder, object[] args, out object result)
        {
          var method = typeof(NativeMethods).GetMethod(binder.Name);
    
          var parameters = method.GetParameters();
    
          if((parameters.Length - 1) != args.Length)
          {
            result = null;
            return false;
          }
    
          var lastParameter = parameters[parameters.Length - 1];
    
          var arguments = new object[args.Length + 1];
    
          for(int i = 0; i < args.Length; ++i)
          {
            arguments[i] = args[i];
          }
    
          result = arguments[args.Length] = Activator.CreateInstance(lastParameter.ParameterType);
    
          method.Invoke(null, arguments);
    
          return true;
        }
      }
    
      public class Program
      {
        public static void Main(string[] args)
        {
          dynamic nativeCaller = new NativeCaller();
    
          var image = nativeCaller.CreateImage(100, 200);
    
          Console.WriteLine("Width: {0}\tHeight: {1}", image.Width, image.Height);
    
          Console.ReadKey(true);
        }
      }
    }
    

    Das NativeCaller Objekt passt den Aufruf der Methode entsprechend an und erstellt für dich das letzte Argument und gibt dieses als Resultat zurück. Die Klasse NativeCaller , einmal korrekt implementiert, muss nicht mehr angepasst werden und lädt alle nötigen Informationen aus den Methoden definiert in NativeMethods . Die importierten Methoden definiert man wie bisher.

    Es lebe dynamic ! 😃 🤡

    Grüssli


Anmelden zum Antworten