interop: calling foo(void **) from C#

C

cees

Hi,

I need to call an external C-function that accept an array of void
pointers (void **). Each item can either be a single float,int,char or
a C-array of these types. Is that possible and if so how to do it most
elegantly?
I found a lot of samples and stuff in MSDN that got me some ideas but
somehow I am not able to progress to the exact situation I need, while
it seems simple.
Below you find a C#-sample, with in comment the working Ansi-C version
of the functionality I need.

Thanks in advance,
Cees


using System.Runtime.InteropServices; // DllImport
public class Sample {

[DllImport("containingFoo.dll")]
unsafe public static extern void foo(void **data);

/* C code sample
void callFromC()
{
extern void foo(void **data);
float f[4] = {1,2,3,4};
int i = 5;
void* data[2] = { &f, &i};

foo(data);
}
*/

unsafe static void callFromCSharp() {
float[] f = {1,2,3,4};
int i = 5;

void** data;
// in C: void* data[2] = { &f, &i};
// WHAT TO DO IN C#?

foo(data);
}
};
 
W

Willy Denoyette [MVP]

Hi,

I need to call an external C-function that accept an array of void
pointers (void **). Each item can either be a single float,int,char or
a C-array of these types. Is that possible and if so how to do it most
elegantly?
I found a lot of samples and stuff in MSDN that got me some ideas but
somehow I am not able to progress to the exact situation I need, while
it seems simple.
Below you find a C#-sample, with in comment the working Ansi-C version
of the functionality I need.

Thanks in advance,
Cees


using System.Runtime.InteropServices; // DllImport
public class Sample {

[DllImport("containingFoo.dll")]
unsafe public static extern void foo(void **data);

/* C code sample
void callFromC()
{
extern void foo(void **data);
float f[4] = {1,2,3,4};
int i = 5;
void* data[2] = { &f, &i};

foo(data);
}
*/

unsafe static void callFromCSharp() {
float[] f = {1,2,3,4};
int i = 5;

void** data;
// in C: void* data[2] = { &f, &i};
// WHAT TO DO IN C#?

foo(data);
}
};

Pass the array as an IntPtr[], IntPtr are by default marshaled as void*. No
need for unsafe.

[DllImport("containingFoo.dll")]
public static extern void foo( IntPtr[] ptr);
...

// some floats
float[] fpa = {7.2F, 2.3F, 3.3F, 4.5F, 6.5F};
// allocate unmanaged for float[] fpa and int (length of array)
IntPtr fptr = Marshal.AllocHGlobal(fpa.Length *
Marshal.SizeOf(typeof(float)));
IntPtr iptr = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(int)));
// set length of array
Marshal.WriteInt32(iptr, fpa.Length);
// copy the array
Marshal.Copy(fpa, 0, fptr, fpa.Length);
// strore both pointers in IntPtr[]
IntPtr[] pptr = {fptr, iptr};
// call foo passing the IntPtr[] to C
foo(pptr);


//C/C++
// note that stdcall is the default calling convention when using
PInvoke!!!!

void __stdcall foo(void** data)
{
float * fa = (float*)*data; // first element points to float array
int *ip = (int*)data + 1; // pointer to next element in void array
int *pp = (int*)*ip; // get pointer to int
for (int i = 0; i < *pp ; i++)
{
printf("\t: %f\n", *fa++);
}
}


Willy.
 
C

cees

Thanks, that got me going.
I however forgot to mention that the ptr's in data array can serve for
both input and output. I can not find an elegant way for putting stuff
back to managed data types. If I choose for unsafe I can do:


float *output= stackalloc float[25];
IntPtr[] data = {fptr, (IntPtr)output};
foo(data);
// copy ouput to float Array if needed

while having to compile for unsafe then, I do see the difference as
only cosmetic since passing raw memory from AllocHGlobal to my C
function can cause the same type of havoc anyway.
 
W

Willy Denoyette [MVP]

Thanks, that got me going.
I however forgot to mention that the ptr's in data array can serve for
both input and output. I can not find an elegant way for putting stuff
back to managed data types. If I choose for unsafe I can do:


float *output= stackalloc float[25];
IntPtr[] data = {fptr, (IntPtr)output};
foo(data);
// copy ouput to float Array if needed

while having to compile for unsafe then, I do see the difference as
only cosmetic since passing raw memory from AllocHGlobal to my C
function can cause the same type of havoc anyway.

You can change/overwrite the array passed from the caller as long as you
don't overwrite the buffer passed in, using the same code ...

// Pass an "empty" array... (not really, arrays are never empty)
float[] fpa = new float[10];
IntPtr fptr = Marshal.AllocHGlobal(fpa.Length *
Marshal.SizeOf(typeof(float)));
IntPtr iptr = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(int)));
Marshal.WriteInt32(iptr, fpa.Length);
Marshal.Copy(fpa, 0, fptr, fpa.Length);
IntPtr[] pptr = {fptr, iptr};
try
{
PassArrayOfArray(pptr); // call C function that changes/fills the
array contents.
ShowReturnedData(fptr, iptr); // retrieve modified contents.
}
finally
{
Marshal.FreeHGlobal(fptr);
Marshal.FreeHGlobal(iptr);
}
}

static void ShowReturnedData(IntPtr fptr, IntPtr iptr)
{
int arrSize = Marshal.ReadInt32(iptr);
float[] fpa = new float[arrSize];
unsafe
{
float* vp = (float*)fptr.ToPointer();
for (int n = 0; n < Marshal.ReadInt32(iptr) ; n++)
{
fpa[n] = (float)*vp + n;
}
}
foreach(float f in fpa)
{
Console.WriteLine(f);
}
}


Note that in v2 of the framework you can copy from unmanaged to managed
arrays, no unsafe block needed anymore. Don't forget that unsafe code blocks
generate unverifiable code!!

// v2.0 ...
static void ShowReturnedData(IntPtr fptr, IntPtr iptr)
{
int arrSize = Marshal.ReadInt32(iptr);
float[] fpa = new float[arrSize];
Marshal.Copy(fptr, fpa ,0, fpa.Length);
foreach(float f in fpa)
{
Console.WriteLine(f);
}
}

Willy.
 

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