Howto use callback functions with a C DLL

J

Jef Driesen

I have a C DLL that I want to use from a C# project. The C header file
contains these declarations:

typedef void (*callback_t) (const unsigned char *data, unsigned int
size, void *userdata);
void myfunction (callback_t callback, void *userdata);

How do I translate this to C#?

I tried with:

delegate void callback_t (Byte[] data, UInt32 size, IntPtr userdata);
[DllImport("mydll.dll")]
static extern void myfunction (callback_t callback, IntPtr userdata);

When calling with myfunction (null, IntPtr.Zero) everything works as
expected. But once I start passing a callback function, the application
crashes with "Unhandled Exception: System.AccessViolationException:
Attempted to read or write protected memory. This is often an indication
that other memory is corrupt."

void test (Byte[] data, UInt32 size, IntPtr userdata)
{
// Nothing here
}

callback_t callback = new callback_t (test);
myfunction (callback, IntPtr.Zero);

I tried changing the data parameter from a byte array to an IntPtr, but
that seems to make no difference. What am I doing wrong? All other
functions (without a callback function parameter) work perfect.
 
N

Nicholas Paldino [.NET/C# MVP]

Jef,

What is the C code doing with the callback data and how is it calling
the callback?
 
J

Jef Driesen

Nicholas said:
What is the C code doing with the callback data and how is it calling
the callback?

The C function is downloading data from an external device. This data is
stored internally in a C style array (stored on the stack or malloc'ed,
depending on the implementation) and processed to extract chunks of
data. Those chunks are passed to the caller by means of the callback
function.

typedef void (*callback_t) (const unsigned char *data, unsigned int
size, void *userdata);

void
myfunction (callback_t callback, void *userdata)
{
unsigned char buffer[SIZE];

// Read some data into the buffer here.

unsigned int offset = 0;
while (offset < sizeof (buffer)) {
unsigned int size = ...;

if (callback) callback (buffer + offset, size, userdata);

offset += size;
}
}

Inside the callback function, the application can process the downloaded
data, store it somewhere, etc. But it is not allowed to modify the data,
hence the usage of const. And of course the buffer remains only valid
during the lifetime of the callback function. If the app needs the data
longer, it needs to copy it.

Everything works great when the DLL is used in a C application, but not
in my C# application.
 
J

Jef Driesen

Jef said:
Nicholas said:
What is the C code doing with the callback data and how is it calling
the callback?

The C function is downloading data from an external device. This data is
stored internally in a C style array (stored on the stack or malloc'ed,
depending on the implementation) and processed to extract chunks of
data. Those chunks are passed to the caller by means of the callback
function.

typedef void (*callback_t) (const unsigned char *data, unsigned int
size, void *userdata);

void
myfunction (callback_t callback, void *userdata)
{
unsigned char buffer[SIZE];

// Read some data into the buffer here.

unsigned int offset = 0;
while (offset < sizeof (buffer)) {
unsigned int size = ...;

if (callback) callback (buffer + offset, size, userdata);

offset += size;
}
}

Inside the callback function, the application can process the downloaded
data, store it somewhere, etc. But it is not allowed to modify the data,
hence the usage of const. And of course the buffer remains only valid
during the lifetime of the callback function. If the app needs the data
longer, it needs to copy it.

Everything works great when the DLL is used in a C application, but not
in my C# application.

I'm not really sure that the data parameter is the problem, because if I
remove that one, and reduce the DLL function to this simple example:

typedef void (*callback_t) (unsigned int size, void *userdata);

void
myfunction (callback_t callback, void *userdata)
{
for (unsigned int i = 0; i < 100; ++i) {
printf ("iteration %u\n", i);

if (callback) callback (i, userdata);
}
}

And create a minimal console project with this code:

delegate void callback_t (UInt32 size, IntPtr userdata);

[DllImport("mydll.dll")]
static extern void myfunction (callback_t callback, IntPtr userdata);

static void test_callback (UInt32 size, IntPtr userdata)
{
Console.WriteLine (size);
}

static void Main(string[] args)
{
callback_t mycallback = new callback_t (test_callback);
myfunction (mycallback, IntPtr.Zero);
}

It runs fine for the first few iterations, but than something goes
wrong. I get this output:

iteration 0
0
iteration 1
1
iteration 2
2
iteration 3
3
iteration 4
0
iteration 1
1697911994

Unhandled Exception: System.AccessViolationException: Attempted to read
or write protected memory. This is often an indication that other memory
is corrupt.
at test.Class1.myfunction(callback_t callback, IntPtr userdata)
at test.Class1.Main(String[] args)
 
J

Jef Driesen

Jef said:
I have a C DLL that I want to use from a C# project. The C header file
contains these declarations:

typedef void (*callback_t) (const unsigned char *data, unsigned int
size, void *userdata);
void myfunction (callback_t callback, void *userdata);

How do I translate this to C#?

I tried with:

delegate void callback_t (Byte[] data, UInt32 size, IntPtr userdata);
[DllImport("mydll.dll")]
static extern void myfunction (callback_t callback, IntPtr userdata);

When calling with myfunction (null, IntPtr.Zero) everything works as
expected. But once I start passing a callback function, the application
crashes with "Unhandled Exception: System.AccessViolationException:
Attempted to read or write protected memory. This is often an indication
that other memory is corrupt."

void test (Byte[] data, UInt32 size, IntPtr userdata)
{
// Nothing here
}

callback_t callback = new callback_t (test);
myfunction (callback, IntPtr.Zero);

I tried changing the data parameter from a byte array to an IntPtr, but
that seems to make no difference. What am I doing wrong? All other
functions (without a callback function parameter) work perfect.

I think C# is somehow messing up the stack of my C DLL. If I export this
very simple function in my DLL (declared as extern "C" to avoid name
mangling):

typedef void (*callback_t) (const unsigned char *data, unsigned int
size, void *userdata);

void
myfunction (callback_t callback, void *userdata)
{
unsigned char data[] = {'a', 'b', 'c', 0x00};
printf ("pointer=%p\n", data);
for (unsigned int i = 0; i < 5; ++i) {
printf ("iteration %u\n", i);
if (callback)
callback (NULL, sizeof (data), userdata);
printf ("pointer=%p\n", data);
}
}

And in C#, I use this code:

delegate void callback_t (IntPtr data, UInt32 size, IntPtr userdata);
[DllImport("mydll.dll")]
static extern void myfunction (callback_t callback, IntPtr userdata);

static void test (IntPtr data, UInt32 size, IntPtr userdata)
{
// Nothing here
}

Now, if I call the DLL functon in my main function with the following
arguments:

myfunction1 (new callback_t (test), IntPtr.Zero);

I get this output:

pointer=0012F5F0
iteration 0
pointer=0012F5FC
iteration 1
pointer=0012F608
iteration 2
pointer=0012F614
iteration 3
pointer=0012F620
iteration 4
pointer=0012F62C

As you can see, the "data" pointer is increased by 12 bytes after each
invocation of the callback function, even if this pointer was never
passed to the callback function at all. If I pass "null" for the
callback function, the pointer remains the same. If I do the same
experiment in a C project, the pointer remains the same, just as it
should be. What am I doing wrong?
 
J

Jef Driesen

Jef said:
Jef said:
I have a C DLL that I want to use from a C# project. The C header file
contains these declarations:

typedef void (*callback_t) (const unsigned char *data, unsigned int
size, void *userdata);
void myfunction (callback_t callback, void *userdata);

How do I translate this to C#?

I tried with:

delegate void callback_t (Byte[] data, UInt32 size, IntPtr userdata);
[DllImport("mydll.dll")]
static extern void myfunction (callback_t callback, IntPtr userdata);

When calling with myfunction (null, IntPtr.Zero) everything works as
expected. But once I start passing a callback function, the application
crashes with "Unhandled Exception: System.AccessViolationException:
Attempted to read or write protected memory. This is often an indication
that other memory is corrupt."

void test (Byte[] data, UInt32 size, IntPtr userdata)
{
// Nothing here
}

callback_t callback = new callback_t (test);
myfunction (callback, IntPtr.Zero);

I tried changing the data parameter from a byte array to an IntPtr, but
that seems to make no difference. What am I doing wrong? All other
functions (without a callback function parameter) work perfect.

I think C# is somehow messing up the stack of my C DLL. If I export this
very simple function in my DLL (declared as extern "C" to avoid name
mangling):

typedef void (*callback_t) (const unsigned char *data, unsigned int
size, void *userdata);

void
myfunction (callback_t callback, void *userdata)
{
unsigned char data[] = {'a', 'b', 'c', 0x00};
printf ("pointer=%p\n", data);
for (unsigned int i = 0; i < 5; ++i) {
printf ("iteration %u\n", i);
if (callback)
callback (NULL, sizeof (data), userdata);
printf ("pointer=%p\n", data);
}
}

And in C#, I use this code:

delegate void callback_t (IntPtr data, UInt32 size, IntPtr userdata);
[DllImport("mydll.dll")]
static extern void myfunction (callback_t callback, IntPtr userdata);

static void test (IntPtr data, UInt32 size, IntPtr userdata)
{
// Nothing here
}

Now, if I call the DLL functon in my main function with the following
arguments:

myfunction1 (new callback_t (test), IntPtr.Zero);

I get this output:

pointer=0012F5F0
iteration 0
pointer=0012F5FC
iteration 1
pointer=0012F608
iteration 2
pointer=0012F614
iteration 3
pointer=0012F620
iteration 4
pointer=0012F62C

As you can see, the "data" pointer is increased by 12 bytes after each
invocation of the callback function, even if this pointer was never
passed to the callback function at all. If I pass "null" for the
callback function, the pointer remains the same. If I do the same
experiment in a C project, the pointer remains the same, just as it
should be. What am I doing wrong?

The problem was caused by the difference in calling convention between
the C DLL (cdecl) and the C# callback function (stdcall). I was able to
fix that by changing the calling convention of the callback function to
cdecl (.NET 2.x or higher only):

[UnmanagedFunctionPointer (CallingConvention.Cdecl)]
public delegate void callback_t (IntPtr data, UInt32 size, IntPtr userdata);
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Top