I know this can be done by mallocing in C, passing malloced pointer to delegate with parameter type IntPtr, marshalling to string[] and then freeing malloced memory with separate, exported C-function from managed code.
My question is: Can this be done simpler way? E.g. :
- C# delegate parameter is of type string[]?
- no separate free function to call from managed code
EDIT: I tried with delegate signature:
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
MyManagedDelegate(string[] values, int valueCount)
and fucntion in C:
void NativeCallDelegate(char *pStringValues[], int nValues)
{
if (gSetStringValuesCB)
gSetStringValuesCB(pStringValues, nValues);
}
calling it in C:
char *Values[]= {"One", "Two", "Three"};
NativeCallDelegate(Values, 3);
This results in that i can use only 1st string in array.
Here's how to do it properly, I'll give a full example so it's reproductible.
The C side
typedef void(*setStringValuesCB_t)(char *pStringValues[], int nValues);
static setStringValuesCB_t gSetStringValuesCB;
void NativeCallDelegate(char *pStringValues[], int nValues)
{
if (gSetStringValuesCB)
gSetStringValuesCB(pStringValues, nValues);
}
__declspec(dllexport) void NativeLibCall(setStringValuesCB_t callback)
{
gSetStringValuesCB = callback;
char *Values[] = { "One", "Two", "Three" };
NativeCallDelegate(Values, 3);
}
Nothing fancy here, I just added the necessary glue code and left the rest alone.
The C# side
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
public delegate void MyManagedDelegate(
[MarshalAs(UnmanagedType.LPArray, ArraySubType = UnmanagedType.LPStr, SizeParamIndex = 1)]
string[] values,
int valueCount);
[DllImport("NativeTemp", CallingConvention = CallingConvention.Cdecl)]
public static extern void NativeLibCall(MyManagedDelegate callback);
public static void Main()
{
NativeLibCall(PrintReceivedData);
}
public static void PrintReceivedData(string[] values, int valueCount)
{
foreach (var item in values)
Console.WriteLine(item);
}
The trick lies in the marshaling part:
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
public delegate void MyManagedDelegate(
[MarshalAs(UnmanagedType.LPArray, ArraySubType = UnmanagedType.LPStr, SizeParamIndex = 1)]
string[] values,
int valueCount);
The MarshalAs
attribute tells the .NET marshaler the following:
UnmanagedType.LPArray
You're getting an array...
ArraySubType = UnmanagedType.LPStr
...of standard C strings...
SizeParamIndex = 1
...and the size of that array is specified by the second parameter.
The C strings are copied and converted to System.String
instances by the .NET marshaler before the invocation of your C# method. So if you need to pass dynamically generated strings to C#, you malloc
them, then you call gSetStringValuesCB
, and you can free
them immediately afterwards, all from the C code, as .NET has its own copy of the data.
You can refer to the docs:
UnmanagedType.LPArray
:
A pointer to the first element of a C-style array. When marshaling from managed to unmanaged code, the length of the array is determined by the length of the managed array. When marshaling from unmanaged to managed code, the length of the array is determined from the MarshalAsAttribute.SizeConst
and MarshalAsAttribute.SizeParamIndex
fields, optionally followed by the unmanaged type of the elements within the array when it is necessary to differentiate among string types.
UnmanagedType.LPStr
:
A single byte, null-terminated ANSI character string. You can use this member on the System.String
and System.Text.StringBuilder
data types.
MarshalAs.SizeParamIndex
:
Indicates the zero-based parameter that contains the count of array elements, similar to size_is
in COM.
I came up with far from optimal solution:
public delegate void MyManagedDelegate([MarshalAs(UnmanagedType.LPArray, ArraySubType = UnmanagedType.LPStr, SizeConst=10)]string[] values, int valueCount);
This is not working if called like this:
char *Values[]= {"One", "Two", "Three"};
NativeCallDelegate(Values, 3);
I could have fixed size 10 array where the values are copied and that is always passed to delegate. This is not what I want. I wonder if there is good solution to this...