1

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

  • Looks like multiple problems, you'll definitely crash like this when the GC collects the delegate object that you asked the C# compiler to create for the `Enum` target. You have to store it. Smart thing to do here is to just use the existing C# wrapper, you'll find it in the *extras* subdirectory of the physfs source distribution. – Hans Passant Jun 28 '15 at 18:01
  • @HansPassant I already checked that library out, and I decided to write my own because that one didn't satisfy me :D In fact, it is a bit lacklustre and misses, for example, this function., therefore it is not of much help in solving this problem... – Francesco Bertolaccini Jun 28 '15 at 18:05
  • 1
    It is *very* hard to explain why it works at all, you should always get garbage arguments in the callback. Makes it hard to answer reliably of course. Well, good luck with it. – Hans Passant Jun 28 '15 at 18:10
  • I'd like to point out that I took the code from [this MSDN article](https://msdn.microsoft.com/en-us/library/system.runtime.interopservices.gchandle(VS.71).aspx), so this method must, to an extent, be valid. I understand I'm on an edge case, though. – Francesco Bertolaccini Jun 28 '15 at 18:13

2 Answers2

0

Have you tried to create the callback and store it as a class static field?

private static EnumFilesCallback callback = new EnumFilesCallback(Enum);

And in your main method:

PhysFS.EnumerateFilesCallback("/hello/", callback, x);

This should probably avoid the GC to collect the local variable holding the delegate object.

thepirat000
  • 12,362
  • 4
  • 46
  • 72
0

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