Unity 6.3
0 онлайн 46 гостей 3 в системе
Вход

Струны

При передаче строки между управляемым и неуправляемым кодом в большинстве случаев поведение сортировки по умолчанию создает копию строки и преобразует ее между управляемым и неуправляемым кодом. .NET и форматы платформы по умолчанию, если это необходимо. Вы можете изменить это поведение с помощью MarshalAsAttributeСм. Строки, используемые в вызовах платформы для больше информации.

Примечание: Различные платформы используют разные распределители памяти. Когда нативный код возвращает буфер, который маршрал .NET позже высвобождает, вы должны выделить его с распределителем, ожидаемым временем выполнения: распределителем COM (CoTaskMemAlloc) на Windows и распределителем C (malloc) на других платформах. Выделение и высвобождение памяти с несоответствующими функциями может вызвать сбой программы.

Следующие примеры демонстрируют способы передачи строковых данных между управляемыми и неуправляемыми функциями.

#include <stdlib.h> // malloc
#include <string.h> // strlen, memcpy
#if defined(_WIN32)
#include <objbase.h> // CoTaskMemAlloc
#endif

#if defined(_WIN32)
void* Allocate(size_t size)
{
    // .NET runtime assumes the COM allocator when freeing memory returned from native code on Windows-based platforms
    return CoTaskMemAlloc(size);
}
#else
void* Allocate(size_t size)
{
    // .NET runtime assumes the C allocator when freeing memory returned from native code on non-Windows platforms
    return malloc(size);
}
#endif

char* AllocateAndCopyString(const char* value)
{
    size_t bufferSize = strlen(value) + 1;
    void* ret = Allocate(bufferSize);
    memcpy(ret, value, bufferSize);
    return (char*)ret;
}

extern "C" {
    void SendString(const char* message) {
        printf("%s\n", message); // Shown in Player.log
        fflush(stdout);
    }

    const char* GetUnmanagedString() {
        return AllocateAndCopyString("Hello from C\u002B\u002B"); // The runtime frees this with the matching allocator
    }

    void SendStringArray(const char** messages, int length) {
        for(int i = 0; i < length; i++) {
            printf("%s\n", messages[i]);
        }
        fflush(stdout);
    }

    void SendCollectionsString(const char* strPtr, int length) {
        printf("%.*s\n", length, strPtr);
        fflush(stdout);
    }
}

Примечание: См. Вызов неуправляемых функций из управляемого кода для получения информации об аннотациях, необходимых для компиляции и вызова таких функций, как эта, в качестве части динамически загружаемой библиотеки.

Примечание: Маршаллизация строк как LPUTF8Str (как в примере C# ниже) передает правильные UTF-8 байтов в основной код, но печать их с printf не отображает их одинаково на каждой платформе. На платформах, где консоль и время выполнения C по умолчанию UTF-8 (например, macOS, Linux, Android и iOS), вышеуказанные вызовы printf отображают текст не ASCII правильно. На Windows консоль и время выполнения C по умолчанию не UTF-8 кодовая страница, так что обычная printf из UTF-8 текста может выглядеть искаженным, даже если данные маршеллированы правильно. Чтобы печатать текст не ASCII на Windows, настройте консоль на UTF-8 (например, с SetConsoleOutputCP(CP_UTF8)) или преобразуйте UTF-8 байтов в UTF-16 с MultiByteToWideChar и печатайте их с широким символом APIs.

В этом примере C# используются неуправляемые функции, чтобы продемонстрировать, как:

  • передать строку неуправляемой функции
  • возврат строки из неуправляемой функции
  • передать строку массива неуправляемой функции
using UnityEngine;
using System;
using System.Runtime.InteropServices;

public class StringMarshalExample : MonoBehaviour
{
    // Pass string parameter. Marshal as UTF-8 so non-ASCII text is preserved;
    // the default string marshalling isn't Unicode-safe on every platform.
    [DllImport("__Internal")]
    private static extern void SendString([MarshalAs(UnmanagedType.LPUTF8Str)] string message);

    // Return string from native. Marshalling the return value as LPUTF8Str makes
    // the runtime copy the string into managed memory and free the native buffer
    // with the platform's default allocator (CoTaskMemFree on Windows, free
    // elsewhere), which matches the Allocate() function in the native code.
    [DllImport("__Internal")]
    [return: MarshalAs(UnmanagedType.LPUTF8Str)]
    private static extern string GetUnmanagedString();

    // Pass string array parameter. Marshal each element as UTF-8 for the same
    // reason as SendString; without ArraySubType the elements default to the
    // platform's ANSI marshalling, which isn't Unicode-safe.
    [DllImport("__Internal")]
    private static extern void SendStringArray(
        [In, MarshalAs(UnmanagedType.LPArray, ArraySubType = UnmanagedType.LPUTF8Str)] string[] strings,
        int length);

    void Start()
    {
        // Pass strings to native function
        SendString("Hello from C#");
        SendString("七転び八起き");

        // Get a string from the native function. The runtime copies it into managed
        // memory and frees the native buffer for you.
        string managedStr = GetUnmanagedString();
        Debug.Log(managedStr);

        // Pass string arrays to native function
        string[] strings = new string[] { "one", "two", "three" };
        SendStringArray(strings, strings.Length);
    }
}

Примечание: Время выполнения скрипта ожидает, что управляемые строки будут неизменными. Несмотря на то, что вы можете написать неуправляемый код, изменяющий базовые данные, на которые ссылается строковая переменная, это может привести к появлению тонких и не столь тонких ошибок в вашей программе.

Unity.Collections пространство имён содержит несколько типов для работы со строками, хранящимися в неуправляемой памяти. Они позволяют избежать частого копирования строк и сборки мусора, характерных для класса System.String.

Следующие примеры демонстрируют несколько способов передачи строк, хранящихся в типах Unity.Collections, неуправляемым функциям (например, функция SendCollectionsString в предыдущем примере кода C/C++). Эти примеры опираются на указатели и требуют контекста unsafe.

using Unity.Collections;
using Unity.Collections.LowLevel.Unsafe;
using System;
using System.Runtime.InteropServices;
using System.Text;

public class CollectionStringExample
{
    // Pass a pointer to a Unity.Collections object containing a string
    [DllImport("__Internal")]
    private static extern void SendCollectionsString(IntPtr textData, int letterCount);

    public static void RunExamples()
    {
        unsafe // Unsafe because of use of pointers to Collections buffers
        {
            // Pass pointer to NativeText instance
            var nativeText = new NativeText("十人十色", Allocator.Temp);
            SendCollectionsString((IntPtr)nativeText.GetUnsafePtr(), nativeText.Length);
            nativeText.Dispose();

            // Pass pointer to FixedString instance
            var fixedStr = new FixedString128Bytes("Fixed string");
            SendCollectionsString((IntPtr)fixedStr.GetUnsafePtr(), fixedStr.Length);

            // Pass pointer to NativeArray containing a string of UTF8 characters
            byte[] utf8Bytes = Encoding.UTF8.GetBytes("雨降って地固まる"); // Managed strings are UTF-16
            var nativeArray = new NativeArray<byte>(utf8Bytes, Allocator.Temp);
            void* unsafePtr = nativeArray.GetUnsafePtr();
            SendCollectionsString((IntPtr)unsafePtr, nativeArray.Length);
            nativeArray.Dispose();
        }
    }
}

Примечание: Измените DllImport, чтобы использовать имя библиотеки, если вы используете предкомпилированные, динамически связанные библиотеки. Используйте специальную строку, __Internal для плагинов исходного кода и статически связанных библиотек. Дополнительные сведения см. в атрибуте DllImport.