Can someone please help me call this C function from C#?

B

Berhack

I am not too familiar with C# interop so please help me out. I need
to call the following C function (in a DLL):

// this creates an array of strings
// LPTSTR is just char *
void C_Func(LPTSTR **pszStrings)
{
(*pszStrings) = reinterpret_cast<LPTSTR *>(malloc(2 *
sizeof(LPTSTR)));
(*pszStrings)[0] = _T("A test");
(*pszStrings)[1] = _T("Anorther String");
}

This is what I have so far in C# (please be gentle, i'm a newbie at
it):

[DllImport(...)]
public static extern void recvReports(out IntPtr ptr);

// later on...
IntPtr ptr;
IntPtr i = C_Func(out ptr);
int offset = 0;
for (;;)
{
IntPtr p = Marshal.ReadIntPtr(ptr, offset);
if (p == IntPtr.Zero) break;
Console.Writelin("Found String: " +
Marshal.PtrToStringAnsi(p));
Marshal.FreeHGlobal(p); // trying to free the malloc here...
offset += IntPtr.Size;
}

Can someone correct it?? Extra marks for explaining why my code is
wrong. Thanks a lot. :)
 
W

Willy Denoyette [MVP]

Berhack said:
I am not too familiar with C# interop so please help me out. I need
to call the following C function (in a DLL):

// this creates an array of strings
// LPTSTR is just char *
void C_Func(LPTSTR **pszStrings)
{
(*pszStrings) = reinterpret_cast<LPTSTR *>(malloc(2 *
sizeof(LPTSTR)));
(*pszStrings)[0] = _T("A test");
(*pszStrings)[1] = _T("Anorther String");
}

This is what I have so far in C# (please be gentle, i'm a newbie at
it):

[DllImport(...)]
public static extern void recvReports(out IntPtr ptr);

// later on...
IntPtr ptr;
IntPtr i = C_Func(out ptr);
int offset = 0;
for (;;)
{
IntPtr p = Marshal.ReadIntPtr(ptr, offset);
if (p == IntPtr.Zero) break;
Console.Writelin("Found String: " +
Marshal.PtrToStringAnsi(p));
Marshal.FreeHGlobal(p); // trying to free the malloc here...
offset += IntPtr.Size;
}

Can someone correct it?? Extra marks for explaining why my code is
wrong. Thanks a lot. :)

This is not gonna work, you'll have to add a null pointer as last element
(or better, let the callee return the number of pointers in the array).
Another point is that you can't release memory allocated in C using malloc
other than calling the CRT free() library function, the best way to do this
is by exposing a function that the client has to call when done, that
function needs to call free()).
You have to use Marshal.PtrToStringAuto with LPTSTR strings.

Following should work, given the last element of the pointer array is null
!!!

IntPtr sa;
ArrayList temp = new ArrayList();
C_Func((out sa);
int i = 0;
for (;;)
{
IntPtr p = new IntPtr(sa.ToInt32() + Marshal.SizeOf(typeof(IntPtr))
* i++);
if ( Marshal.ReadIntPtr(p)== IntPtr.Zero)
break;
temp.Add(Marshal.ReadIntPtr(p));
}
foreach(IntPtr ip in temp)
{
string s1 = Marshal.PtrToStringAuto(ip);
if (s1 != null)
Console.WriteLine(s1);
}
// free the unmanaged memory
C_FuncReleasePointer(sa);

Willy.
 
B

Berhack

This is not gonna work, you'll have to add a null pointer as last element
(or better, let the callee return the number of pointers in the array).
Another point is that you can't release memory allocated in C using malloc
other than calling the CRT free() library function, the best way to do this
is by exposing a function that the client has to call when done, that
function needs to call free()).
You have to use Marshal.PtrToStringAuto with LPTSTR strings.

Following should work, given the last element of the pointer array is null
!!!

IntPtr sa;
ArrayList temp = new ArrayList();
C_Func((out sa);
int i = 0;
for (;;)
{
IntPtr p = new IntPtr(sa.ToInt32() + Marshal.SizeOf(typeof(IntPtr))
* i++);
if ( Marshal.ReadIntPtr(p)== IntPtr.Zero)
break;
temp.Add(Marshal.ReadIntPtr(p));
}
foreach(IntPtr ip in temp)
{
string s1 = Marshal.PtrToStringAuto(ip);
if (s1 != null)
Console.WriteLine(s1);
}
// free the unmanaged memory
C_FuncReleasePointer(sa);

Willy.

Hello Willy,

Thanks for your help. Unfortunately, the code does not work. I get
one string returned, with garbage. Do I perhaps need to include some
special marshalling attributes when declaring C_Func in C#? Here is
my C_Func C again:

// creates an array of strings
// how to call in C#???
__declspec(dllexport) void C_Func(LPTSTR **pszStrings)
{
(*pszStrings) = reinterpret_cast<LPTSTR *>(malloc(3 *
sizeof(LPTSTR)));
(*pszStrings)[0] = _T("String 1");
(*pszStrings)[1] = _T("String 2");
(*pszStrings)[1] = NULL;
}

Thanks for any help again!

Roy
 
W

Willy Denoyette [MVP]

Its normal you only get one string...

(*pszStrings)[1] = _T("String 2");
(*pszStrings)[1] = NULL;

should be ...
(*pszStrings)[2] = NULL;

but, I'm not sure why you got passed the NULL pointer though, could you try
this:
memset(*pszStrings, 0, 3 * sizeof(LPTSTR)); // clear the allocated memory
(3 is the size of the array)

and remove this..

(*pszStrings)[2] = NULL;

Willy.

Berhack said:
This is not gonna work, you'll have to add a null pointer as last element
(or better, let the callee return the number of pointers in the array).
Another point is that you can't release memory allocated in C using malloc
other than calling the CRT free() library function, the best way to do
this
is by exposing a function that the client has to call when done, that
function needs to call free()).
You have to use Marshal.PtrToStringAuto with LPTSTR strings.

Following should work, given the last element of the pointer array is null
!!!

IntPtr sa;
ArrayList temp = new ArrayList();
C_Func((out sa);
int i = 0;
for (;;)
{
IntPtr p = new IntPtr(sa.ToInt32() +
Marshal.SizeOf(typeof(IntPtr))
* i++);
if ( Marshal.ReadIntPtr(p)== IntPtr.Zero)
break;
temp.Add(Marshal.ReadIntPtr(p));
}
foreach(IntPtr ip in temp)
{
string s1 = Marshal.PtrToStringAuto(ip);
if (s1 != null)
Console.WriteLine(s1);
}
// free the unmanaged memory
C_FuncReleasePointer(sa);

Willy.

Hello Willy,

Thanks for your help. Unfortunately, the code does not work. I get
one string returned, with garbage. Do I perhaps need to include some
special marshalling attributes when declaring C_Func in C#? Here is
my C_Func C again:

// creates an array of strings
// how to call in C#???
__declspec(dllexport) void C_Func(LPTSTR **pszStrings)
{
(*pszStrings) = reinterpret_cast<LPTSTR *>(malloc(3 *
sizeof(LPTSTR)));
(*pszStrings)[0] = _T("String 1");
(*pszStrings)[1] = _T("String 2");
(*pszStrings)[1] = NULL;
}

Thanks for any help again!

Roy
 
B

Berhack

Hi Willy,

Yeah missed that bug, but since then I've corrected it and added the
zeroing:

DLLEXPORT void C_Func(LPTSTR **pszStrings)
{
(*pszStrings) = reinterpret_cast<LPTSTR *>(malloc(3 *
sizeof(LPTSTR)));
memset(*pszStrings, 0, 3 * sizeof(LPTSTR));
(*pszStrings)[0] = _T("String 1");
(*pszStrings)[1] = _T("String 2");
(*pszStrings)[2] = NULL;
}

Unfortunately, still doesn't work with the code you gave me. The loop
DOES break at the correct number of strings (2) but each string is
still garbage.

I was playing around and I noticed that this seems to work:

// in C#
public static extern IntPtr C_Func(out IntPtr[] sa);

When I called it, sa had one element and it was the SAME value (a
memory address) of **pszStrings (I debugged the C function to find
this), ie it contained the address of the first character of the first
string ('S'). But alas,

string s1 = Marshal.PtrToStringAuto(sa[0]);

gave me garbage :-(
 
W

Willy Denoyette [MVP]

Are you just looking at this in the debugger, or are you getting garbage
when displaying the string using Console.WriteLine()?

Willy.

Berhack said:
Hi Willy,

Yeah missed that bug, but since then I've corrected it and added the
zeroing:

DLLEXPORT void C_Func(LPTSTR **pszStrings)
{
(*pszStrings) = reinterpret_cast<LPTSTR *>(malloc(3 *
sizeof(LPTSTR)));
memset(*pszStrings, 0, 3 * sizeof(LPTSTR));
(*pszStrings)[0] = _T("String 1");
(*pszStrings)[1] = _T("String 2");
(*pszStrings)[2] = NULL;
}

Unfortunately, still doesn't work with the code you gave me. The loop
DOES break at the correct number of strings (2) but each string is
still garbage.

I was playing around and I noticed that this seems to work:

// in C#
public static extern IntPtr C_Func(out IntPtr[] sa);

When I called it, sa had one element and it was the SAME value (a
memory address) of **pszStrings (I debugged the C function to find
this), ie it contained the address of the first character of the first
string ('S'). But alas,

string s1 = Marshal.PtrToStringAuto(sa[0]);

gave me garbage :-(



Its normal you only get one string...

(*pszStrings)[1] = _T("String 2");
(*pszStrings)[1] = NULL;

should be ...
(*pszStrings)[2] = NULL;

but, I'm not sure why you got passed the NULL pointer though, could you
try
this:
memset(*pszStrings, 0, 3 * sizeof(LPTSTR)); // clear the allocated memory
(3 is the size of the array)

and remove this..

(*pszStrings)[2] = NULL;

Willy.
 
W

Willy Denoyette [MVP]

Willy Denoyette said:
Are you just looking at this in the debugger, or are you getting garbage
when displaying the string using Console.WriteLine()?

Willy.

What I mean is that, depending on the platform you are running this on,
Marshal.PtrToStringAuto will treat the string to marshal as a Unicode or
Ansi string. On NT based system, the marshaler considers the string as
Unicode, that means your C++ program must be a Unicode build. However, if
it's an ANSI build, you should use Marshal.PtrToStringAnsi.

Willy.
 
B

Berhack

Many thanks Willy, this worked once i switched to using
PtrToStringAnsi!!

I am now curious as to what the code is doing exactly. Can you
correct me in my following assumptions:

[Code that you gave me again]

IntPtr sa;
ArrayList temp = new ArrayList();
C_Func(out sa);
int i = 0;

for (;;)
{

(1) IntPtr ptr = new IntPtr(sa.ToInt32() +
Marshal.SizeOf(typeof(IntPtr)) * i++);

(2) if (Marshal.ReadIntPtr(ptr)== IntPtr.Zero) break;

(3) temp.Add(Marshal.ReadIntPtr(ptr));

}

foreach(IntPtr ip in temp)
{
string s1 = Marshal.PtrToStringAnsi(ip);
if (s1 != null)
Console.WriteLine(s1);
}


1. As my C function took in the parameter LPTSTR **, when calling it
in C# like so:

C_Func(out intPtr)

What exactly does intPtr contain? If I were to call this from C:

LPTSTR *s;
C_Func(&s);

Now it's clear to me s is a pointer-to-LPTSTR (the first one in an
array presumably). Is intPtr the same way?

2. In (1), I am guessing that 4 * i + base address returns the
address of the first character of the next string?

3. It LOOKS to me like ReadIntPtr treats the contents of ptr as an
IntPtr. So, each location will contain the address of the first
character of the next string until the last entry, which just contains
NULL?

4. Instead of adding to temp ArrayList, is there a reason why I
cannot just create my string after calculating ptr:

Marshal.PtrToStringAnsi(Marshal.ReadIntPtr(ptr));

Strangely enough, I ask this question because I noticed that in the
foreach loop the each ip is different value than what was added in
(3).

Anyay that's all. I cannot thank you enough for helping me get on
with my work.....

Roy
 
W

Willy Denoyette [MVP]

Inline

Willy.

Berhack said:
Many thanks Willy, this worked once i switched to using
PtrToStringAnsi!!

I am now curious as to what the code is doing exactly. Can you
correct me in my following assumptions:

[Code that you gave me again]

IntPtr sa;
ArrayList temp = new ArrayList();
C_Func(out sa);
int i = 0;

for (;;)
{

(1) IntPtr ptr = new IntPtr(sa.ToInt32() +
Marshal.SizeOf(typeof(IntPtr)) * i++);

(2) if (Marshal.ReadIntPtr(ptr)== IntPtr.Zero) break;

(3) temp.Add(Marshal.ReadIntPtr(ptr));

}

foreach(IntPtr ip in temp)
{
string s1 = Marshal.PtrToStringAnsi(ip);
if (s1 != null)
Console.WriteLine(s1);
}


1. As my C function took in the parameter LPTSTR **, when calling it
in C# like so:

C_Func(out intPtr)

What exactly does intPtr contain? If I were to call this from C:
IntPtr wraps a machine specific integer ( a 32 bit value on 32 OS, 64 bit
value on 64 bit OS).
LPTSTR *s;
C_Func(&s);

Now it's clear to me s is a pointer-to-LPTSTR (the first one in an
array presumably). Is intPtr the same way?

LPTSTR has only a meaning for the C++ compiler, it's of no value at all in
other languages.
In fact LPTSTR **, is nothing else than a pointer to a pointer to something
that is only known by the C++ compiler, so what is retuned by the C function
is just the address of a pointer to something, but we know more, we know
that the address returned points to an array of pointers pointing to zero
terminated C string. So what we have in sa when the function returns, is the
address of the first element of the array of pointers.

2. In (1), I am guessing that 4 * i + base address returns the
address of the first character of the next string?
Yep, here I increment the pointer such that it points to the next element of
the array.
Note that I'm using the length of the IntPtr (4 or 8) to make it portable
between platforms.
3. It LOOKS to me like ReadIntPtr treats the contents of ptr as an
IntPtr. So, each location will contain the address of the first
character of the next string until the last entry, which just contains
NULL?

ReadIntPtr reads the pointer from the array element; and yes WE know that
it's the address of the first char (and what you know is that the C program
was compiled for Ansi ;-) so it's a C string (zero terminated)).
4. Instead of adding to temp ArrayList, is there a reason why I
cannot just create my string after calculating ptr:

Marshal.PtrToStringAnsi(Marshal.ReadIntPtr(ptr));

Sure, but I stored the pointer in an arraylis, so I could iterate over it in
a foreach block.
Strangely enough, I ask this question because I noticed that in the
foreach loop the each ip is different value than what was added in
(3).

Not sure I follow you here, what's stored in the arraylist is the IntPtr
representing the address of each char array (the address of the first char
of each char array).
 

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