Array of StringBuilders not working with P/Invoke - c#

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?

Related

Marshaling issue with char** while accessing a library function

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.

How to copy an array of floats from c# to a C dll

I'm trying to copy an array of floats from my C# application to an array in a C-coded DLL.
Im used to programming in C#, not so much with C. However I have no problem doing the reverse procedure ie. reading an array of floats from a C coded DLL into my C# application. I've read several threads on this site but cant work out where Im going wrong.
C# CODE
[DllImport(#"MyDll")]
static extern int CopyArray(double[] MyArray);
double[] myValues = new double[100]
int a = 0;
while(a < 100)
{
myValues[a] = a;
a++;
}
CopyArray(myValues);
C++ DLL
This is the function header;
__declspec(dllexport) int CopyArray(float* Wavelengths);
This is the function code;
float theValues[100];
int CopyArray(float* theArray)
{
status = 0;
int count = 0;
while (count < 100)
{
theValues[count] = theArray[count];
++count;
}
return(status);
}
I'm expecting my C# array to end up in the C array "theValues" but that's not happening. There is nothing getting into "theValues" array.
A couple of things.
You are mixing data types and they are different lengths (float is 32bit and double is 64bit). Both types exist in both languages, but your caller and callee need to agree on the data type. Here is a list of data types and their managed/unmanaged equivalents.
The parameter you are sending is not a pointer. It might be translated to that automatically by the compiler, but there are several options. It is entirely possible that the compiler will pick one you don't want (more info here). The one you are probably looking for is [In]. If you want to get data back from C to C#, you will also want [Out]:
[DllImport(#"MyDll")]
static extern int CopyArray([In, Out]double[] MyArray);

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