C#托管和非托管的资源(六)——平台调用

并不是Windows API调用的所有特性都可用于.NET Framework。旧的Windows API调用是这样,Windows10Windows Server2016中的新功能也是这样。也许开发人员会编写一些DLL,导出非托管的方法,在C#中使用它们。

要重用一个非托管库,其中不包含COM对象,只包含导出的功能,就可以使用平台调用(P/Invoke)。有了P/Invoke,CLR会加载DLL,其中包含应调用的函数,并编组参数。要使用非托管函数,首先必须确定导出的函数名。为此,可以使用dumpbin工具和/exports选项。例如,命令:dumpbin/exports c:\ windows\ system32\ kernel32. dll | more列出DLL kernel32. dll 中所有导出的函数。这个示例使用WindowsAPI函数CreateHardLink来创建到现有文件的硬链接。 使用此API调用,可以用几个文件名引用相同的文件,只要文件名在一个硬盘上即可。这个API调用不能用于. NET Framework 4. 5. 1,因此必须使用平台调用。为了调用本机函数,必须定义一个参数数量相同的C#外部方法,用非托管方法定义的参数类型必须用托管代码映射类型。在C++中,Windows API调用CreateHardLink有如下定义:

BOOL CreateHardLink( LPCTSTR lpFileName, LPCTSTR lpExistingFileName,

LPSECURITY_ ATTRIBUTES lpSecurityAttributes);

这个定义必须映射到.NET数据类型上。非托管代码的返回类型是BOOL;它仅映射到bool数据类型。LPCTSTR定义了一个指向const字符串的long指针。Windows API给数据类型使用Hungarian命名约定。LP是一个long指针,C是一个常量,STR是以null结尾的字符串。T把类型标志为泛型类型,根据编译器设置为32还是64位,该类型解析为LPCSTRANSI字符串)或LPWSTR(宽Unicode字符串)。C字符串映射到.NET类型为StringLPSECURITY_ATTRIBUTES是一个long指针,指向SECURITY_ATTRIBUTES类型的结构。因为可以把NULL传递给这个参数,所以把这种类型映射到IntPtr是可行的。该方法的C#声明必须用extern修饰符标记,因为在C#代码中,这个方法没有实现代码。相反,该方法的实现代码在DLL kernel32. dll中,它用属性[DllImport]引用。.NET声明CreateHardLink的返回类型是bool,本机方法CreateHardLink返回一个布尔值,所以需要一些额外的澄清。因为C++有不同的Boolean数据类型(例如,本机boolWindows定义的BOOL有不同的值),所以特性[MarshalAs]指定.NET类型bool应该映射为哪个本机类型:

[DllImport(" kernel32. dll", SetLastError=" true",EntryPoint=" CreateHardLink", CharSet= CharSet. Unicode)]

[return: MarshalAs( UnmanagedType. Bool)]

public static extern bool CreateHardLink( string newFileName, string existingFilename, IntPtr securityAttributes);

注意:网站http://www.pinvoke.net非常有助于从本机代码到托管代码的转换。

可以用[DllImport]特性指定的设置在下表中列出。

DLLIMPORT属性或字段

说明

EntryPoint

可以给函数的C#声明指定与非托管库不同的名称。非托管库中方法的名称在EntryPoint字段中定义

CallingConvention

根据编译器或用来编译非托管函数的编译器设置,可以使用不同的调用约定。调用约定定义了如何处理参数,把它们放在堆栈的什么地方。 可以设置一个可枚举的值,来定义调用约定。WindowsAPI在Windows操作系统上通常使用StdCall调用约定,在WindowsCE上使用Cdecl调用约定。把值设置为CallingConvention.Winapi,可让WindowsAPI用于Windows和WindowsCE环境

CharSet

字符串参数可以是ANSI或Unicode。通过CharSet设置,可以定义字符串的管理方式。用CharSet枚举定义的值有Ansi、Unicode和Auto.CharSet。Auto在WindowsNT平台上使用Unicode,在微软的旧操作系统上使用ANSI

SetLastError

如果非托管函数使用Windows API SetLastError设置一个错误,就可以把SetLastError字段设置为true。这样,就可以使用Marshal.GetLastWin32Error读取后面的错误号

为了使CreateHardLink方法更易于在.NET环境中使用,应该遵循如下规则:

●创建一个内部类NativeMethods,来包装平台调用的方法调用。

●创建一个公共类,给.NET应用程序提供本机方法的功能。

●使用安全特性来标记所需的安全。

在接下来的例子中,类FileUtility中的公共方法CreateHardLink可以由.NET应用程序使用。这个方法的文件名参数,与本机Windows API方法CreateHardLink的顺序相反。第一个参数是现有文件的名称,第二个参数是新的文件。这类似于框架中的其他类,如File.Copy。因为第三个参数用来传递新文件名的安全特性,此实现代码不使用它,所以公共方法只有两个参数。返回类型也改变了。它不通过返回false值来返回一个错误,而是抛出一个异常。如果出错,非托管方法CreateHardLink就用非托管API SetLastError设置错误号。要从.NET中读取这个值,[DllImport]字段SetLastError设置为true。在托管方法CreateHardLink中,错误号是通过调用Marshal.GetLastWin32Error读取的。要从这个号中创建一个错误消息,应使用System.ComponentModel名称空间中的Win32Exception类。这个类通过构造函数接受错误号,并返回一个本地化的错误消息。如果出错,就抛出IOException类型的异常,它有一个类型Win32Exception的内部异常。应用公共方法CreateHardLink的FileIOPermission特性,检查调用程序是否拥有必要的许可。

using System; using System. ComponentModel;

using System.IO;

using System.Runtime.InteropServices;

using System.Security;

using System.Security.Permissions;

namespace Rbsoft.ProCSharp.Interop

{

[SecurityCritical]

internal static class NativeMethods

{

[DllImport("kernel32.dll", SetLastError = true, EntryPoint = "CreateHardLinkW",CharSet = CharSet. Unicode)]

[return: MarshalAs( UnmanagedType. Bool)]

private static extern bool CreateHardLink( [In, MarshalAs( UnmanagedType. LPWStr)] string newFileName, [In, MarshalAs( UnmanagedType. LPWStr)] string existingFileName, IntPtr securityAttributes);

internal static void CreateHardLink( string oldFileName,

string newFileName)

{

if (! CreateHardLink( newFileName, oldFileName, IntPtr.Zero))

{

var ex = new Win32Exception( Marshal.GetLastWin32Error());

throw new IOException(ex.Message, ex);

}

}

}

public static class FileUtility

{

[FileIOPermission( SecurityAction. LinkDemand, Unrestricted = true)] public static void CreateHardLink( string oldFileName, string newFileName)

{

NativeMethods.CreateHardLink( oldFileName, newFileName);

}

}

}

现在可以使用这个类来轻松地创建硬链接。如果程序的第一个参数传递的文件不存在,就会得到一个异常,提示“系统无法找到指定的文件”。如果文件存在,就得到一个引用原始文件的新文件名。很容易验证它:在一个文件中改变文本,它就会出现在另一个文件中:

using PInvokeSampleLib;

using System.IO;

using static System.Console;

namespace PInvokeSample

{

public class Program

{

public static void Main( string[] args)

{

if (args.Length ! = 2)

{

WriteLine(" usage: PInvokeSample " + "existingfilename newfilename"); return;

}

try

{

FileUtility.CreateHardLink( args[ 0], args[ 1]);

}

catch (IOException ex)

{

WriteLine( ex. Message);

}

}

}

}

调用本地方法时,通常必须使用Windows句柄。Windows句柄是一个32位或64位值,根据句柄类型,不允许使用一些值。在.NET1.0中,句柄通常使用IntPtr结构,因为可以用这种结构设置每一个可能的32位值。然而,对于一些句柄类型,这会导致安全问题,可能还会出现线程竞态条件,在终结阶段泄露句柄。所以.NET2.0引入了SafeHandle类。SafeHandle类是一个抽象的基类,用于每个Windows句柄。Microsoft.Win32.SafeHandles名称空间里的派生类是SafeHandleZeroOrMinus-OneIsInvalid和SafeHandleMinusOneIsInvalid。顾名思义,这些类不接受无效的0或1值。进一步派生的句柄类型是SafeFileHandle、SafeWaitHandle、SafeNCryptHandle和SafePipeHandle,可以供特定的Windows API调用使用。

例如,为了映射 Windows API CreateFile,可以使用以下声明,返回一个SafeFileHandle。当然,通常可以使用.NET类File和FileInfo。

[DllImport(" Kernel32. dll", SetLastError = true, CharSet = CharSet. Unicode)] internal static extern SafeFileHandle CreateFile( string fileName, [MarshalAs( UnmanagedType. U4)] FileAccess fileAccess, [MarshalAs( UnmanagedType. U4)]FileShare fileShare, IntPtr securityAttributes, [MarshalAs( UnmanagedType. U4)] FileMode creationDisposition, int flags, SafeFileHandle template);

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

掌控自身命运

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值