Update2: Open sourced the bindings. See append2 at the bottom for an update.
I've finally thought of another topic to write about. Or, rather, the topic fell on my desk. Again. It is about using managed methods as callbacks in native code. I first learned about this months (a year?) ago when I was working on a ZBar.Processor proxy class for zbar-sharp (.NET bindings for the zbar bar code scanning library). This was my first real experience using P/Invoke.
The
zbar_processor
is effectively a high-level object to scan frames from a Web cam for bar codes and return the encoded data back to the calling application. The data is returned via a callback function. This seemed simple enough once I figured out how P/Invoke basically is done. I just wrote up a compatible method and passed it into the zbar API. While it did seem to work, the native library would eventually throw memory access violations.It turns out that the problem was me (SHOCK). Or rather, my code. Or rather, missing code. See in .NET the equivalent of a function pointer is basically a delegate instance. This is effectively a method object (I imagine it to be similar to a functor in C++). In any case, calling the delegate instance is equivalent to calling the method. Even the
this
reference is preserved.#digress Delegates In C#
A delegate type is declared like so:namespace Application
{
class Example
{
public delegate void foo(int bar);
}
}
What this does is basically declare a delegate type named "foo" with no return value and a single int
parameter. You can then create an instance of the delegate like so:foo f = new foo(MyMethod);
Where MyMethod is a static or non-static method that matches the signature. The instantiation can also be implicit:foo f = MyMethod;
For a more complete example:
using System;
namespace Application
{
class Program
{
static void Main()
{
new Program().Run();
}
public void Run()
{
// Using a method, explicit instantiation.
Example.foo f1 = new Example.foo(this.Foo);
// Using a method, implicit instantiation.
Example.foo f2 = this.Foo;
// Using a lambda, explicit instantiation.
Example.foo f3 = new Example.foo(
(int bar) => Console.WriteLine(bar));
// Using a lambda, implicit instantiation.
Example.foo f4 = (int bar) => Console.WriteLine(bar);
f1(1);
f2(2);
f3(3);
f4(4);
}
void Foo(int bar)
{
Console.WriteLine(bar);
}
}
class Example
{
public delegate void foo(int bar);
}
}
#enddigress
As I was saying before I so rudely interrupted me, I was missing a tiny little detail. In order to use one of these delegates as a callback in native code you'd first have to instantiate one. OK, first we actually need a native method to call, and a callback type.namespace Application
{
class Example
{
public class NativeMethods
{
public delegate void zbar_image_data_handler_t(
IntPtr image,
IntPtr userdata);
[DllImport("libzbar0")]
public static extern IntPtr zbar_processor_set_data_handler(
IntPtr processor,
zbar_image_data_handler_t handler,
IntPtr userdata);
}
}
}
Now in order to use it we simply do as above:using System;
namespace Application
{
class Program
{
static void Main()
{
new Program().Run();
}
public void Run()
{
IntPtr processor = IntPtr.Zero;
/*
* Use your imagination here. We would have had to call native
* methods to create and initialize a zbar_processor struct.
*/
var handler =
new Example.NativeMethods.zbar_image_data_handler_t(
this.ZBarProcessorImageDataHandler);
Example.NativeMethods.zbar_processor_set_data_handler(
processor,
handler,
null);
}
void ZBarProcessorImageDataHandler(IntPtr image, IntPtr userdata)
{
Console.WriteLine("Bar code successfully scanned!");
}
}
}
Pretty straightforward, really, but there is a problem! Can you see it?! Not likely because it isn't even in the code. Remember that .NET is managed and garbage collected and delegates are basically objects. What this means is that when the garbage collector finds a delegate that is no longer referenced by anything it will eventually garbage collect it. This was something that certainly hadn't occurred to me when I was writing this code. It wasn't until I sought help online that somebody (it was many months ago, but I'm guessing in #csharp on freenode) pointed out that the delegate object could be destroyed by the time the zbar library attempted to call it. So what is the solution? We need to keep a reference to it around so it isn't destroyed. We can do that by simply creating a field to hold it....
class Program
{
Example.NativeMethods.zbar_image_data_handler_t
imageDataHandler_;
...
public void Run()
{
IntPtr processor = IntPtr.Zero;
/*
* Use your imagination here. We would have had to call native
* methods to create and initialize a zbar_processor struct.
*/
this.imageDataHandler_ =
new Example.NativeMethods.zbar_image_data_handler_t(
this.ZBarProcessorImageDataHandler);
Example.NativeMethods.zbar_processor_set_data_handler(
processor,
this.imageDataHandler_,
null);
}
...
}
...
At this point, after fixing a few other subtle bugs, the program ran fine without access violations! Unfortunately, the zbar_processor (at least, at the time) wasn't very reliable and we ultimately ended up implementing the Web cam interface ourselves and passing the scanned frames to zbar.In any case, I've recently been writing .NET bindings for wkhtmltox, another native library. This one is for converting HTML/CSS Web pages to PDF documents. It works really well because it's based on the WebKit framework (hence, the "wk" in the name). So I forgot all about the lifetime of the callback delegates and ran into the same problem. Today I finally remembered and looked back at the zbar bindings to see what was wrong.
I've now fixed that by adding delegate fields to the appropriate proxy class, but unfortunately I'm STILL getting access violation exceptions. It's probably going to be some other subtle bug as a result of .NET speaking to native code. I haven't figured it out yet though.
If you have any ideas about what else would cause memory access violations in P/Invoke programs or have any questions about what I've shown then please leave a comment. :)
Append:
Seems there is more to the story than I thought. See the following thread for details:http://www.gamedev.net/topic/270670-pinvoke-callback-access-violation/
The gist of it is that there is a
System.Diagnostics.InteropServices.UnmanagedFunctionPointerAttribute
attribute that can be used to mark up your callback delegates with P/Invoke attributes (for example, calling convention). I'm not sure why, but specifying CallingConvention.CDecl
for my callback delegates seems to postpone or avoid the access violations. I don't really understand why because the functions of the library are being called with the default calling convention, which I think is CallingConvention.StdCall
. Attempting to specify CallingConvention.CDecl
for the native functions immediately causes the stack to become imbalanced/corrupted (according to the .NET runtime; I think I trust it in this case ;). It doesn't really make sense to me that the callbacks would use a different calling convention so I suspect that the different calling convention for the callbacks just coincidentally changes the way the memory is accessed to avoid violations... I don't know. I'm lost. :(
Duuude THANKS.
ReplyDeleteSaved my butt with this post.
I am fighting with this for last three days.
Thank you _very_ much. I was getting an access violation without any useful traceback, and running it in Mono only pointed me at the Mono VM itself, so that didn't give me anything either.
ReplyDeleteI added [UnmanagedFunctionPointer(CallingConvention = CallingConvention.CDecl)] to my delegate definition and it works. As you say in the append, I'm not entirely sure why, but I'd been banging my head against this for a day and a half, so thank you!
*realizes that he hasn't responded to either yet*
ReplyDeleteThanks for the comments, guys. I'm glad that it helped. :)
Hmm, I doubt that this is enough. The garbage collector is still allowed to move the object around (it's a copying garbage collector that compactifies the heap). That's why there's a "pinning" feature in .NET. Can't really say I understood how that works though.
ReplyDeleteThat sounds like a valid concern, john. I'm not familiar with the .NET runtime's garbage collector semantics, but I could see that being relevant. Alas, this is code that I'm not even sure is still in use, but hopefully it can help somebody else (or me in the future). Thanks for commenting!
Delete