Pass strings between C# and native code
Marshal strings and string arrays between C# and native code, and pass Unity.Collections strings without extra copies.
読み終わるまでの所要時間 5 分最終更新 1日前
When you pass a string between managed and unmanaged code, the default marshalling behavior usually makes a copy of the string and converts it between the .NET format and the default platform format, if necessary. You can change this behavior with (Microsoft). For more information, refer to Strings used in platform invoke (Microsoft).
MarshalAsAttributePass managed strings
The following C++ code defines unmanaged functions that receive and return string data:
#include <stdio.h>#include <stdlib.h>#include <string.h>#if defined(_WIN32)#include <objbase.h>#endif#if defined(_WIN32)void* Allocate(size_t size){ // On Windows, the runtime uses the COM allocator to free memory that native code returns. return CoTaskMemAlloc(size);}#elsevoid* Allocate(size_t size){ // On other platforms, the runtime uses the C allocator to free memory that native code returns. return malloc(size);}#endifchar* 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) { // The output appears in the Player log file. printf("%s\n", message); fflush(stdout); } const char* GetUnmanagedString() { // The runtime frees this buffer with the matching allocator. return AllocateAndCopyString("Hello from C\u002B\u002B"); } 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); }}
The following C# example uses these unmanaged functions to do the following:
- Pass a string to an unmanaged function.
- Return a string from an unmanaged function.
- Pass a string array to an unmanaged function.
using UnityEngine;using System;using System.Runtime.InteropServices;public class StringMarshalExample : MonoBehaviour{ // Pass a string parameter. Marshal as UTF-8 to preserve non-ASCII text. // The default string marshalling isn't Unicode-safe on every platform [DllImport("__Internal")] private static extern void SendString([MarshalAs(UnmanagedType.LPUTF8Str)] string message); // Return a string from native code. When you marshal the return value as LPUTF8Str, // the runtime copies the string into managed memory and frees the native buffer // with the default allocator of the platform: CoTaskMemFree on Windows and free elsewhere. // This matches the Allocate() function in the native code. [DllImport("__Internal")] [return: MarshalAs(UnmanagedType.LPUTF8Str)] private static extern string GetUnmanagedString(); // Pass a string array parameter. Marshal each element as UTF-8 for the same reason as SendString. // Without ArraySubType, the elements use the ANSI marshalling of the platform, 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 the 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. string managedStr = GetUnmanagedString(); Debug.Log(managedStr); // Pass a string array to the native function. string[] strings = new string[] { "one", "two", "three" }; SendStringArray(strings, strings.Length); }}
Print non-ASCII text from unmanaged code
When you marshal strings as , as the previous example does, native code receives correct UTF-8 bytes. However, doesn't display UTF-8 text the same way on every platform. On platforms where the console and C runtime default to UTF-8, such as macOS, Linux, Android, and iOS, the calls in the example display non-ASCII text correctly. On Windows, the console and C runtime default to a non-UTF-8 code page, so output of UTF-8 text can appear garbled even though the marshalled data is correct.
LPUTF8StrprintfprintfprintfTo print non-ASCII text on Windows, use one of the following approaches:
- Configure the console for UTF-8, for example with .
SetConsoleOutputCP(CP_UTF8) - Convert the UTF-8 bytes to UTF-16 with and print them with the wide-character APIs.
MultiByteToWideChar
Pass strings stored in unmanaged collection types
The Unity.Collections namespace contains a few types that you can use to work with strings stored in unmanaged memory. Use these types to avoid the frequent string copy and garbage collection that occurs with the System.String class.
The following examples demonstrate a few ways that you can pass strings stored in Unity.Collections types to unmanaged functions (such as the function in the previous C/C++ code example). These examples rely on pointers and require an context.
SendCollectionsStringunsafeusing 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 that contains a string. [DllImport("__Internal")] private static extern void SendCollectionsString(IntPtr textData, int letterCount); public static void RunExamples() { // Use an unsafe block because the code uses pointers to Collections buffers. unsafe { // Pass a pointer to a NativeText instance. var nativeText = new NativeText("十人十色", Allocator.Temp); SendCollectionsString((IntPtr)nativeText.GetUnsafePtr(), nativeText.Length); nativeText.Dispose(); // Pass a pointer to a FixedString instance. var fixedStr = new FixedString128Bytes("Fixed string"); SendCollectionsString((IntPtr)fixedStr.GetUnsafePtr(), fixedStr.Length); // Pass a pointer to a NativeArray that contains a string of UTF-8 characters. // Managed strings are UTF-16, so convert the string to UTF-8 bytes first. byte[] utf8Bytes = Encoding.UTF8.GetBytes("雨降って地固まる"); var nativeArray = new NativeArray<byte>(utf8Bytes, Allocator.Temp); void* unsafePtr = nativeArray.GetUnsafePtr(); SendCollectionsString((IntPtr)unsafePtr, nativeArray.Length); nativeArray.Dispose(); } }}