Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Pinning an updateble struct before passing to unmanaged code?

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 unmanaged code, the unmanaged code copies the pointer and returns immediately. The unmanaged code can access that struct in background, in another thread. I have no control over the unmanaged code that runs in another thread nor the thread itself.

The fixed { } statement can't be used for pinning because it not designed for async unmanaged pinning.

GCHandle can pin only references, so the struct must be boxed to use GCHandle. I tried it, and it works. The main problem with it, is that you can't update the struct from managed code. To update a struct, first of all we need to unbox it, then update, then box again, but... oops ... box again?!? this means the previous pointer in the memory still point to the old non-up-to-date struct, and the new struct have another pointer, and this means that i need to pass new pointer to the unmanaged code... inapplicable in my case.

How can i pin a struct in the memory without fixed { } statement, and so that i can update it from managed code without change it's pointer?

Thanks.

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?

like image 345
DxCK Avatar asked Dec 05 '09 00:12

DxCK


3 Answers

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;
  }
}
like image 106
Hans Passant Avatar answered Nov 19 '22 18:11

Hans Passant


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.

like image 42
dtb Avatar answered Nov 19 '22 16:11

dtb


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

like image 1
Reed Copsey Avatar answered Nov 19 '22 17:11

Reed Copsey