Pinning an updateble struct before passing to unmanaged code?

后端 未结 6 2049
一个人的身影
一个人的身影 2021-02-04 11:11

I using some old API and need to pass the a pointer of a struct to unmanaged code that runs asynchronous.

In other words, after i passing the struct pointer to the unman

相关标签:
6条回答
  • 2021-02-04 11:44

    Struct example:

    [StructLayout(LayoutKind.Sequential)]
    public struct OVERLAPPED_STRUCT
    {
       public IntPtr InternalLow;
       public IntPtr InternalHigh;
       public Int32 OffsetLow;
       public Int32 OffsetHigh;
       public IntPtr EventHandle;
    }
    

    How to pin it to the struct and use it:

    OVERLAPPED_STRUCT over_lapped = new OVERLAPPED_STRUCT();
    // edit struct in managed code
    over_lapped.OffsetLow = 100;
    IntPtr pinned_overlap_struct = Marshal.AllocHGlobal(Marshal.SizeOf(over_lapped));
    Marshal.StructureToPtr(over_lapped, pinned_overlap_struct, true);
    
    // Pass pinned_overlap_struct to your unmanaged code
    // pinned_overlap_struct changes ...
    
    // Get resulting new struct
    OVERLAPPED_STRUCT nat_ov = (OVERLAPPED_STRUCT)Marshal.PtrToStructure(pinned_overlap_struct, typeof(OVERLAPPED_STRUCT));
    // See what new value is
    int offset_low = nat_ov.OffsetLow;
    // Clean up
    Marshal.FreeHGlobal(pinned_overlap_struct);
    
    0 讨论(0)
  • 2021-02-04 11:46

    To answer your edit:

    Just thought... is there a way to pin the parent object that contains the struct, and then get the pointer of the struct rather than the container object?

    I think so. If anything, you should be able to with a managed array of structures (possibly an array of one).

    Here is an example code:

        [StructLayout(LayoutKind.Sequential)]
        struct SomeStructure
        {
            public int first;
            public int second;
            public SomeStructure(int first, int second) { this.first=first; this.second=second; }
        }
        
        /// <summary>
        /// For this question on Stack Overflow:
        /// https://stackoverflow.com/questions/1850488/pinning-an-updateble-struct-before-passing-to-unmanaged-code
        /// </summary>
        private static void TestModifiableStructure()
        {
            SomeStructure[] objArray = new SomeStructure[1];
            objArray[0] = new SomeStructure(10, 10);
            
            GCHandle hPinned = GCHandle.Alloc(objArray, GCHandleType.Pinned);
    
            //Modify the pinned structure, just to show we can
            objArray[0].second = 42;
    
            Console.WriteLine("Before unmanaged incrementing: {0}", objArray[0].second);
            PlaceholderForUnmanagedFunction(hPinned.AddrOfPinnedObject());
            Console.WriteLine("Before unmanaged incrementing: {0}", objArray[0].second);
            
            //Cleanup
            hPinned.Free();
        }
        
        //Simulates an unmanaged function that accesses ptr->second
        private static void PlaceholderForUnmanagedFunction(IntPtr ptr)
        {
            int secondInteger = Marshal.ReadInt32(ptr, 4);
            secondInteger++;
            Marshal.WriteInt32(ptr, 4, secondInteger);
        }
    

    And its output:

    Before unmanaged incrementing: 42
    Before unmanaged incrementing: 43
    
    0 讨论(0)
  • 2021-02-04 11:48

    Is unsafe code an option?

    // allocate unmanaged memory
    Foo* foo = (Foo*)Marshal.AllocHGlobal(sizeof(Foo));
    
    // initialize struct
    foo->bar = 0;
    
    // invoke unmanaged function which remembers foo
    UnsafeNativeMethods.Bar(foo);
    Console.WriteLine(foo->bar);
    
    // update struct
    foo->bar = 10;
    
    // invoke unmanaged function which uses remembered foo
    UnsafeNativeMethods.Qux();
    Console.WriteLine(foo->bar);
    
    // free unmanaged memory
    Marshal.FreeHGlobal((IntPtr)foo);
    

    This compiles and doesn't throw an exception, but I don't have an unmanaged function at hand to test if it works.

    From MSDN:

    When AllocHGlobal calls LocalAlloc, it passes a LMEM_FIXED flag, which causes the allocated memory to be locked in place. Also, the allocated memory is not zero-filled.

    0 讨论(0)
  • 2021-02-04 11:56

    Using pinned memory in this case is not a good idea, given that the memory for the struct needs to be valid for a long time. GCHandle.Alloc() will box the structure and store it on the heap. With it being pinned, it will be a long term burden to the garbage collector as it needs to constantly find a way around the rock in the road.

    The simple solution is to allocate memory for the struct in unmanaged memory. Use Marshal.SizeOf() to get the size of the structure and Marshal.AllocCoTaskMem() to allocate the memory. That gets you the pointer you need to pass to the unmanaged code. Initialize the memory with Marshal.StructureToPtr(). And read updates to the structure written by the unmanaged code with PtrToStructure().

    If you do this frequently, you'll be constantly copying the structure. That could be expensive, depending on the size of the structure. To avoid that, use an unsafe pointer to access the unmanaged memory directly. Some basic syntax:

    using System;
    using System.Runtime.InteropServices;
    
    class Program {
      unsafe static void Main(string[] args) {
        int len = Marshal.SizeOf(typeof(Test));
        IntPtr mem = Marshal.AllocCoTaskMem(len);
        Test* ptr = (Test*)mem;
        ptr->member1 = 42;
        // call method
        //..
        int value = ptr->member1;
        Marshal.FreeCoTaskMem(mem);
      }
      public struct Test {
        public int member1;
      }
    }
    
    0 讨论(0)
  • 2021-02-04 11:57

    How about having the struct include an ActOnMe() interface and method something like:

    delegate void ActByRef<T1,T2>(ref T1 p1, ref T2 p2);
    interface IActOnMe<TT> {ActOnMe<T>(ActByRef<TT,T> proc, ref T param);}
    struct SuperThing : IActOnMe<SuperThing>
    {
      int this;
      int that;
      ...
      void ActOnMe<T>(ActByRef<SuperThing,T>, ref T param)
      {
        proc(ref this, ref param);
      }
    }
    

    Because the delegate takes a generic parameter by reference, it should be possible in most cases to avoid the overhead of creating closures by passing a delegate to a static method along with a reference to a struct to carry data to or from that method. Further, casting an already-boxed instance of SuperThing to IActOnMe<SuperThing> and calling ActOnMe<T> on it will expose the fields of that boxed instance for updating, as opposed to creating another copy of them as would occur with a typecast to the struct.

    0 讨论(0)
  • 2021-02-04 12:06

    Instead of pinning, you need to use Marshal.StructureToPtr and Marshal.PtrToStructure to marshal the struct into memory that's usable in native code.

    0 讨论(0)
提交回复
热议问题