System.StringクラスとSystem.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.LPStrとUnmanagedType.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の場合、使用できるオプションは、LPStr、LPTStr、および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型は、構造体内に表示されるインラインの固定長文字配列に使用されます。 その他の型は、文字列へのポインターを含む構造体内に含まれる文字列参照に適用されます。
格納構造体に適用されるCharSetのStructLayoutAttribute引数によって、構造体内の文字列の文字形式が決まります。 次の構造体の例には、文字列参照とインライン文字列のほか、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
固定長文字列バッファー
場合によっては、固定長文字バッファーをアンマネージ コードに渡して操作する必要があります。 この場合、呼び出し先は渡されたバッファーの内容を変更できないため、文字列を渡すだけでは機能しません。 文字列が参照渡しされた場合でも、バッファーを特定のサイズに初期化する方法はありません。
解決策は、byte[]の代わりに、予想されるエンコーディングに応じて、char[]またはStringを引数として渡すことです。
[Out]でマークされている配列は、割り当てられた配列の容量を超えていない場合、呼び出し先によって逆参照および変更できます。
たとえば、Windows lpString引数は、呼び出し元によって割り当てられたサイズnMaxCountバッファーを指します。 呼び出し元はバッファーを割り当て、 nMaxCount 引数を割り当てられたバッファーのサイズに設定することが想定されています。 次の例は、GetWindowText で定義されている関数の宣言を示しています。
int GetWindowText(
HWND hWnd, // Handle to window or control.
LPTStr lpString, // Text buffer.
int nMaxCount // Maximum number of characters to copy.
);
char[]は、呼び出し先によって逆参照および変更できます。 推奨される方法は、 ArrayPool<T> を使用して char[]をレンタルし、繰り返しヒープ割り当てを回避することです。 次のコード例は、このパターンを示しています。
using System;
using System.Buffers;
using System.Runtime.InteropServices;
internal static class NativeMethods
{
[DllImport("User32.dll", CharSet = CharSet.Unicode)]
public static extern int 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);
try
{
int length = NativeMethods.GetWindowText(h, buffer, buffer.Length);
return new string(buffer, 0, length);
}
finally
{
ArrayPool<char>.Shared.Return(buffer);
}
}
}
Imports System
Imports System.Buffers
Imports System.Runtime.InteropServices
Friend Class NativeMethods
Public Declare Auto Function GetWindowText Lib "User32.dll" _
(hWnd As IntPtr, <Out> lpString() As Char, nMaxCount As Integer) 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)
Try
Dim length As Integer = NativeMethods.GetWindowText(h, buffer, buffer.Length)
Return New String(buffer, 0, length)
Finally
ArrayPool(Of Char).Shared.Return(buffer)
End Try
End Function
End Class
また、StringBuilderではなく、Stringを渡すこともできます。
StringBuilderのマーシャリング時に作成されるバッファーは、StringBuilderの容量を超えない限り、呼び出し先によって逆参照および変更できます。 また、固定長に初期化することもできます。 たとえば、 StringBuilder バッファーを N の容量に初期化すると、マーシャラーはサイズ (N+1) 文字のバッファーを提供します。 +1 は、アンマネージ文字列に null ターミネータがあるのに対し、StringBuilder には含まれていないという事実を考慮に入れています。
注意事項
パフォーマンスが重要な場合は、パラメーター StringBuilder 避けてください。
StringBuilderを常にマーシャリングすると、ネイティブ バッファー コピーが作成されます。 ネイティブ コードから文字列を取得する一般的な呼び出しでは、次の 4 つの割り当てが発生する可能性があります。
- マネージド
StringBuilderバッファー。 - マーシャリング中に割り当てられたネイティブ バッファー。
-
[Out]場合、ネイティブ バッファーの内容は、新しく割り当てられたマネージド配列にコピーされます。 -
ToString()によってstringが割り当てられる。
呼び出し間で同じ StringBuilder を再利用すると、1 つの割り当てのみが保存されます。
ArrayPool<char>からレンタルされた文字バッファーを使用する方がはるかに効率的です。これにより、後続の呼び出しがToString()の割り当てだけに減ります。
さらに、 StringBuilder 容量には、相互運用が常に考慮する非表示の null ターミネータは含 まれません 。 ほとんどの API は null を 含む バッファーのサイズを必要とするため、これは一般的な間違いです。 これにより、無駄な割り当てや不要な割り当てが発生する可能性があり、コピーを最小限に抑えるためにランタイムが StringBuilder マーシャリングを最適化できなくなります。
詳細については、「 文字列パラメーター と CA1838: P/Invoke のパラメーター StringBuilder 避ける」を参照してください。
こちらも参照ください
.NET