文字列に対する既定のマーシャリング

System.StringSystem.Text.StringBuilder クラスのマーシャリング動作は類似しています。

文字列は、COM スタイル BSTR 型または null で終わる文字列 (null 文字で終わる文字配列) としてマーシャリングされます。 文字列内の文字は、Unicode (Windows システムでの既定値) または ANSI としてマーシャリングすることができます。

インターフェイスで使用される文字列

次の表は、アンマネージド コードへのメソッド引数としてマーシャリングするときの、文字列データ型のマーシャリングのオプションを示しています。 MarshalAsAttribute 属性は、COM インターフェイスへの文字列をマーシャリングする UnmanagedType 列挙値を提供します。

列挙型 アンマネージ形式の説明
UnmanagedType.BStr (既定値) 長さと Unicode 文字がプレフィックスされた COM スタイル BSTR
UnmanagedType.LPStr ANSI 文字の null で終わる配列へのポインター。
UnmanagedType.LPWStr Unicode 文字の null で終わる配列へのポインター。

この表は String に適用されます。 StringBuilder の場合、許可される唯一のオプションは UnmanagedType.LPStrUnmanagedType.LPWStr です。

以下の例では、IStringWorker インターフェイスで宣言された文字列を示します。

public interface IStringWorker
{
    void PassString1(string s);
    void PassString2([MarshalAs(UnmanagedType.BStr)] string s);
    void PassString3([MarshalAs(UnmanagedType.LPStr)] string s);
    void PassString4([MarshalAs(UnmanagedType.LPWStr)] string s);
    void PassStringRef1(ref string s);
    void PassStringRef2([MarshalAs(UnmanagedType.BStr)] ref string s);
    void PassStringRef3([MarshalAs(UnmanagedType.LPStr)] ref string s);
    void PassStringRef4([MarshalAs(UnmanagedType.LPWStr)] ref string s);
}
Public Interface IStringWorker
    Sub PassString1(s As String)
    Sub PassString2(<MarshalAs(UnmanagedType.BStr)> s As String)
    Sub PassString3(<MarshalAs(UnmanagedType.LPStr)> s As String)
    Sub PassString4(<MarshalAs(UnmanagedType.LPWStr)> s As String)
    Sub PassStringRef1(ByRef s As String)
    Sub PassStringRef2(<MarshalAs(UnmanagedType.BStr)> ByRef s As String)
    Sub PassStringRef3(<MarshalAs(UnmanagedType.LPStr)> ByRef s As String)
    Sub PassStringRef4(<MarshalAs(UnmanagedType.LPWStr)> ByRef s As String)
End Interface

以下の例では、タイプ ライブラリに記述された対応するインターフェイスを示します。

interface IStringWorker : IDispatch
{
    HRESULT PassString1([in] BSTR s);
    HRESULT PassString2([in] BSTR s);
    HRESULT PassString3([in] LPStr s);
    HRESULT PassString4([in] LPWStr s);
    HRESULT PassStringRef1([in, out] BSTR *s);
    HRESULT PassStringRef2([in, out] BSTR *s);
    HRESULT PassStringRef3([in, out] LPStr *s);
    HRESULT PassStringRef4([in, out] LPWStr *s);
};

プラットフォーム呼び出しで使用される文字列

CharSet が Unicode の場合、または文字列引数が明示的に [MarshalAs(UnmanagedType.LPWSTR)] とマークされていて、かつ文字列が (ref または out ではなく) 値で渡される場合、文字列は固定され、ネイティブ コードから直接使用されます。 そうではない場合、プラットフォーム呼び出しにより、文字列の引数は .NET Framework 形式 (Unicode) からプラットフォーム アンマネージ形式に変換され、コピーされます。 文字列は不変であり、呼び出しが戻るときに、アンマネージド メモリから元のマネージド メモリにコピーされることはありません。

ネイティブ コードがメモリの解放を担当するのは、文字列が参照渡しされ、新しい値が割り当てられる場合のみです。 それ以外の場合は、.NET ランタイムによってメモリが所有され、呼び出しの後に解放されます。

次の表は、文字列をプラットフォーム呼び出しのメソッド引数としてマーシャリングする際のマーシャリング オプションをリストしています。 MarshalAsAttribute 属性は、文字列をマーシャリングする UnmanagedType 列挙値を提供します。

列挙型 アンマネージ形式の説明
UnmanagedType.AnsiBStr 長さと ANSI 文字がプレフィックスされた COM スタイル BSTR
UnmanagedType.BStr 長さと Unicode 文字がプレフィックスされた COM スタイル BSTR
UnmanagedType.LPStr (既定値) ANSI 文字の null で終わる配列へのポインター。
UnmanagedType.LPTStr プラットフォーム依存文字の null で終わる配列へのポインター。
UnmanagedType.LPUTF8Str UTF-8 でエンコードされた文字の NULL で終わる配列へのポインター。
UnmanagedType.LPWStr Unicode 文字の null で終わる配列へのポインター。
UnmanagedType.TBStr 長さとプラットフォーム依存文字がプレフィックスされた COM スタイル BSTR
VBByRefStr Visual Basic で、アンマネージド コードの文字列を変更し、結果をマネージド コードに反映できるようにする値。 この値は、プラットフォーム呼び出しでだけサポートされます。 これは、ByVal 文字列に対する Visual Basic の既定値です。

この表は String に適用されます。 StringBuilder の場合、許可される唯一のオプションは LPStrLPTStr、および LPWStr です。

次の型定義は、プラットフォーム呼び出しで MarshalAsAttribute を使用するための正しい方法を示しています。

class StringLibAPI
{
    [DllImport("StringLib.dll")]
    public static extern void PassLPStr([MarshalAs(UnmanagedType.LPStr)] string s);
    [DllImport("StringLib.dll")]
    public static extern void PassLPWStr([MarshalAs(UnmanagedType.LPWStr)] string s);
    [DllImport("StringLib.dll")]
    public static extern void PassLPTStr([MarshalAs(UnmanagedType.LPTStr)] string s);
    [DllImport("StringLib.dll")]
    public static extern void PassLPUTF8Str([MarshalAs(UnmanagedType.LPUTF8Str)] string s);
    [DllImport("StringLib.dll")]
    public static extern void PassBStr([MarshalAs(UnmanagedType.BStr)] string s);
    [DllImport("StringLib.dll")]
    public static extern void PassAnsiBStr([MarshalAs(UnmanagedType.AnsiBStr)] string s);
    [DllImport("StringLib.dll")]
    public static extern void PassTBStr([MarshalAs(UnmanagedType.TBStr)] string s);
}
Class StringLibAPI
    Public Declare Auto Sub PassLPStr Lib "StringLib.dll" (
        <MarshalAs(UnmanagedType.LPStr)> s As String)
    Public Declare Auto Sub PassLPWStr Lib "StringLib.dll" (
        <MarshalAs(UnmanagedType.LPWStr)> s As String)
    Public Declare Auto Sub PassLPTStr Lib "StringLib.dll" (
        <MarshalAs(UnmanagedType.LPTStr)> s As String)
    Public Declare Auto Sub PassLPUTF8Str Lib "StringLib.dll" (
        <MarshalAs(UnmanagedType.LPUTF8Str)> s As String)
    Public Declare Auto Sub PassBStr Lib "StringLib.dll" (
        <MarshalAs(UnmanagedType.BStr)> s As String)
    Public Declare Auto Sub PassAnsiBStr Lib "StringLib.dll" (
        <MarshalAs(UnmanagedType.AnsiBStr)> s As String)
    Public Declare Auto Sub PassTBStr Lib "StringLib.dll" (
        <MarshalAs(UnmanagedType.TBStr)> s As String)
End Class

構造体で使用される文字列

文字列は構造体の有効なメンバーです。ただし、StringBuilder バッファーは構造体では無効です。 次の表は、型をフィールドとしてマーシャリングするときの、String データ型のマーシャリングのオプションを示しています。 MarshalAsAttribute 属性は、文字列をフィールドにマーシャリングする UnmanagedType 列挙値を提供します。

列挙型 アンマネージ形式の説明
UnmanagedType.BStr 長さと Unicode 文字がプレフィックスされた COM スタイル BSTR
UnmanagedType.LPStr (既定値) ANSI 文字の null で終わる配列へのポインター。
UnmanagedType.LPTStr プラットフォーム依存文字の null で終わる配列へのポインター。
UnmanagedType.LPUTF8Str UTF-8 でエンコードされた文字の NULL で終わる配列へのポインター。
UnmanagedType.LPWStr Unicode 文字の null で終わる配列へのポインター。
UnmanagedType.ByValTStr 固定長の文字配列。配列の型は、包含構造体の文字セットによって決まります。

ByValTStr 型は、構造体に定義されているインライン固定長文字配列で使用します。 その他の型は、文字列へのポインターを含む構造体に含まれている文字列参照に適用されます。

包含構造体に適用される StructLayoutAttributeCharSet 引数によって、構造体内の文字列の文字形式が決まります。 以下の構造体の例には、文字列参照とインライン文字列、そして ANSI、Unicode、およびプラットフォーム依存文字が含まれています。 タイプ ライブラリ内のこれらの構造体の表現を次の C++ コードに示します。

struct StringInfoA
{
    char *  f1;
    char    f2[256];
};

struct StringInfoW
{
    WCHAR * f1;
    WCHAR   f2[256];
    BSTR    f3;
};

struct StringInfoT
{
    TCHAR * f1;
    TCHAR   f2[256];
};

次の例は、MarshalAsAttribute を使用して、同一の構造体を複数の異なる形式で定義する方法を示しています。

[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
struct StringInfoA
{
    [MarshalAs(UnmanagedType.LPStr)] public string f1;
    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 256)] public string f2;
}

[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
struct StringInfoW
{
    [MarshalAs(UnmanagedType.LPWStr)] public string f1;
    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 256)] public string f2;
    [MarshalAs(UnmanagedType.BStr)] public string f3;
}

[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
struct StringInfoT
{
    [MarshalAs(UnmanagedType.LPTStr)] public string f1;
    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 256)] public string f2;
}
<StructLayout(LayoutKind.Sequential, CharSet := CharSet.Ansi)> _
Structure StringInfoA
    <MarshalAs(UnmanagedType.LPStr)> Public f1 As String
    <MarshalAs(UnmanagedType.ByValTStr, SizeConst := 256)> _
    Public f2 As String
End Structure

<StructLayout(LayoutKind.Sequential, CharSet := CharSet.Unicode)> _
Structure StringInfoW
    <MarshalAs(UnmanagedType.LPWStr)> Public f1 As String
    <MarshalAs(UnmanagedType.ByValTStr, SizeConst := 256)> _
    Public f2 As String
<MarshalAs(UnmanagedType.BStr)> Public f3 As String
End Structure

<StructLayout(LayoutKind.Sequential, CharSet := CharSet.Auto)> _
Structure StringInfoT
    <MarshalAs(UnmanagedType.LPTStr)> Public f1 As String
    <MarshalAs(UnmanagedType.ByValTStr, SizeConst := 256)> _
    Public f2 As String
End Structure

固定長文字列バッファー

状況によっては、固定長の文字列バッファーを、操作するためにアンマネージ コードに渡す必要があります。 この場合、呼び出し先は渡されたバッファーの内容を修正できないので、単に文字列を渡すだけでは不十分です。 文字列が参照によって渡された場合でも、バッファーを特定のサイズに初期化する方法はありません。

解決方法は、期待されるエンコードに応じて、String ではなく、byte[] または char[] を引数として渡すことです。 [Out] でマークされた配列は、割り当てられた配列の容量を超えない限り、呼び出し先で逆参照したり変更したりすることができます。

たとえば、Windows GetWindowText API 関数 (winuser.h で定義されています) では、関数からウィンドウのテキストが出力される固定長の文字バッファーを呼び出し元が渡す必要があります。 lpString 引数は、呼び出し元が割り当てたサイズ nMaxCount のバッファーを指しています。 呼び出し元は、バッファーを割り当てて、nMaxCount 引数を割り当てられたバッファーのサイズに設定することが期待されています。 次の例は、winuser.h で定義されている GetWindowText 関数宣言を示しています。

int GetWindowText(
    HWND hWnd,        // Handle to window or control.
    LPTStr lpString,  // Text buffer.
    int nMaxCount     // Maximum number of characters to copy.
);

char[] は、呼び出し先によって逆参照して変更することができます。 次のコード例は、ArrayPool<char> を使って char[] を事前に割り当てる方法を示しています。

using System;
using System.Buffers;
using System.Runtime.InteropServices;

internal static class NativeMethods
{
    [DllImport("User32.dll", CharSet = CharSet.Unicode)]
    public static extern void GetWindowText(IntPtr hWnd, [Out] char[] lpString, int nMaxCount);
}

public class Window
{
    internal IntPtr h;        // Internal handle to Window.
    public string GetText()
    {
        char[] buffer = ArrayPool<char>.Shared.Rent(256 + 1);
        NativeMethods.GetWindowText(h, buffer, buffer.Length);
        return new string(buffer);
    }
}
Imports System
Imports System.Buffers
Imports System.Runtime.InteropServices

Friend Class NativeMethods
    Public Declare Auto Sub GetWindowText Lib "User32.dll" _
        (hWnd As IntPtr, <Out> lpString() As Char, nMaxCount As Integer)
End Class

Public Class Window
    Friend h As IntPtr ' Friend handle to Window.
    Public Function GetText() As String
        Dim buffer() As Char = ArrayPool(Of Char).Shared.Rent(256 + 1)
        NativeMethods.GetWindowText(h, buffer, buffer.Length)
        Return New String(buffer)
   End Function
End Class

もう 1 つの解決方法は、String ではなく StringBuilder を引数として渡すことです。 呼び出し先では、StringBuilder の容量を超えない範囲で、StringBuilder のマーシャリング時に作成されたバッファーを逆参照したり変更したりすることができます。 また、固定長に初期化することもできます。 たとえば、StringBuilder バッファーを初期化してその容量を N にする場合、マーシャラーは (N+1) 文字のサイズのバッファーを提供します。 +1 は、アンマネージ文字列に null 終了文字があることをしめしています。StringBuilder にはそれがありません。

注意

一般に、パフォーマンスを気にしている場合は、StringBuilder 引数の受け渡しはお勧めしません。 詳細については、「文字列パラメーター」を参照してください。

関連項目