System.String 和 System.StringBuilder 类都有相似的封送处理行为。它们之间的主要差异是字符串是不可变的,而 StringBuilder 缓冲区的内容则可以由被调用方更改并复制回调用方。
字符串作为 COM 样式的 BSTR 类型封送,或者作为以空引用(在 Visual Basic 中为 Nothing)终止的字符数组结束。字符串内的字符可以作为 Unicode 或 ANSI 封送,或以平台相关的方式封送(在 Microsoft Windows NT、Windows 2000 和 Windows XP 上为 Unicode;在 Windows 98 和 Windows Millennium Edition 即 Windows Me 上为 ANSI)。
本主题提供下列有关封送字符串类型的信息:
下表显示字符串数据类型被作为方法参数封送到非托管代码时的封送处理选项。MarshalAsAttribute 属性提供了若干个 UnmanagedType 枚举值以便将字符串封送到 COM 接口。
枚举类型 | 非托管格式的说明 |
---|---|
UnmanagedType.BStr (默认) | 具有预设长度并包含 Unicode 字符的 COM 样式 BSTR。 |
UnmanagedType.LPStr | 指向 ANSI 字符的空终止数组的指针。 |
UnmanagedType.LPWStr | 指向 Unicode 字符的空终止数组的指针。 |
下面的示例显示 IStringWorker
接口中声明的字符串。
[C++] 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); );
下面的示例显示在类型库中描述的对应接口。
[...]
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);
);
平台调用中使用的字符串
平台调用复制字符串参数,并从 .NET Framework 格式 (Unicode) 转换为平台非托管格式。字符串是不可变的,在调用返回时不会从非托管内存复制回托管内存。
下表列出了在字符串被作为对平台调用进行的调用的方法参数封送时的封送处理选项。MarshalAsAttribute 属性提供了若干个 UnmanagedType 枚举值以封送字符串。
枚举类型 | 非托管格式的说明 |
---|---|
UnmanagedType.AnsiBStr | 具有预设长度并包含 ANSI 字符的 COM 样式 BSTR。 |
UnmanagedType.BStr (默认) | 具有预设长度并包含 Unicode 字符的 COM 样式 BSTR。 |
UnmanagedType.LPStr | 指向 ANSI 字符的空终止数组的指针。 |
UnmanagedType.LPTStr | 指向平台相关的字符的空终止数组的指针。 |
UnmanagedType.LPWStr | 指向 Unicode 字符的空终止数组的指针。 |
UnmanagedType.TBStr | 具有预设长度并包含平台相关字符的 COM 样式 BSTR。 |
下面的类型定义针对平台调用的调用显示 MarshalAsAttribute 的正确用法。
[Visual Basic] 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 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
[C#] 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 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);
}
结构中使用的字符串
字符串是结构的有效成员;但是,StringBuilder 缓冲区在结构中是无效的。下表显示当字符串数据类型被作为字段封送时该类型的封送处理选项。MarshalAsAttribute 属性提供了若干个 UnmanagedType 枚举值以便将字符串封送到字段。
枚举类型 | 非托管格式的说明 |
---|---|
UnmanagedType.BStr | 具有预设长度并包含 Unicode 字符的 COM 样式 BSTR。 |
UnmanagedType.LPStr | 指向 ANSI 字符的空终止数组的指针。 |
UnmanagedType.LPTStr | 指向平台相关的字符的空终止数组的指针。 |
UnmanagedType.LPWStr | 指向 Unicode 字符的空终止数组的指针。 |
UnmanagedType.ByValTStr | 定长的字符数组;数组的类型由包含数组的结构的字符集确定。 |
ByValTStr 类型用于出现在结构内的内联的定长字符数组。其他类型应用于包含指向字符串的指针的结构内所包含的字符串引用。
应用于包含结构的 StructLayoutAttribute 属性的 CharSet 参数确定结构中字符串的字符格式。下面的示例结构包含字符串引用和内联字符串,以及 ANSI、Unicode 和平台相关字符。
类型库表示形式
struct StringInfoA {
char * f1;
char f2[256];
};
struct StringInfoW {
WCHAR * f1;
WCHAR f2[256];
BSTR f3;
};
struct StringInfoT {
TCHAR * f1;
TCHAR f2[256];
};
下面的代码示例显示如何使用 MarshalAsAttribute 属性以不同格式定义同一结构。
[Visual Basic] <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
[C#] [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;
}
定长字符串缓冲区
在某些环境中,必须将定长的字符缓冲区传递到非托管代码中以进行操作。在这种情况下,只传递字符串不起作用,原因是被调用方无法修改传递的缓冲区的内容。即使字符串是通过引用传递的,仍然无法将缓冲区初始化为给定的大小。
解决方案是将 StringBuilder 缓冲区作为参数而不是字符串传递。StringBuilder 可以由被调用方取消引用和修改,条件是它不超过 StringBuilder 的容量。还可将其初始化为固定长度。例如,如果将 StringBuilder 缓冲区初始化为容量为 N,则封送拆收器将提供大小为 (N+1) 个字符的缓冲区。这个 +1 说明非托管字符串具有 Null 结束符,而 StringBuilder 却没有。
例如,Microsoft Win32 API GetWindowText 函数(定义在 Windows.h 中)是必须传递到非托管代码中进行操作的定长字符缓冲区。LpString 指向大小为 nMaxCount 的由调用方分配的缓冲区。调用方应分配缓冲区,并将 nMaxCount 参数设置为所分配的缓冲区的大小。以下代码显示在 Windows.h 中定义的 GetWindowText 函数声明。
int GetWindowText(
HWND hWnd, // Handle to window or control.
LPTStr lpString, // Text buffer.
int nMaxCount // Maximum number of characters to copy.
);
StringBuilder 可以由被调用方取消引用和修改,条件是它不超过 StringBuilder 的容量。下面的代码示例说明可以如何将 StringBuilder 初始化为固定长度。
[Visual Basic] Public Class Win32API Public Declare Auto Sub GetWindowText Lib "User32.Dll" _ (h As Integer, s As StringBuilder, nMaxCount As Integer) End Class Public Class Window Friend h As Integer ' Friend handle to Window. Public Function GetText() As String Dim sb As New StringBuilder(256) Win32API.GetWindowText(h, sb, sb.Capacity + 1) Return sb.ToString() End Function End Class
[C#] public class Win32API {
[DllImport("User32.Dll")]
public static extern void GetWindowText(int h, StringBuilder s,
int nMaxCount);
}
public class Window {
internal int h; // Internal handle to Window.
public String GetText() {
StringBuilder sb = new StringBuilder(256);
Win32API.GetWindowText(h, sb, sb.Capacity + 1);
return sb.ToString();
}
}