C# 程序员参考--平台调用教程
引用地址:http://www.xici.net/b231526/d37573244.htm [复制│超文本复制]
字符串的默认封送处理
更新:2007 年 11 月
http://msdn.microsoft.com/zh-cn/library/s9ts558h.aspx
System..::.String 和 System.Text..::.StringBuilder 类都有相似的封送处理行为。
字符串作为 COM 样式的 BSTR 类型封送,或者作为以 null 引用(在 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 指向以 null 终止的 ANSI 字符数组的指针。
UnmanagedType.LPWStr 指向以 null 终止的 Unicode 字符数组的指针。
此表适用于字符串。但是,对于 StringBuilder ,唯一允许的选项为 UnmanagedType.LPStr 和 UnmanagedType.LPWStr。
平台调用复制字符串参数,并从 .NET Framework 格式 (Unicode) 转换为平台非托管格式。字符串是不可变的,在调用返回时不会从非托管内存复制回托管内存。
下表列出了在字符串被作为对平台调用进行的调用的方法参数封送时的封送处理选项。MarshalAsAttribute 属性提供了若干个 UnmanagedType 枚举值来封送字符串。
枚举类型 非托管格式的说明
UnmanagedType.AnsiBStr 具有预设长度并包含 ANSI 字符的 COM 样式的 BSTR。
UnmanagedType.BStr 具有预设长度并包含 Unicode 字符的 COM 样式的 BSTR。
UnmanagedType.LPStr 指向以 null 终止的 ANSI 字符数组的指针。
UnmanagedType.LPTStr(默认值) 指向以 null 终止的平台相关的字符数组的指针。
UnmanagedType.LPWStr 指向以 null 终止的 Unicode 字符数组的指针。
UnmanagedType.TBStr 具有预设长度并包含平台相关字符的 COM 样式的 BSTR。
VBByRefStr 一个值,该值使 Visual Basic .NET 能够更改非托管代码中的字符串,并使结果在托管代码中反 映出来。该值仅对平台调用受支持。
此表适用于字符串。但是,对于 StringBuilder ,唯一允许的选项为 LPStr、LPTStr 和 LPWStr。
字符串是结构的有效成员;但是,StringBuilder 缓冲区在结构中是无效的。下表显示当字符串数据类型被作为字段封送时该类型的封送处理选项。MarshalAsAttribute 属性提供了若干个 UnmanagedType 枚举值,以便将字符串封送到字段。
枚举类型 非托管格式的说明
UnmanagedType.BStr 具有预设长度并包含 Unicode 字符的 COM 样式的 BSTR。
UnmanagedType.LPStr 指向以 null 终止的 ANSI 字符数组的指针。
UnmanagedType.LPTStr 指向以 null 终止的平台相关的字符数组的指针。
UnmanagedType.LPWStr 指向以 null 终止的 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 属性以不同格式定义同一结构。
[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 初始化为固定长度。
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();
}
}
平台调用服务 (PInvoke) 允许托管代码调用在 DLL 中实现的非托管函数。
本教程说明使用什么方法才能从 C# 调用非托管 DLL 函数。该教程所讨论的属性允许您调用这些函数并使数据类型得到正确封送。
教程
C# 代码有以下两种可以直接调用非托管代码的方法:
直接调用从 DLL 导出的函数。
调用 COM 对象上的接口方法(有关更多信息,请参见 COM Interop 第一部分:C# 客户端教程)。
对于这两种技术,都必须向 C# 编译器提供非托管函数的声明,并且还可能需要向 C# 编译器提供如何封送与非托管代码之间传递的参数和返回值的说明。
该教程由下列主题组成:
直接从 C# 调用 DLL 导出
默认封送处理和为非托管方法的参数指定自定义封送处理
为用户定义的结构指定自定义封送处理
注册回调方法
该教程包括下列示例:
示例 1 使用 DllImport
示例 2 重写默认封送处理
示例 3 指定自定义封送处理
直接从 C# 调用 DLL 导出
若要声明一个方法使其具有来自 DLL 导出的实现,请执行下列操作:
使用 C# 关键字 static 和 extern 声明方法。
将 DllImport 属性附加到该方法。DllImport 属性允许您指定包含该方法的 DLL 的名称。通常的做法是用与导出的方法相同的名称命名 C# 方法,但也可以对 C# 方法使用不同的名称。
还可以为方法的参数和返回值指定自定义封送处理信息,这将重写 .NET Framework 的默认封送处理。
示例 1
本示例显示如何使用 DllImport 属性通过调用 msvcrt.dll 中的 puts 输出消息。
// PInvokeTest.cs
using System;
using System.Runtime.InteropServices;
class PlatformInvokeTest
{
[DllImport("msvcrt.dll")]
public static extern int puts(string c);
[DllImport("msvcrt.dll")]
internal static extern int _flushall();
public static void Main()
{
puts("Test");
_flushall();
}
}
输出
Test
代码讨论
前面的示例显示了声明在非托管 DLL 中实现的 C# 方法的最低要求。PlatformInvokeTest.puts 方法用 static 和 extern 修饰符声明并且具有 DllImport 属性,该属性使用默认名称 puts 通知编译器此实现来自 msvcrt.dll。若要对 C# 方法使用不同的名称(如 putstring),则必须在 DllImport 属性中使用 EntryPoint 选项,如下所示:
[DllImport("msvcrt.dll", EntryPoint="puts")]
有关 DllImport 属性的语法的更多信息,请参见 DllImportAttribute 类。
默认封送�
评论0