Reputation: 2302
I have some of unmanaged C++ dynamic library and C# GUI application, using it. I need to pass array of strings with known size to C++ library, which populates it. Also there is maximum string length value. I'm trying to pass array of StringBuilder
with suitable size itself, and suitable internal elements capacity (aka array of buffers).
// C++ library part
#define MAX_STRING_LENGTH 200
const wchar_t* stringToPopulate = L"Hello, World";
MYDLL_API uint8_t __stdcall getSomeStrings(wchar_t** strings, uint64_t count) {
for(auto i =0; i < count; ++i){
// trivial string population
wcsncpy(*(providers + i), stringToPopulate,
min(wcslen(stringToPopulate), MAX_STRING_LENGTH ));
}
return 0;
}
// C# application part
[DllImport("myDllName.dll", CallingConvention = CallingConvention.StdCall)]
static extern Byte getSomeStrings(
[MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 0)] StringBuilder[] strings,
UInt64 size
);
// calling
// create suitable string array
StringBuilder[] stringArrayToPass= new StringBuilder[desirableStringCount];
for(ulong i =0; i < desirableStringCount; ++i) {
stringArrayToPass[i] = new StringBuilder(maxStringLength);
}
res = getSomeStrings(stringArrayToPass, desirableStringCount); // exception
I've got System.AccessViolationException
exception on the library API call. What can be a reason of such issue?
Upvotes: 0
Views: 1260
Reputation: 942348
[MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 0)]
That's nonsense, the size is given by parameter 1, not 0. Just remove it completely, the pinvoke marshaller doesn't need any help figuring out how long the array is. It already knows, it uses the array's Length property.
wcsncpy(*(providers + i), stringToPopulate,
min(wcslen(stringToPopulate), MAX_STRING_LENGTH ));
wcsncpy is an evil, evil, evil function. You normally get a loud objection from the Microsoft C/C++ compiler about it, warning you that it is unsafe. Don't just ignore such a warning. It solves the "string doesn't fit" problem by doing the unthinkable, it doesn't zero-terminate the string. The caller will always malfunction when that happens, hopelessly blundering beyond the end of the string. An AVE is (almost) unavoidable, reading garbage if it doesn't. The way you used it this will always happen.
Presumably this was an attempt to make the function safe, it accomplished the opposite. The core issue that there is no way for the caller to specify how large the string buffers are. So you should rewrite it so it can specify that size. For example:
MYDLL_API int __stdcall getOneString(int index, wchar_t* buffer, size_t bufferLength)
Now it is simple and always safe, the client code just calls it repeatedly, incrementing the index argument. It doesn't have to guess how large an array it needs to pass. And your C code doesn't have to guess how large the buffer is. Return an error code if the index is out of range so the caller knows to stop calling. And another one if the string doesn't fit so the client can recover.
Upvotes: 3