字串的預設封送處理

System.StringSystem.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.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)],且該字串是透過值 (不是 refout) 傳遞時,該字串會加以固定並由機器碼直接使用。 否則,平台叫用會複製字串引數,從 .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,唯一允許的選項為 LPStrLPTStrLPWStr

下列類型定義會顯示對於平台叫用呼叫要如何正確使用 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 類型使用於出現在結構中的內嵌固定長度字元陣列。 其他類型適用於包含在結構內的字串參考,其中包含字串的指標。

套用至包含結構之 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

固定長度字串緩衝區

在某些情況下,固定長度的字元緩衝區必須傳遞至 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 引數。 如需詳細資訊,請參閱字串參數

另請參閱