Струны
При передаче строки между управляемым и неуправляемым кодом в большинстве случаев поведение сортировки по умолчанию создает копию строки и преобразует ее между управляемым и неуправляемым кодом. .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.