【问题标题】:How to properly marshal strings from Unity to C/C++?如何正确地将字符串从 Unity 编组为 C/C++?
【发布时间】:2017-06-09 18:28:29
【问题描述】:

以下代码 sn-p 来自 Unities Bonjour 客户端示例,该示例演示了如何与 C# 中的本机代码进行交互。这是一个简单的 C 函数,将字符串返回给 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 ();

这里有几件事让我感到困惑:

  1. 这是将字符串从 iOS 原生代码返回到 C# 的正确方法吗?
  2. 如何释放返回的字符串?
  3. 有更好的方法吗?

感谢您对此事的任何见解。 谢谢。

【问题讨论】:

    标签: c# ios c unity3d marshalling


    【解决方案1】:

    1.No.

    2。你必须自己做。

    3.是

    如果您在 C 或 C++ 端的函数内部分配内存,则必须释放它。我没有看到任何代码分配内存,但我假设你离开了那部分。此外,不要将堆栈上声明的变量返回给 C#。您最终会遇到未定义的行为,包括崩溃。

    Here 是一个 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版本使用mallocfree函数来分配和释放内存。

    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 的测试。您可以通过将out int outValue 参数添加到C# 函数然后将int* outValue 参数添加到C 函数来使字符串的大小动态化。然后,您可以在 C 端将字符的大小写入此参数,并从 C# 端访问该大小。

    然后可以将此大小传递给Marshal.Copy 函数的最后一个参数,并删除当前硬编码的2 值限制。我会把这个留给你做,但如果你感到困惑,请参阅this 帖子的例子。


    更好的解决方案是将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 数组的内存。您可以在答案here 的末尾看到float[] 示例。

    【讨论】:

    • 非常感谢您的详细说明!我在代码 sn-p 中添加了 MakeStringCopy()。
    • 关于不返回堆栈上传递的变量的另一个问题:它是否也适用于原始数据类型?我做了一些测试,字符串立即失败,但 int 似乎可以工作。
    • 如果是 int,float,bool 就可以了。如果您使用& 返回它的引用或使用* 返回它的指针,那么它就不好了。如果它是一个数组,例如int []int *,那么它也不好,因为您要返回它的内存地址。如果它是使用 C 中的 malloc 或 C++ 中的 new 关键字创建的变量,那很好,因为即使您在函数之外,它们也会在那里。我认为您应该了解如何在函数中返回引用或指针。你会完全理解这些。
    • 好的,我明白了!再次感谢您的澄清。如果您刚开始使用 C# 编组,它是一个复杂的野兽。非常感谢您的帮助!谢谢。
    • 实际上第一个问题的答案是肯定的,这是从本机函数返回字符串的有效方法。根据 Unity 的文档:“从本机方法返回的字符串值应该是 UTF-8 编码并在堆上分配。这样的字符串可以免费调用单声道编组。”
    猜你喜欢
    • 2013-05-28
    • 2019-07-19
    • 1970-01-01
    • 2020-02-26
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-05-11
    • 2013-08-18
    相关资源
    最近更新 更多