美文网首页
C# 调用非托管DLL

C# 调用非托管DLL

作者: Justin_901e | 来源:发表于2019-07-19 20:43 被阅读0次

    C# 高用非托管代码4种方法:

    1,通过平台调用(Platform Invoke,  P/Invoke)来调用非托管Dll所公开的API

    2, 通过不安全代码(unsafe code),它允许我们访问内存指针和地址

    3,对Windows 8或更新版本,通过Window Runtiime(WinRT) API

    调用非托管Dll

    平台调用:

    CLI通过P/Invoke调用非托管DLL所导出的函数,此方法只能调用原生API公开的方法

    1, 外部函数的声明

           确定了要调用的目标函数后,P/Invoke的下一步便是托管代码声明函数。和类的所有普通方法一样,必须在类的上下文中声明目标API,但是要为它添加extern修饰符,从而把它声明为外部函数。

    声明外部方法

         本例声明的类是VirtualMemoryManager,它将包含与内存管理相关的函数。注意,方法返回的是一个IntPtr. extern方法永远不包含任何主体,而且几乎总是静态方法。

         外部函数GetCurrentProcess()获取当前进程的一个“伪句柄”(pseudohandle)。在调用中会使用这个伪句柄进行虚拟内存分配。以下是非托管声明:

                                    HANDLE GetCurrentProcess();

    2, 参数的数据类型

        假设开发人员已经确定了目标DLL和导出的函数,那么最困难的一步是标识或创建与外部函数中非托管数据类型对应的托管数据类型。下图是一个理难的API:

    VirtualAllocEx

    VirtualAllocEx()分配操作系统特别为代码执行或数据指定的虚拟内存。为了调用它,托管代码中要为每一种数据类型提供对应的定义。HANDLE, LPVOID, SIZE_T 和 DWORD在CLI托管代码中是未定义的,因此C#定义如下:

    C#中声明VirtualAllocEx

            托管代码一的一个显著特征是,像int这样的基本数据类型不会因为处理器改变大小。int始终是32位。 不过在非托管代码中,内存指针会随处理器而变化。因此,不要将HANDLE和LPVOID等类型映射到int ,而应该把它们映射到System.IntPtr,其大小将依据处理器的内存布局而变化。 

            IntPtr不仅能用于存储指针,还能用于存储其它东西,比如 dwSize。 IntPtr并非只能表示" 存储在整数中的指针 ", 它还能表示 “指针大小的整数” 。IntPtr并非一定要包含指针,它只需包含指针大小的内容即可。许多东西都只有指针大小,但并非指针

    3, 使用 ref 而不是指针

        很多 情况下,非托管代码会为传引用(pass-by-reference)参数使用指针。在这些情况下,P/Invoke不要求你在托管代码中将数据类型映射到指针。相反,应该将对应的参数映射为ref(或 out) , 具体取决于参数是输入/输出, 还是仅输出。 下面代码中,参数 lpflOldProtect便是一个例子,它的数据类型是PDWORD,返回的是一个指针,该指针指向一个变量    ,该变量接收的是“指定页区域的第一页上一个访问保护”

    使用ref和out而不是指针

            文档中lpfOleProtect被定义为[out] 参数,但是随后又的描述中又指出该参数必须指向一个有效的变量,而不能是NULL,针对这种情况,我们原则是为 P/Invoke类型参数使用ref,而不是out.因为被调用者总是能忽略随同ref传递的数据,反之则不然。

            其它参数与VirtualAllocEx()差不多,唯一例外的是lpAddress, 它是从VirtualAllocEx()返回的地址。此外,flNewProtct指定了确切的内存保护类型 page execute,page reade-only等。

    4, 为顺序布局使用StructLayoutAttribute

            有些API涉及的类型没有对应的托管类型。要调用这些API,需要用托管代码重新声明类型。例如,可以使用托管代码来声明非托管的COLORREE结构:

            以上声明关键之处在于StructLayoutAttribute.托管代码可以优化类型的内存布局,所以内存布局可能不是从一个字段到另一个字段顺序存储。为了强制顺序布局,使类型能够直接映射,而且可以在托管和非托管代码之间逐位了复制,需要添加 StructLayoutAttribue,并指针 LayoutKind.Sequential.(从文件流读写数据时,如果要求一个顺序布局,那么也应该这样指定)

              由于struct的非托管定义没有映射到C#定义,所以在非托管结构和托管结构之间不存在直接的映射关系。开发人员应遵循常规的C#指导原则来操作,即类型在行为上是像值类型还是像引用类型,以及大小是否很少(约小于16字节)。

    5,错误处理

        Win32 API编程的一个不便之处在于,错误常以不一致的方式 来报告。为了了解错误细节,还需要额外调用GetLastError()API,再调用FormatMessage()来获取对应的错误消息。

        幸好,P/Invoke设计者为错误处理提供了相应的机制。要启用该机制,DllImport特性SetLastError命名参数要设为true.这样就可实例化一个System.ComponentModel.Win32Exception().在P/Invoke调用之后,会自动用Win32错误数据初始化它:

    6,使用SafeHandle

        P/Invoke经常使用资源,比如一个窗口句柄。在用完此类资源之后,代码需要清理它们。但是不要强迫开发人员记住这一步骤是必须,并每次都手动编写代码,而是应该提供实现IDisposable接口和终结器的类。

    相关文章

      网友评论

          本文标题:C# 调用非托管DLL

          本文链接:https://www.haomeiwen.com/subject/qdislctx.html