Marshaling issue with char** while accessing a library function - c#

I'm porting an existing library/DLL writen in C++/VisualStudio to codeblocks/GCC. The DLL in Windows has been tested in C#, C, C++, Python, Delphi, Java, VB.NET, LabVIEW, etc and works fine and stable.
However, when porting it to Linux, I'm having issues while testing it from Mono/C#, while it's working fine from FreePascal and Python.
The root of the issue is a function that detects some devices and returns an integer with the number of devices detected, and a list of the paths (array of ASCII strings of chars) where the devices are located, through parameters:
int DetectDevices(char ** DevicePaths);
They way I'm copying the results in the library is:
i=0;
for (vector<string>::iterator it=lstDetected.begin(); it!=lstDetected.end(); ++it)
strcpy(DevicePaths[i++], (*it).c_str());
In C#, I declare the external function using the following code:
[DllImport(LIBRARY_PATH)]
public static extern int DetectDevices([In, Out, MarshalAs(UnmanagedType.LPArray, ArraySubType = UnmanagedType.LPStr)] string[] DevicePaths);
I would like to note, that I'm actually reserving some memory space in C# before calling the function and getting the value returned:
string[] DevicePaths = new string[50];
for (int i=0; i<DevicePaths.Length; i++)
DevicePaths[i] = new string('\0', 255);
This is working fine in Windows/VisualStudio, but not in Linux/Mono.
Replacing LPStr with LPWStr and performing a debug, shows that the characters are supposedly arriving but the equivalent ASCII code received is 0 for all the characters in LPStr and 63 in LPWStr.
I'm thinking that this could be related to an issue related with character encoding, but I might be wrong.
Does anyone have any idea on what could be wrong here?
Help will be much appreciated!

I finally managed to find a solution to the Marshaling problem.
While in Windows (.NET framework) & Visual Studio, returning an C array of strings (array of char array) parameter through the following manner is allowed:
[DllImport(LIBRARY_PATH)]
public static extern int DetectDevices([In, Out, MarshalAs(UnmanagedType.LPArray, ArraySubType = UnmanagedType.LPStr)] string[] DevicePaths);
for some reason this is not working in Linux / Mono and I had to use the following method:
public static extern int DetectDevices(IntPtr[] pDevicePaths);
and then, in the code retrieve each string using the following method:
const int VCOUNT = 50;
const int MAXSTRINGSIZE = 255;
string[] MyValues = new string[VCOUNT];
IntPtr[] ptr = new IntPtr[VCOUNT];
for (int i = 0; i < ptr.Length; i++) ptr[i] = Marshal.AllocCoTaskMem(MAXSTRINGSIZE);
int n = DetectDevices(ptr);
if (n > 0) {
for (int i = 0; i < n; i++) {
StringBuilder sb = new StringBuilder(Marshal.PtrToStringAnsi(ptr[i]));
MyValues[i] = sb.ToString();
}
}
This is a more C/C++ style, which adds complexity but makes sense.
So I believe that either Mono is not fully implemented or there is a bug somewhere.
In case anyone has a better solution, I'll really appreciate it.

Try with LPTStr which will convert the string to the platform’s default string encoding. For Mono this is UTF-8.
UnmanagedType.LPStr => ansi
UnmanagedType.LPWStr => unicode
UnmanagedType.LPTStr => platform default
There are other UnmanagedType that could also help... BStr perhaps...?
If this does not help then consider using Custom Marshaling or Manual Marshaling.
The documentation is pretty good.

Related

Array of StringBuilders not working with P/Invoke

This was marked as a duplicate of Pass writeable StringBuilder array to C++ from C# but that does not at all address the issue of the use of StringBuilder[] and only comments on the incorrect use of wcsncpy and MarshalAs. I am not even using wcsncpy or MarshalAs anywhere in my question.
So, I'm trying to use one of my C++ functions in C# like this:
[DllImport("CPPDLLImport", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Unicode)]
static extern void StringBuilderArrayTest(StringBuilder[] text, int[] textLengths, int numberOfTextItems);
Now, normally this would work fine of course, but I'm don't think I'm getting the correct data in C++ from text.
So, in order to test this I set up a P/Invoke project.
I am calling the method in C# using this:
var stringBuilderArray = new StringBuilder[]
{
new StringBuilder("abc"),
new StringBuilder("def"),
new StringBuilder("ghi")
};
var stringBuilderLengths = new int[3] { 3, 3, 3 };
StringBuilderArrayTest(stringBuilderArray, stringBuilderLengths, 3);
And this is my method in C++ - to test it I'm just printing it to the screen, and to make sure it definitely wasn't printf, I am literally printing it character-by-character just to be sure:
EXPORT void StringBuilderArrayTest(wchar_t** text, int* textLengths, int numberOfTextItems) {
for (int i = 0; i < numberOfTextItems; i++)
for (int j = 0; j < textLengths[i]; j++)
printf("%c", text[i][j]);
}
And, well, the parameter text is definitely not right. It's just giving me "ÇPÿ" three times - definitely not the string "abc", "def" or "ghi".
Is it just not marshaling the StringBuilder array correctly? If so, how can I get this array of strings across?
UPDATE:
OK, I have decided to use a string[] for sending data to my C++ code, however, I now need to do the reverse - sending data from C++ into C#. So, what alternatives are there to using an array of StringBuilder?

Receive char ** from a C++ DLL into C# string[]

Despite all the questions, I can't find a suitable answer for doing this.
My goal is to fill a string[] with the use of a DLL that returns a char**.
DLL Declaration :
extern "C" SHTSDK_EXPORT int GetPeerList(SHTSDK::Camera *camera, int* id, int id_size, char** name, int name_size, int* statut, int statut_size);
My import :
[DllImport(libName)]
static public extern int GetPeerList(IntPtr camera, IntPtr id, int id_size, IntPtr name, int name_size, IntPtr statut, int statut_size);
My use in C# code :
StringBuilder[] name = new StringBuilder[nbPeer];
for (int i = 0; i < nbPeer; i++)
{
name[i] = new StringBuilder(256);
}
//Alloc peer name array
GCHandle nameHandle = GCHandle.Alloc(name, GCHandleType.Pinned);
IntPtr pointeurName = nameHandle.AddrOfPinnedObject();
int notNewConnection = APIServices.GetPeerList(cameraStreaming, pointeurId,
nbPeer, pointeurName, nbPeer, pointeurStatut, nbPeer);
// Now I'm supposed to read string with name[i] but it crashes
What did I miss? I really searched on the other topics, I thought this one could work, but still crashing.
Thanks.
I suggest you developing a tiny C++/CLI bridging layer. The purpose of this C++/CLI bridge is to take the string array returned by the DLL in the form of char** raw pointers, and convert it to a .NET string array, that can be consumed in your C# code as a simple string[].
The C++/CLI version of C# string[] (string array) is array<String^>^, e.g.:
array<String^>^ managedStringArray = gcnew array<String^>(count);
You can use the usual syntax with operator[] (i.e. managedStringArray[index]) to assign each string to the array.
You may write some code like this:
// C++/CLI wrapper around your C++ native DLL
ref class YourDllWrapper
{
public:
// Wrap the call to the function of your native C++ DLL,
// and return the string array using the .NET managed array type
array<String^>^ GetPeerList( /* parameters ... */ )
{
// C++ code that calls your DLL function, and gets
// the string array from the DLL.
// ...
// Build a .NET string array and fill it with
// the strings returned from the native DLL
array<String^>^ result = gcnew array<String^>(count);
for (int i = 0; i < count; i++)
{
result[i] = /* i-th string from the DLL */ ;
}
return result;
}
...
}
You may find this article on CodeProject on C++/CLI arrays an interesting reading as well.
P.S. The strings returned from your native DLL are in the form of char-strings. On the other hand, .NET strings are Unicode UTF-16 strings. So you need to clarify what encoding is used to represent text in your native strings, and convert to UTF-16 for .NET strings.

Passing String from C function to C#

I have the following test function set up in a C project:
__declspec(dllexport) int test(char *str, int strlen){
char* h = "Hello";
int length = 5;
for(int i = 0; i < length; i++){
str[0] = h[0];
}
return strlen;
}
And in my C# project I declare the method as follows:
[DllImport("solver.dll", CharSet = CharSet.Unicode ,CallingConvention = CallingConvention.Cdecl)]
public static extern int test(StringBuilder sol, int len);
And I try to use it in my project like so:
StringBuilder sol = new StringBuilder(15);
int t = test(sol, sol.Capacity);
string str = sol.ToString();
I'd like to pass "Hello" back to the C# code as a test, but when I run the code the StringBuilder stays empty, and even though 15 is passed to the C function as the length, when the C function returns the length it returns a 'random' large number like 125822695. What am I missing?
A number of problems:
You state CharSet.Unicode in the C#, but use ANSI in the unmanaged code.
You only write to the first character.
You ignore the value of strlen passed to the function.
You don't attempt to write a null-terminator.
As for the value returned from the function, that cannot be explained by the code that you present. There is something missing from your question that we need to see in order to explain that.
It is quite common when we see these questions, that the unmanaged code is clearly broken. It's hard enough to write correct p/invokes to correct unmanaged code. Trying to debug the p/invoke and the unmanaged code at the same time is so much harder. Test your unmanaged code in an unmanaged setting first. Only when you are confident it is correct should you move to write your p/invoke.

PInvoke Access Violation with StringBuilder

I am currently testing some PInvoke stuff and wrote a short C function to try some different things out. I successfully managed to pass Ints and return an addition, but I am having some trouble when it comes to strings.
Here is the C function:
__declspec(dllexport) int test(char *str, int slen){
for(int i = 0; i < slen; i++){
str[i] = 'a';
}
return slen;
}
And here is the C# function declaration and usage:
[DllImport("solver.dll", CharSet = CharSet.Ansi ,CallingConvention = CallingConvention.Cdecl)]
public static extern int test(StringBuilder sol, int len);
StringBuilder sol = new StringBuilder(15);
int ret = test(sol, sol.Capacity);
string str = sol.ToString();
I've been researching this for most of the day and I've seen several posts about simply passing a StringBuilder, filling it on the C end and it should be accessible when the function finishes. However I am currently getting an AccessViolation error in the C code as if the Memory hadn't been allocated, but I definitely allocate the Memory with new StringBuilder(15)
The C function definitely works if I allocate a piece of memory in the C code and pass it.
Is there something I am missing?
Sounds like you are missing to NUL-terminate the string buffer.
You may want to update your code like this:
for (int i = 0; i < (slen-1); i++) {
str[i] = 'a';
}
str[slen-1] = '\0'; // Add a NUL-terminator
Note that in this case I'm assuming the buffer length passed by the caller is the total length, including room for the NUL-terminator.
(Other conventions are possible as well, for example assuming the length passed by the caller excludes the NUL-terminator; the important thing is clarity of the interface documentation.)
I'd also like to add that a usual calling convention for those exported functions is __stdcall instead of __cdecl (although that's not correlated to your problem).

How to pass const array of strings back from c dll to c#

I have C# code that calls a C dll. The dll has the following global const array of strings:
const char *PtxEditorColumnHeaders[] = {
"Ptx#",
"Primitive",
"PtxType",
"_END_COLUMNS"
};
All I want to do is grab this text and stuff it into the Column text of a ListView control.
I have found there are several ways to do this, using Pinvoke, strcpy, etc. But, since I'm still learning c# and so far uncorrupted in my ways, what is the best practices way to do this?
Write a C function to return the pointer to the first element of the array, and the number of elements:
const char **GetPtxEditorColumnHeaders(int *count)
{
*count = 4;//or however you want to get hold of this information
return PtxEditorColumnHeaders;
}
And then declare the p/invoke:
[DllImport(#"mydll.dll", CallingConvention=CallingConvention.Cdecl)]
static extern IntPtr GetPtxEditorColumnHeaders(out int count);
Call the function like this:
int count;
IntPtr PtxEditorColumnHeaders = GetPtxEditorColumnHeaders(out count);
List<string> headers = new List<string>();
for (int i=0; i<count; i++)
{
IntPtr strPtr = Marshal.ReadIntPtr(PtxEditorColumnHeaders);
headers.Add(Marshal.PtrToStringAnsi(strPtr));
PtxEditorColumnHeaders += Marshal.SizeOf(typeof(IntPtr));
}
This stuff gets tedious pretty quickly, at which point a C++/CLI wrapper begins to look like a more attractive option.

Categories