Passing byte array between C++ and C# ByRef raises AccessViolationException - c#

I am trying to create a Win32 DLL exposes some functions which are called in C# as follows
__declspec(dllexport) int GetData(unsigned char* *data, int* size)
{
try
{
int tlen = 3;
unsigned char* tchr = new unsigned char[5];
tchr[0] = 'a';
tchr[1] = 'b';
tchr[2] = 'c';
*size = tlen;
*data = tchr;
return 1;
}
catch (char *p)
{
return 0;
}
}
And on C# side
[DllImport("MyDll.dll")]
static extern int GetData(ref byte[] data, ref int size);
static void Main()
{
try
{
int hr = 0;
byte[] gData = null;
int gSize = 0;
hr = GetData(ref gData, ref gSize);
Console.WriteLine(gSize);
for (int i = 0; i < gSize; i++)
Console.WriteLine((char)gData[i]);
}
catch (Exception p)
{
Console.WriteLine(p.ToString());
}
}
When I run C# code, AccessViolationException happens on GetData function which is a sign of exception in C++ code however, following C++ code snippet works fine without any error.
int _tmain(int argc, _TCHAR* argv[])
{
unsigned char* data = NULL;
int size = NULL;
GetData(&data, &size);
printf("%d", size);
for (int i = 0; i < size; i++)
printf("%c,", data[i]);
return 0;
}
If you compare C# main function and C++ _tmain, they are almost analoguous so where I may make a mistake?

You are returning an array allocated by a call to C++ new and hoping that the marshaler will turn it into a C# byte[]. That won't happen.
You'll need to pass a pointer by reference and then marshal it by hand. Your p/invoke should look like this:
[DllImport("MyDll.dll")]
static extern int GetData(out IntPtr data, out int size);
When the function returns data will point to the array and you can read the contents using the Marshal class. I guess you would copy it to a new byte array.
var arr = new byte[size];
Marshal.Copy(data, arr, 0, size);
Some other points:
The calling conventions do not match. The native side is cdecl and the managed is stdcall.
You'll need to export a deallocator to delete the memory returned by the native function. Consider a re-design where the caller allocates the buffer.

Related

Problem returning unsigned char value from C++ dll to C#

In the following example, I try to concatenate two unsigned chars(which is the requirement) by passing the values to a C++ dll and return a single string. The output I'm getting is not right.
C#:
using System;
using System.Runtime.InteropServices;
using System.Text;
class HelloWorld
{
[DllImport("cpp_func.dll")]
public static extern IntPtr concat_fun(byte[] a,byte[] b, int c, int d);
static void Main()
{
int x,y;
IntPtr return_value;
string hello = "hello", world = "world", final;
byte[] hel = Encoding.ASCII.GetBytes(hello);
byte[] wor = Encoding.ASCII.GetBytes(world);
x = hel.Length;
y = wor.Length;
return_value = concat_fun(hel, wor, hel.Length, wor.Length);
final = Marshal.PtrToStringAuto(return_value);
Console.WriteLine("Concatenated string:" +final);
Console.Read();
}
}
I've declared them as byte[], since that's how native type uint8_t is represented in .NET(https://learn.microsoft.com/en-us/dotnet/standard/native-interop/type-marshaling)
I've passed the two byte array along with their lengths as parameters.
C++:
_declspec(dllexport) unsigned char * concat_fun(unsigned char a[], unsigned char b[], int d, int e) {
int i, ind = 0;
unsigned char c[20];
for (i = 0; i < d; i++) {
c[ind] = a[i];
ind++;
}
for (i = 0; i < e; i++) {
c[ind] = b[i];
ind++;
}
return c;
}
The output I'm getting is this:
Concatenated string:????????????????
How do I get the concatenated string?
Note: Getting input as an unsigned char for the dll function parameter, is a requirement
I know I'm making some trivial mistake here, since I'm just a beginner.
The memory for c array is allocated in the concat_fun function and has a scope and lifetime of this function, so the memory is released when you leave the function body.
Try to allocate c array in the calling function Main or use dynamic memory allocation: new/delete or malloc/free in concat_fun.

How to marshall array of int from c# to delphi

I have the following export from a dll made with Delphi2006.
procedure ScSetMRStatus(StatusType: TStatusType; active_mr_ids: TLongIntArray; id_dst: Integer; id_src_list: TLongIntArray; IsComplete: Boolean); stdcall; export;
where TLongIntArray is defined as:
TLongIntArray = array of LongInt;
and TStatusType is just an enum:
TStatusType = ( stPhysical, stMaster );
Now I tried to call this method from a c# application.
[DllImport(DelphiDLLName, EntryPoint = "ScSetMRStatus", CallingConvention = CallingConvention.StdCall)]
private static extern void ScSetMRStatus(
Int32 statusType,
IntPtr activeMrIds,
Int32 IdDst,
IntPtr idSrcList,
[MarshalAs(UnmanagedType.U1)] bool isComplete);
Using it this way from c#:
ScSetMRStatus((Int32) statusType, ConvertManagedArrayToDelphiDynIntArray(activeMrIds), idDst, ConvertManagedArrayToDelphiDynIntArray(idSrcList), isComplete);
ConvertManagedArrayToDelpiDynIntArray looks like:
public static IntPtr ConvertManagedArrayToDelphiDynIntArray(int[] array)
{
if (array == null) return IntPtr.Zero;
int elementSize = sizeof(int);
int arrayLength = array.Length;
int allocatedMemSize = 8 + elementSize * arrayLength;
IntPtr delphiArrayPtr = Marshal.AllocHGlobal(allocatedMemSize);
Marshal.WriteInt32(delphiArrayPtr, 0, 1);
Marshal.WriteInt32(delphiArrayPtr, 4, arrayLength);
for (int k = 0; k < arrayLength; k++) {
Marshal.WriteInt32(delphiArrayPtr, 8 + k*elementSize, array[k]);
}
return delphiArrayPtr+8;
}
But this doesn't work!
How do I send c# arrays to delphi?
It's finally working!
We did a few changes around the call to free the allocated memory.
var activeMrIdsNative = ConvertManagedArrayToDelphiDynIntArray(activeMrIds);
var idSrcListNative = ConvertManagedArrayToDelphiDynIntArray(idSrcList);
ScSetMRStatus((Int32) statusType, activeMrIdsNative, idDst, idSrcListNative, isComplete);
Marshal.FreeHGlobal(activeMrIdsNative-8);
Marshal.FreeHGlobal(idSrcListNative-8);
We just thought it would not work, as we have not looked at what the Delphi side does with it. All data gets it's way into the delphi dll and it's working good.
Maybe there is a memory issue, but we will examine this.

PInvoke function with pointer to pointer parameter

Hello I'm wrapping C++ library with C#. Next function in C++:
SCREENCAPTUREDLL_API wchar_t** getAudioDeviceList(int* listSize) {
static std::vector<wchar_t*> descriptionList;
AudioCaptureList::getInstance().Update();
AudioCaptureList::getInstance().getList(&descriptionList);
*listSize = descriptionList.size();
return &descriptionList[0];
}
Wrapping with next C# code:
[DllImport(screenCaptureDLLPath, CallingConvention = callConversion)]
private static extern IntPtr getAudioDeviceList(ref int arrayCount);
public static string[] GetAudioDeviceList()
{
IntPtr outputStr;
int length = 0;
outputStr = getAudioDeviceList(ref length);
string[] resultArray = new string[length];
for (int j = 0; j < length; j++)
{
resultArray[j] = Marshal.PtrToStringUni(Marshal.ReadIntPtr(outputStr, 4 * j));
}
return resultArray;
}
That works perfect, exactly as I expected, but I was about to change the way I returning value from function itself to variable by reference, so I changing my code to:
C++
SCREENCAPTUREDLL_API void getAudioDeviceList(wchar_t** list, int* listSize) {
static std::vector<wchar_t*> descriptionList;
AudioCaptureList::getInstance().Update();
AudioCaptureList::getInstance().getList(&descriptionList);
*listSize = descriptionList.size();
list = &descriptionList[0];
}
C#
[DllImport(screenCaptureDLLPath, CallingConvention = callConversion)]
private static extern void getAudioDeviceList(out IntPtr listRef, ref int arrayCount);
public static string[] GetAudioDeviceList()
{
IntPtr outputStr;
int length = 0;
getAudioDeviceList(out outputStr, ref length);
string[] resultArray = new string[length];
for (int j = 0; j < length; j++)
{
resultArray[j] = Marshal.PtrToStringUni(Marshal.ReadIntPtr(outputStr, 4 * j));
}
return resultArray;
}
But I got error, returned memory address is zero. What is the problem here? Please help me understood what cause the problem and how to fix that, thanks!
Why doesn't Pinvoke work? Because you are trying to interpret a pointer to a string as a pointer to a set of strings. But there is nothing wrong with PInvoke - it happens because there is actually a problem with new function signature and its internal code.
See:
SCREENCAPTUREDLL_API void getAudioDeviceList(wchar_t** listRef, int* listSize);
can't provide the same data like
DLL_API wchar_t** getAudioDeviceList(int* listSize)
Because original definition basically returned pointer to a set of pointers to strings(C style strings, I mean), while wchar_t** listRef can only allow to return a single pointer to a string.
SCREENCAPTUREDLL_API void getAudioDeviceList(wchar_t** listRef, int* listSize)
{
...
*listRef = "string";
I don't know what is going inside new version of the function(you didn't show the code), but listRef = &descriptionList[0]; will compile though won't do anything, and even if *listRef = &descriptionList[0]; somehow compiles it won't contain what you want.
So function signature should containt triple pointer to allow return of a set of strings.
SCREENCAPTUREDLL_API void getAudioDeviceList(wchar_t*** listRef, int* listSize)
{
...
*listRef = &descriptionList[0];
}
Then your PInvoke would work correctly because it will have the same pointer to a set of string pointers.

Marshal float* to C#

I have a DLL which exports a function that returns a float*, that I would like to use it in my C# code. I am not sure how to Marshal my float* so that I can safely use it in C#.
So, in my C++ DLL, I have declared:
static float* GetSamples(int identifier, int dataSize);
In my C# script, I have:
[DllImport ("__Internal")]
public static extern float[] GetSamples (int identifier, int dataSize);
The C++ GetSamples(int,int) allocates memory and return a pointer t the float array. How do I declare the C# GetSamples to Marshal my float array, and how do I access the data (either by iteration or Marshal.Copy)?
Also, can I delete the float* from C# or do I have to call another C++ function to delete the allocated memory?
EDIT:
So this is what I have tried up to now.
First, on the C# side:
Declaration:
[DllImport ("__Internal")]
public static extern int GetSamples ([In, Out]IntPtr buffer,int length, [Out] out IntPtr written);
Trying to call it:
IntPtr dataPointer = new IntPtr();
IntPtr outPtr;
GetSamples(dataPointer, data.Length, out outPtr);
for (var i = 0; i < data.Length; i++){
copiedData[i] = Marshal.ReadByte(dataPointer, i);
}
Then in my C++ lib:
int AudioReader::RetrieveSamples(float * sampleBuffer, size_t dataLength, size_t * /* out */ written)
{
float* mydata = new float[dataLength];
//This is where I copy the actual data into mydata
memcpy(sampleBuffer, mydata, dataLength*sizeof(float));
delete data;
return dataLength;
}
I don't really know what outPtr is for... And I know I have some additional copying steps that I can removes, I just want to get it working for now.
So this is a bit of a complicated answer...
.NET doesn't know how to handle C++ memory allocation, so regardless returning a float * is dangerous at best for this. Furthermore the .NET memory model is based on COM so it is CoTaskMemAlloc based, not that it really helps you here. So here is what I would suggest:
int AudioReader::RetrieveSamples(
float * sampleBuffer,
int dataLength,
int * /* out */ written)
{
// assuming mydata is already defined
if(sampleBuffer == NULL || dataLength == 0)
{
*written = sizeof(mydata);
return -1;
}
ZeroMemory(sampleBuffer, dataLength);
int toCopy = min(dataLength, sizeof(myData));
//This is where I copy the actual data into mydata
memcpy(sampleBuffer, mydata, toCopy);
*written = toCopy;
return 0;
}
[DLLImport("__internal")]
private static extern int GetSamples(
[In, Out]IntPtr buffer,
[In] int length,
[Out] out int written);
float[] RetrieveFloats()
{
int bytesToAllocate = 0;
GetSamples(IntPtr.Zero, 0, out bytesToAllocate);
if(bytesToAllocate == 0)
return null;
int floatCount = bytesToAllocate/ sizeof(float);
float[] toReturn = new float[floatCount];
IntPtr allocatedMemory = Marshal.AllocHGlobal(bytesToAllocate);
int written = 0;
if(GetSamples(allocatedMemory, bytesToAllocate, out written) != -1)
{
floatCount = written/sizeof(float);
Marshal.Copy(allocatedMemory, toReturn, 0, floatCount);
}
Marshal.FreeHGlobal(allocatedMemory);
return toReturn;
}
Passing a bufferLength of zero would return the space required for the buffer, which can then be allocated and passed in.
You will need to allocate the memory for the buffer in C#, you cannot allocate it in C++

AccessViolation

I keep getting an AccessViolationException when calling the following from an external C DLL:
short get_device_list(char ***device_list, int *number_of_devices);
I set up a DLLImport declaration as such:
[DLLImport("mydll.dll")]
static public extern short get_device_list([MarshalAs(UnmanagedType.LPArray)] ref string[] devices, ref int number_of_devices);
My C# application code:
{
string[] devices = new string[20];
int i = 0;
short ret = 0;
ret = get_device_list(ref devices, ref i); // I receive the AccessViolation Exception here
// devices[0] = "2255f796e958f7f31a7d2e6b833d2d426c634621" which is correct.
}
Although I receive the exception, the device array gets filled correctly with the 2 UUIDs of the devices connected (and also gets resized to size = 2; i is also 2;).
What is wrong?
PS: After a long research I also tried:
[DLLImport("mydll.dll")]
static public extern short get_device_list(ref IntPtr devices, ref int number_of_devices);
and
{
IntPtr devices = new IntPtr();
int i = 0;
short ret = 0;
ret = get_device_list(ref devices, ref i); // No AccessViolation Exception here
string b = Marshal.PtrToStringAuto(devices); // b = "歀ׄ", which is incorrect
}
but that did not help me.
Thanks in advance!
[DLLImport("mydll.dll")]
static public extern short get_device_list(out IntPtr devices,
out int number_of_devices);
Is the best way to tackle this. The memory is allocated and owned on the native side of the interface. The trick is how to get at it. Something like this should work.
static public string[] getDevices()
{
IntPtr devices;
int deviceCount;
short ret = get_device_list(out devices, out deviceCount);
//need to test ret in case of error
string[] result = new string[deviceCount];
for (int i=0; i<deviceCount; i++)
{
IntPtr ptr = (IntPtr)Marshal.PtrToStructure(devices, typeof(IntPtr));
result[i] = Marshal.PtrToStringAnsi(ptr);
devices += IntPtr.Size;//move to next element of array
}
return result;
}
Your code was using PtrToStringAuto but that's going to interpret the data as UTF-16 encoded. But your C++ code uses char* which is 8 bit ANSI. So you need PtrToStringAnsi. OK, there's an assumption here that the encoding is not UTF-8, but that's a detail I cannot provide. It's easy enough to adapt this to UTF-8.
You should also double check that the native code uses the stdcall calling convention and isn't using cdecl.
Edit:
Ok I think I know the problem in your second attempt.
{
IntPtr devices = new IntPtr();
int i = 0;
short ret = 0;
ret = get_device_list(ref devices, ref i); // No AccessViolation Exception here
string b = Marshal.PtrToStringAuto(devices); // b = "歀ׄ", which is incorrect
}
You try to convert a pointer to an array of strings to a string. You have to deference it first. Please check whether this works for you:
IntPtr devices = new IntPtr();
int numDevices = 0;
short ret = get_device_list(ref devices, ref numDevices); // No AccessViolation Exception here
for (int i=0; i<numDevices; i++)
{
IntPtr ptrToString = Marshal.ReadIntPtr(devices);
string deviceString = Marshal.PtrToStringAnsi(ptrToString);
devices += IntPtr.size;
Console.WriteLine(deviceString);
}

Categories

Resources