The following code snippet is from Unities Bonjour client example, which demonstrates how to interact with native code from C#. It's a simple C function that returns a string back to C# (Unity):
char* MakeStringCopy (const char* string)
{
if (string == NULL)
return NULL;
char* res = (char*)malloc(strlen(string) + 1);
strcpy(res, string);
return res;
}
const char* _GetLookupStatus ()
{
// By default mono string marshaler creates .Net string for returned UTF-8 C string
// and calls free for returned value, thus returned strings should be allocated on heap
return MakeStringCopy([[delegateObject getStatus] UTF8String]);
}
The C# declaration of the function looks like:
[DllImport ("__Internal")]
private static extern string _GetLookupStatus ();
There are a few things that puzzle me here:
- Is this the right way to return a string from iOS native code to C#?
- How does the returned string ever get freed?
- Is there a better way to do it?
Any insights in this matter are appreciated.
Thank you.
1.No.
2.You have to do that yourself.
3.Yes
If you allocate memory inside a function on the C or C++ side, you must free it. I don't see any code allocating memory on the side but I assume you left that part. Also, do not return a variable declared on the stack to C#. You will end up with undefined behavior including crashes.
Here is a C++ solution for this.
For the C solution:
char* getByteArray()
{
//Create your array(Allocate memory)
char * arrayTest = malloc( 2 * sizeof(char) );
//Do something to the Array
arrayTest[0]=3;
arrayTest[1]=5;
//Return it
return arrayTest;
}
int freeMem(char* arrayPtr){
free(arrayPtr);
return 0;
}
The only difference is that the C version uses malloc
and free
function to allocate and de-allocate memory.
C#:
[DllImport("__Internal", CallingConvention = CallingConvention.Cdecl)]
public static extern IntPtr getByteArray();
[DllImport("__Internal", CallingConvention = CallingConvention.Cdecl)]
public static extern int freeMem(IntPtr ptr);
//Test
void Start()
{
//Call and return the pointer
IntPtr returnedPtr = getIntArray();
//Create new Variable to Store the result
byte[] returnedResult = new byte[2];
//Copy from result pointer to the C# variable
Marshal.Copy(returnedPtr, returnedResult, 0, 2);
//Free native memory
freeMem(returnedPtr);
//The returned value is saved in the returnedResult variable
byte val1 = returnedResult[0];
byte val2 = returnedResult[1];
}
Note that this is only a test that uses char with 2 characters only. You can make the size of the string dynamic by adding a out int outValue
parameter to the C# function then adding int* outValue
parameter to the C function. You can then write to this parameter on the C side the size of the character is and access that size from the C# side.
This size can then be passed to the last argument of the Marshal.Copy
function and remove the current hard-coded 2 value limit. I will leave this for you to do but if confused, see this post for example of that.
The better solution is to pass StringBuilder
to the native side then write to it. The bad side is that you have to declare the size of the StringBuilder
on time.
C++:
void __cdecl _GetLookupStatus (char* data, size_t size)
{
strcpy_s(data, size, "Test");
}
C#:
[DllImport("__Internal", CallingConvention = CallingConvention.Cdecl)]
public static extern int _GetLookupStatus(StringBuilder data, int size);
//Test
void Start()
{
StringBuilder buffer = new StringBuilder(500);
_GetLookupStatus (buffer, buffer.Capacity);
string result = buffer.ToString();
}
If you are looking for the fastest way then you should use char array on the C# side, pin it on C# side then send it to C as IntPtr
. On the C side, you can use strcpy_s
to modify the char array. That way, no memory is allocated on the C side. You are just re-using the memory of the char array from C#. You can see the float[]
example at the end of the answer here.