Click to See Complete Forum and Search --> : [RESOLVED] Interop of native C callback functions with void* parameter


hreba
November 11th, 2008, 08:45 AM
Resuming an older topic (accessing the native library GSL) which I never managed to solve, I wrote a primitive example dll (with MingW/gcc on Windows XP) just to demonstrate the problem.

GSL (Gnu Scientific Library) contains a lot of functions (e.g.: integration routine) which require another (callback) function as argument with the prototype

double f (double x, void *params).

My library function example is

double average (double (*f) (double x, void *params),
void *par, double a, double b, int n)
{
int i;
double sum, x, dx;

sum = 0; x = a; dx = (b-a) / (n-1);
for (i=0; i<n; i++) { sum += f(x,par); x += dx; }
return sum / n;
}

I created a native dll from that and call it from a C test program which defines the actual callback function:

double quadratic (double x, void *par)
{ double *p;
p = par;
return (*p * x + *(p+1)) * x + *(p+2);
}

and calls it with

struct Par3 params = {1.0, 0.0, -5.0};

printf("Average in [0,2] (10 points): %f\n",
average (&quadratic, &params, 0.0, 2.0, 10));

and everything works fine.

After several tentatives to call that from .NET/CSharp, which didn't work, I came up to the following, which doesn't work either, but which is the most complete one. Delegate and dllimport are

[StructLayout (LayoutKind.Sequential)]
class Parameters { public double a, b, c; }

public delegate double Func (double x, IntPtr p);

[DllImport ("generic.dll")] public static extern
double average (Func f, IntPtr p, double a, double b, int n);

The actual callback function is
public static double Quad (double x, IntPtr par)
{
GCHandle gch;
Parameters p;

gch = (GCHandle) par;
p = (Parameters)gch.Target;
return (p.a*x + p.b)*x + p.c;
}

and eveything is called like

par = new Parameters();
par.a = 1.0; par.b = 0.0; par.c = -5.0;
gch = GCHandle.Alloc (par, GCHandleType.Pinned);
func = new Func (Quad);
avg = average (func, (IntPtr)gch, 0.0, 2.0, 10);

which finally provokes the error message

FatalExecutionEngineError was detected
Message: The runtime has encountered a fatal error. The address of the error was at 0x79eb059e, on thread 0x56c. The error code is 0xc0000005. This error may be a bug in the CLR or in the unsafe or non-verifiable portions of user code. Common sources of this bug include user marshaling errors for COM-interop or PInvoke, which may corrupt the stack.

The complete source code with makefile and everything is attached.
What is wrong?

ahoodin
November 11th, 2008, 11:42 AM
May I ask why you are not using delegates?

Function pointers doubtless would not be "safe".:confused:

Mutant_Fruit
November 11th, 2008, 12:15 PM
[StructLayout (LayoutKind.Sequential)]
class Parameters { public double a, b, c; }


That should be


[StructLayout (LayoutKind.Sequential)]
struct Parameters { public double a, b, c; }



double quadratic (double x, void *par)


Assuming the "void* par" part refers to a Parameters type, you can safely change this to:


double quadratic (double x, ref Parameters par)

// called like:
Parameters p = new Parameters { a = 1, b = 2, c = 3};
quadratic (5, ref p);



double average (Func f, IntPtr p, double a, double b, int n);


This can be rewritten to use ref Parameters instead of IntPtr aswell.

That should help.

TheCPUWizard
November 11th, 2008, 12:46 PM
The following advice applies to specific situations, notably:

1) The person has a C++ bacckground...
2) The person is NOT an expert on "internals"...
3) Performance is not a 100% driving factor...


a) In native C++, write a COM compliant wrapper (if the target code is not already)

b) Use the COM interop functionallity to create a managed wrapper.

c) USe the managed wrapper.

Even if 1-3 do not "fully" apply, this approach (inconjunction with a tool like Reflector] is often the best way to "derive" the necessary mapping.

hreba
November 12th, 2008, 11:19 AM
May I ask why you are not using delegates?

Function pointers doubtless would not be "safe".:confused:

As an act of despair. Changed it back. See my other response below.

hreba
November 12th, 2008, 11:47 AM
Did as suggested. The complete C# program is now:

using System;
using System.Runtime.InteropServices;

namespace TestSharp
{
class MainClass
{
[StructLayout (LayoutKind.Sequential)]
struct Parameters { public double a, b, c; }

delegate double Func (double x, ref Parameters p);

[DllImport ("generic.dll")] static extern
double average (Func f, ref Parameters p, double a, double b, int n);

static double Quadratic (double x, ref Parameters p)
{ return (p.a*x + p.b)*x + p.c;}

public static void Main(string[] args)
{
Parameters par;
Func func;
double avg;

par = new Parameters(); { par.a = 1; par.b = 0; par.c=-5;}
func = new Func (Quadratic);
avg = average (func, ref par, 0.0, 2.0, 10);
Console.WriteLine("Average in [0,2] (10 points): " + avg.ToString() +"\r\n");
Console.WriteLine("RETURN to terminate ...");
Console.ReadLine();
}
}
}


The error message is now:
Exception System.ExecutionEngineException was thrown in debuggee:
<null reference>

Main() - c:\Documents and Settings\Frank\My Documents\Exercises\C\GSL\Generic\TestSharp\Main.cs:28,5

Line 28 is the one which goes avg = average (func, ref par, ...,
but neither func nor par is null when looking at it with the debugger.
The result is independent of 'Parameters' being a class or a struct.

hreba
November 12th, 2008, 11:55 AM
Unfortunately, prerequisite (1) cannot be supposed
(as well as the one about knowledge of COM) :-(

ahoodin
November 13th, 2008, 12:27 AM
Did as suggested. The complete C# program is now:

I think I see now.

http://msdn.microsoft.com/en-us/library/aa288468.aspx
I dug just far enough to find examples of delegates / function pointers and PInvoke that cover the needed syntax. You can find the syntax down near the bottom of the article.

HTH,

barrow_matt
November 13th, 2008, 02:21 AM
I think I see now.

http://msdn.microsoft.com/en-us/library/aa288468.aspx
I dug just far enough to find examples of delegates / function pointers and PInvoke that cover the needed syntax. You can find the syntax down near the bottom of the article.

HTH,

Thanks for this link, I used this to fix a similar problem I was experiencing

http://www.codeguru.com/forum/showthread.php?t=465078

Thanks,

Matt

ahoodin
November 13th, 2008, 09:47 AM
To Quote from the MSDN:
Registering Callback Methods

To register a managed callback that calls an unmanaged function, declare a delegate with the same argument list and pass an instance of it via PInvoke. On the unmanaged side it will appear as a function pointer. For more information about PInvoke and callback, see A Closer Look at Platform Invoke.

For example, consider the following unmanaged function, MyFunction, which requires callback as one of the arguments:
Copy Code

typedef void (__stdcall *PFN_MYCALLBACK)();
int __stdcall MyFunction(PFN_ MYCALLBACK callback);

To call MyFunction from managed code, declare the delegate, attach DllImport to the function declaration, and optionally marshal any parameters or the return value:
Copy Code

public delegate void MyCallback();
[DllImport("MYDLL.DLL")]
public static extern void MyFunction(MyCallback callback);

Also, make sure the lifetime of the delegate instance covers the lifetime of the unmanaged code; otherwise, the delegate will not be available after it is garbage-collected.

HTH,:D:D:D:wave:

hreba
November 18th, 2008, 07:53 AM
I think I see now.

http://msdn.microsoft.com/en-us/library/aa288468.aspx
I dug just far enough to find examples of delegates / function pointers and PInvoke that cover the needed syntax. You can find the syntax down near the bottom of the article.

HTH,

I cannot help myself, I think I have done everything as recommended there. Reinstalled MinGW and MSys and compiled everything anew. Nevertheless, I get the null reference error message. Attached eveything (native and C#).

hreba
November 21st, 2008, 11:38 AM
Resolved finally. The crucial hints came from ahoodin and from the Mingw-users list with a reference to
www.nabble.com/Using-a-mingw-DLL-from-C--td18586582.html:

Either the callback prototype in the native dll has to be specified as stdcall, or the calling convention of the C# delegate must be specified as cdecl:

[UnmanagedFunctionPointer (CallingConvention.Cdecl)]
delegate double Func (double x, ref Parameters p);

Thank you all!