如何正确地将字符串从 Unity 编组到 C/C++?
How to properly marshal strings from Unity to C/C++?
以下代码片段来自 Unity Bonjour 客户端示例,它演示了如何通过 C# 与本机代码进行交互。这是一个简单的 C 函数,returns 一个字符串返回到 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]);
}
函数的 C# 声明如下所示:
[DllImport ("__Internal")]
private static extern string _GetLookupStatus ();
这里有几件事让我很困惑:
- 这是 return 从 iOS 本机代码到 C# 的字符串的正确方法吗?
- returned 字符串是如何被释放的?
- 有更好的方法吗?
对此事的任何见解表示赞赏。
谢谢。
1.号
2。你必须自己做。
3.是
如果在 C 或 C++ 端的函数内部分配内存,则必须释放它。我没有看到任何分配内存的代码,但我假设你离开了那部分。另外,不要 return 在堆栈上向 C# 声明的变量。您最终会遇到未定义的行为,包括崩溃。
是一个 C++ 解决方案。
对于C解:
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;
}
唯一的区别是 C 版本使用 malloc
和 free
函数来分配和取消分配内存。
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];
}
请注意,这只是一个仅使用 2 个字符 的 char 的测试。您可以通过向 C# 函数添加 out int outValue
参数然后向 C 函数添加 int* outValue
参数来使字符串的大小动态化。然后,您可以在 C 端将字符的大小写入此参数,并从 C# 端访问该大小。
然后可以将此大小传递给 Marshal.Copy
函数的最后一个参数,并删除当前硬编码的 2 值限制。我会把这个留给你去做,但如果你感到困惑,请参见 post 的例子。
更好的解决方案是将StringBuilder
传递给本机端然后写入它。不利的一面是您必须按时声明 StringBuilder
的大小。
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();
}
如果您正在寻找最快的方法,那么您应该在 C# 端使用 char 数组,将其固定在 C# 端,然后将其作为 IntPtr
发送到 C。在C端,可以使用strcpy_s
修改char数组。这样,C 端就不会分配内存。您只是从 C# 中重新使用 char 数组的内存。您可以在答案 .
末尾看到 float[]
示例
以下代码片段来自 Unity Bonjour 客户端示例,它演示了如何通过 C# 与本机代码进行交互。这是一个简单的 C 函数,returns 一个字符串返回到 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]);
}
函数的 C# 声明如下所示:
[DllImport ("__Internal")]
private static extern string _GetLookupStatus ();
这里有几件事让我很困惑:
- 这是 return 从 iOS 本机代码到 C# 的字符串的正确方法吗?
- returned 字符串是如何被释放的?
- 有更好的方法吗?
对此事的任何见解表示赞赏。 谢谢。
1.号
2。你必须自己做。
3.是
如果在 C 或 C++ 端的函数内部分配内存,则必须释放它。我没有看到任何分配内存的代码,但我假设你离开了那部分。另外,不要 return 在堆栈上向 C# 声明的变量。您最终会遇到未定义的行为,包括崩溃。
对于C解:
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;
}
唯一的区别是 C 版本使用 malloc
和 free
函数来分配和取消分配内存。
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];
}
请注意,这只是一个仅使用 2 个字符 的 char 的测试。您可以通过向 C# 函数添加 out int outValue
参数然后向 C 函数添加 int* outValue
参数来使字符串的大小动态化。然后,您可以在 C 端将字符的大小写入此参数,并从 C# 端访问该大小。
然后可以将此大小传递给 Marshal.Copy
函数的最后一个参数,并删除当前硬编码的 2 值限制。我会把这个留给你去做,但如果你感到困惑,请参见
更好的解决方案是将StringBuilder
传递给本机端然后写入它。不利的一面是您必须按时声明 StringBuilder
的大小。
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();
}
如果您正在寻找最快的方法,那么您应该在 C# 端使用 char 数组,将其固定在 C# 端,然后将其作为 IntPtr
发送到 C。在C端,可以使用strcpy_s
修改char数组。这样,C 端就不会分配内存。您只是从 C# 中重新使用 char 数组的内存。您可以在答案
float[]
示例