本主题介绍了如何访问非托管函数,并介绍了几个属性字段,这些字段用于在托管代码中标注方法定义。 有关演示如何构造要用于平台调用的基于 .NET 的声明的示例,请参阅用平台调用封送数据。
在可以从托管代码访问非托管 DLL 函数之前,需要知道函数的名称以及导出它的 DLL 的名称。 利用此信息,可以开始为 DLL 中实现的非托管函数编写托管定义。 此外,可调整平台调用创建函数以及将数据封送到函数和从中封送数据的方法。
注释
使用 Windows API 函数分配字符串后,可以通过诸如 LocalFree
的方法释放该字符串。 平台调用以不同的方式处理此类参数。 对于平台调用,请使参数成为 IntPtr
类型而不是 String
类型。 使用System.Runtime.InteropServices.Marshal类提供的方法手动将类型转换为字符串,并手动释放它。
声明基本知识
如以下示例所示,非托管函数的托管定义依赖于语言。 有关更完整的代码示例,请参阅 平台调用示例。
Friend Class NativeMethods
Friend Declare Auto Function MessageBox Lib "user32.dll" (
ByVal hWnd As IntPtr,
ByVal lpText As String,
ByVal lpCaption As String,
ByVal uType As UInteger) As Integer
End Class
若要将 DllImportAttribute.BestFitMapping、DllImportAttribute.CallingConvention、DllImportAttribute.ExactSpelling、DllImportAttribute.PreserveSigDllImportAttribute.SetLastError或DllImportAttribute.ThrowOnUnmappableChar字段应用于 Visual Basic 声明,必须使用DllImportAttribute属性而不是Declare
语句。
Imports System.Runtime.InteropServices
Friend Class NativeMethods
<DllImport("user32.dll", CharSet:=CharSet.Auto)>
Friend Shared Function MessageBox(
ByVal hWnd As IntPtr,
ByVal lpText As String,
ByVal lpCaption As String,
ByVal uType As UInteger) As Integer
End Function
End Class
using System;
using System.Runtime.InteropServices;
internal static class NativeMethods
{
[DllImport("user32.dll")]
internal static extern int MessageBox(
IntPtr hWnd, string lpText, string lpCaption, uint uType);
}
using namespace System;
using namespace System::Runtime::InteropServices;
[DllImport("user32.dll")]
extern "C" int MessageBox(
IntPtr hWnd, String* lpText, String* lpCaption, unsigned int uType);
调整定义
无论是否显式设置它们,属性字段都在定义托管代码的行为。 平台调用根据程序集中作为元数据存在的各个字段上设置的默认值进行操作。 可以通过调整一个或多个字段的值来更改此默认行为。 在许多情况下,你可以使用 DllImportAttribute 来设置一个值。
下表列出了与平台调用相关的一组完整的属性字段。 对于每个字段,该表包括默认值,以及有关如何使用这些字段定义非托管 DLL 函数的信息的链接。
领域 | DESCRIPTION |
---|---|
BestFitMapping | 启用或禁用最佳映射。 |
CallingConvention | 指定要在传递方法参数中使用的调用约定。 默认值 WinAPI 是适用于 32 位 Intel 平台的默认值,对应于 __stdcall 。 |
CharSet | 控件名称重整以及应将字符串参数封送到函数的方法。 默认值为 CharSet.Ansi 。 |
EntryPoint | 指定要调用的 DLL 入口点。 |
ExactSpelling | 控制是否应修改入口点以对应于字符集。 默认值因编程语言而异。 |
PreserveSig | 控制是否应将托管方法签名转换为非托管签名,使其返回HRESULT,并为返回值增加一个额外的 [out] 和 [retval] 参数。 默认值为 true (不应转换签名)。 |
SetLastError | 使调用方能够使用 Marshal.GetLastWin32Error API 函数来确定在执行方法时是否发生了错误。 在 Visual Basic 中,默认值为 true ;在 C# 和 C++ 中,默认值为 false 。 |
ThrowOnUnmappableChar | 控制在转换为 ANSI "?" 字符的非托管 Unicode 字符上引发的异常。 |
有关详细参考信息,请参阅 DllImportAttribute。
平台调用安全注意事项
Assert
枚举的 Deny
、PermitOnly
和 SecurityAction 成员被称为堆栈审核修饰符。 如果这些成员用作平台调用声明和 COM 接口定义语言(IDL)语句上的声明性属性,则忽略这些成员。
平台调用示例
本部分中的平台调用样本示例演示了将 RegistryPermission
属性与堆栈遍历修饰符结合使用。
在以下示例中,SecurityActionAssert
Deny
和 PermitOnly
修饰符被忽略。
[DllImport("MyClass.dll", EntryPoint = "CallRegistryPermission")]
[RegistryPermission(SecurityAction.Assert, Unrestricted = true)]
private static extern bool CallRegistryPermissionAssert();
[DllImport("MyClass.dll", EntryPoint = "CallRegistryPermission")]
[RegistryPermission(SecurityAction.Deny, Unrestricted = true)]
private static extern bool CallRegistryPermissionDeny();
[DllImport("MyClass.dll", EntryPoint = "CallRegistryPermission")]
[RegistryPermission(SecurityAction.PermitOnly, Unrestricted = true)]
private static extern bool CallRegistryPermissionDeny();
但是,在下面的示例中,Demand
修饰符是被接受的。
[DllImport("MyClass.dll", EntryPoint = "CallRegistryPermission")]
[RegistryPermission(SecurityAction.Demand, Unrestricted = true)]
private static extern bool CallRegistryPermissionDeny();
SecurityAction 修饰符如果放置在包含平台调用的类上,则它们可以正常工作。
[RegistryPermission(SecurityAction.Demand, Unrestricted = true)]
public ref class PInvokeWrapper
{
public:
[DllImport("MyClass.dll", EntryPoint = "CallRegistryPermission")]
private static extern bool CallRegistryPermissionDeny();
};
[RegistryPermission(SecurityAction.Demand, Unrestricted = true)]
class PInvokeWrapper
{
[DllImport("MyClass.dll", EntryPoint = "CallRegistryPermission")]
private static extern bool CallRegistryPermissionDeny();
}
SecurityAction 修饰符在嵌套场景中仍能正常工作,在这种情况下,它们被放置在平台调用的调用方上:
{
public ref class PInvokeWrapper
public:
[DllImport("MyClass.dll", EntryPoint = "CallRegistryPermission")]
private static extern bool CallRegistryPermissionDeny();
[RegistryPermission(SecurityAction.Demand, Unrestricted = true)]
public static bool CallRegistryPermission()
{
return CallRegistryPermissionInternal();
}
};
class PInvokeScenario
{
[DllImport("MyClass.dll", EntryPoint = "CallRegistryPermission")]
private static extern bool CallRegistryPermissionInternal();
[RegistryPermission(SecurityAction.Assert, Unrestricted = true)]
public static bool CallRegistryPermission()
{
return CallRegistryPermissionInternal();
}
}
COM 互操作示例
本节中的 COM 互操作示例演示了将 RegistryPermission
属性与堆栈遍历修饰符一起使用。
以下 COM 互操作接口声明忽略 Assert
、Deny
和 PermitOnly
修饰符,这与上一部分中的平台调用示例类似。
[ComImport, Guid("12345678-43E6-43c9-9A13-47F40B338DE0")]
interface IAssertStubsItf
{
[RegistryPermission(SecurityAction.Assert, Unrestricted = true)]
bool CallRegistryPermission();
[FileIOPermission(SecurityAction.Assert, Unrestricted = true)]
bool CallFileIoPermission();
}
[ComImport, Guid("12345678-43E6-43c9-9A13-47F40B338DE0")]
interface IDenyStubsItf
{
[RegistryPermission(SecurityAction.Deny, Unrestricted = true)]
bool CallRegistryPermission();
[FileIOPermission(SecurityAction.Deny, Unrestricted = true)]
bool CallFileIoPermission();
}
[ComImport, Guid("12345678-43E6-43c9-9A13-47F40B338DE0")]
interface IAssertStubsItf
{
[RegistryPermission(SecurityAction.PermitOnly, Unrestricted = true)]
bool CallRegistryPermission();
[FileIOPermission(SecurityAction.PermitOnly, Unrestricted = true)]
bool CallFileIoPermission();
}
此外,Demand
修饰符在 COM 互操作接口声明的场景中不被接收,如以下示例所示。
[ComImport, Guid("12345678-43E6-43c9-9A13-47F40B338DE0")]
interface IDemandStubsItf
{
[RegistryPermission(SecurityAction.Demand, Unrestricted = true)]
bool CallRegistryPermission();
[FileIOPermission(SecurityAction.Demand, Unrestricted = true)]
bool CallFileIoPermission();
}