System.String和 System.Text.StringBuilder 類別都有類似的編組行為。
字串會封送為 COM 樣式 BSTR 類型或以 NULL 結束的字串(以 NULL 字元結尾的字元陣列)。 字串中的字元可以轉換為 Unicode(Windows 系統上的預設編碼)或 ANSI。
介面中使用的字串
下表顯示當字串資料類型作為方法參數封送到非受控代碼時的封送選項。 MarshalAsAttribute 屬性提供多個 UnmanagedType 列舉值,用於將字串封送至 COM 介面。
| 列舉型別 | Unmanaged 格式的描述 |
|---|---|
UnmanagedType.BStr (預設值) |
具有前置長度和 Unicode 字元的 COM 樣式 BSTR 。 |
UnmanagedType.LPStr |
指向以 Null 結尾的 ANSI 字元陣列的指標。 |
UnmanagedType.LPWStr |
以 null 終止的 Unicode 字元陣列的指標。 |
此表格適用於 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)] 且字串會以實際值(not ref 或 out) 傳遞時,字串會直接釘選並由原生程式碼直接使用。 否則,平台呼叫會複製字串參數,並將其從 .NET Framework 格式(Unicode)轉換成平台的非受控格式。 字串是不可變的,而且當呼叫傳回時,不會從 Unmanaged 記憶體複製回 Managed 記憶體。
原生程式代碼只負責在字串以傳址方式傳遞時釋放記憶體,並指派新的值。 否則,.NET 運行時會擁有記憶體,並在呼叫完成後釋放。
下表列出當字串作為平台調用方法的引數進行封送處理時的封送選項。 屬性 MarshalAsAttribute 提供數個 UnmanagedType 列舉值來封送處理字串。
| 列舉型別 | Unmanaged 格式的描述 |
|---|---|
UnmanagedType.AnsiBStr |
具有前置長度和 ANSI 字元的 COM 樣式 BSTR 。 |
UnmanagedType.BStr |
具有前置長度和 Unicode 字元的 COM 樣式 BSTR 。 |
UnmanagedType.LPStr (預設值) |
指向以 Null 結尾的 ANSI 字元陣列的指標。 |
UnmanagedType.LPTStr |
指向依平台而定的字符之以 Null 結尾的陣列的指標。 |
UnmanagedType.LPUTF8Str |
指向使用 UTF-8 編碼字元且以 Null 終止的陣列的指標。 |
UnmanagedType.LPWStr |
以 null 終止的 Unicode 字元陣列的指標。 |
UnmanagedType.TBStr |
具有前置長度和平臺相依字元的 COM 標準樣式 BSTR。 |
VBByRefStr |
值,可讓 Visual Basic 變更 Unmanaged 程式代碼中的字串,並讓結果反映在 Managed 程式代碼中。 只有平台調用才支援此值。 這是 Visual Basic 中字串 ByVal 的預設值。 |
此表格適用於 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 (預設值) |
指向以 Null 結尾的 ANSI 字元陣列的指標。 |
UnmanagedType.LPTStr |
指向依平台而定的字符之以 Null 結尾的陣列的指標。 |
UnmanagedType.LPUTF8Str |
指向使用 UTF-8 編碼字元且以 Null 終止的陣列的指標。 |
UnmanagedType.LPWStr |
以 null 終止的 Unicode 字元陣列的指標。 |
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 GetWindowText API 函式 (定義於 winuser.h) 需要呼叫端傳遞函式寫入視窗文字的固定長度字元緩衝區。
lpString 參數會指向大小為 nMaxCount 的呼叫端配置緩衝區。 呼叫端應該配置緩衝區,並將 自變數設定 nMaxCount 為已配置的緩衝區大小。 下列範例顯示 GetWindowTextwinuser.h 中所定義的函式宣告。
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 是考量到這個非受控字串有一個 null 終止符,而 StringBuilder 並沒有。
備註
一般而言,如果您擔心效能,不建議傳遞 StringBuilder 自變數。 如需詳細資訊,請參閱 字串參數。