字串的預設封送處理
System.String 和 System.Text.StringBuilder 類別都有類似的封送處理行為。
字串會做為 COM 樣式 BSTR
類型或以 Null 終止的字串 (以 null 字元結束的字元陣列) 進行封送處理。 字串內的字元可以做為 Unicode (Windows 系統上的預設值) 或 ANSI 進行封送處理。
在介面中使用的字串
下表顯示當字串資料類型做為方法引數封送處理至非受控程式碼時,字串資料類型的封送處理選項。 MarshalAsAttribute 屬性提供幾種 UnmanagedType 列舉值來封送處理字串到 COM 介面。
列舉類型 | Unmanaged 格式的描述 |
---|---|
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) 轉換為平台非授控格式。 字串為不可變的,在呼叫傳回時並不會從 Unmanaged 記憶體複製回 Managed 記憶體。
機器碼只會負責在字串藉傳址方式傳遞時釋放記憶體,且其會指派新的值。 否則,.NET 執行階段會擁有記憶體,並在呼叫之後將其釋出。
下表列出當做為平台叫用呼叫的方法引數進行封送處理時,字串的封送處理選項。 MarshalAsAttribute 屬性提供幾種 UnmanagedType 列舉值來封送處理字串。
列舉類型 | Unmanaged 格式的描述 |
---|---|
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 變更 Unmanaged 程式碼中的字串,並且讓結果反映在 Managed 程式碼中。 只有平台叫用支援這個值。 這是 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 列舉值來封送處理字串至欄位。
列舉類型 | Unmanaged 格式的描述 |
---|---|
UnmanagedType.BStr |
具有前置長度和 Unicode 字元的 COM 樣式 BSTR 。 |
UnmanagedType.LPStr (預設值) |
ANSI 字元之 Null 終端陣列的指標。 |
UnmanagedType.LPTStr |
平台相依字元之 Null 終端陣列的指標。 |
UnmanagedType.LPUTF8Str |
UTF-8 編碼字元之 Null 終端陣列的指標。 |
UnmanagedType.LPWStr |
Unicode 字元之 Null 終端陣列的指標 |
UnmanagedType.ByValTStr |
一個固定長度的字元陣列;該陣列的類型由包含結構的字元集決定。 |
ByValTStr
類型使用於出現在結構中的內嵌固定長度字元陣列。 其他類型適用於包含在結構內的字串參考,其中包含字串的指標。
套用至包含結構之 StructLayoutAttribute 的 CharSet
引數會決定在結構中字串的字元格式。 下列範例結構包含字串的參考和內嵌字串,以及 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
固定長度字串緩衝區
在某些情況下,固定長度的字元緩衝區必須傳遞至 Unmanaged 程式碼以進行操作。 在此情況下只傳遞字串無法運作,因為被呼叫端不能修改傳遞緩衝區的內容。 即使該字串以傳址方式傳遞,也沒有任何方式來初始化指定大小的緩衝區。
解決方案是根據預期的編碼方式傳遞 byte[]
或 char[]
,做為引數而不是 String。 以 [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
另一個解決方案為傳遞 StringBuilder做為引數而不是 String。 封送處理 StringBuilder
時建立的緩衝區可以進行取值,且由被呼叫者修改,前提是其不超過 StringBuilder
的容量。 它也可以初始化為固定長度。 例如,如果您將 StringBuilder
緩衝區初始化為 N
的容量,封送處理器會提供大小為 N
+1 個字元的緩衝區。 +1 代表 Unmanged 字串具有 null 結束字元,但 StringBuilder
沒有。
注意
一般而言,如果您擔心效能,則不建議傳遞 StringBuilder
引數。 如需詳細資訊,請參閱字串參數。