Search code examples
c#delegatesinteropintptrphysfs

Pointer of a C# object for unmanaged interop


I am currently writing a wrapper for the PhysFS library, and I stumbled across a bit of troubles regarding the marshalling of managed objects. Take for example the PHYSFS_enumerateFilesCallback method, which takes a function pointer and a user-defined pointer as its arguments. How can I pass managed objects to this method? This is what I am currently doing:

// This is the delegate signature
public delegate void EnumFilesCallback(IntPtr data, string origdir, string fname);

// This is the method signature
[DllImport(DLL_NAME, CallingConvention = CallingConvention.Cdecl)]
public static extern void PHYSFS_enumerateFilesCallback(string dir, EnumFilesCallback c, IntPtr d);

Finally, this is what I'm doing to pass an arbitrary object to the method:

// I use the unsafe keyword because the whole Interop class is declared so.
// This code was taken from https://msdn.microsoft.com/en-us/library/system.runtime.interopservices.gchandle(VS.71).aspx
public static void EnumerateFilesCallback(string dir, EnumFilesCallback c, object data)
{
  unsafe
  {
    GCHandle objHandle = GCHandle.Alloc(data);
    Interop.PHYSFS_enumerateFilesCallback(dir, c, (IntPtr)objHandle);
    objHandle.Free();
  }
}

When I run this code:

static void Enum(IntPtr d, string origdir, string fname )
{
  System.Runtime.InteropServices.GCHandle handle = (System.Runtime.InteropServices.GCHandle)d;
  TestClass c = (TestClass)handle.Target;
  Console.WriteLine("{0} {1}", origdir, fname);
}

static void Main(string[] args)
{
  PhysFS.Init("");
  PhysFS.Mount("D:\\", "/hello", true);

  TestClass x = new TestClass() { a = 3, b = 4 }; // This can be any gibberish object

  PhysFS.EnumerateFilesCallback("/hello/", Enum, x);
}

The delegate gets called 4 times with legit data, the fifth time it contains garbage data and then it throws an AccessViolationException I suspect this is because the object gets GCed in between the calls to the delegate. Can anyone shed light on this?

UPDATE: Changing the mounted directory eliminates the rubbish data, yet the exception is still thrown, and still before all the data can be consumed


Solution

  • Thanks to everyone who invested their time trying to provide an answer! I've finally found the source of the problem and solved it!

    The problem was... I am a bit ashamed of it... calling convention. All the PInvoked methods were declared as cdecl while I forgot to declare the delegates as such, so it created unbalanced stacks and mayhem and whatnot...