C#以及非托管win32函数交互操作方法

一、引言

  .NET平台下促成互操作性有三栽技术——平台调用,C++ Interop和COM
Interop,下边介绍第一种技术,即平台调用。但是朋友等该会时有发生那般的疑点,平台调用到底暴发啊用为?
为啥大家而为此平台调用的技巧了?对于这有限单问题的答案就是是——平台调用能够扶助大家兑现在.NET平台下(也即是乘用C#、VB.net语言描绘的应用程序下)可以调用非托管函数(指定的是C/C++语言写的函数)。这样要大家于.NET平台下促成之效益有古已有之的C/C++
函数实现了如此的法力,这时候大家了无必要自己再用托管语言(如C#、vb.net)去实现一个这样的效用,这时候我们该想到
“拿来主义”,直接下平台调用技术调用C/C++
实现之函数。不过在实质上利用被,使用平台调用技术来调用Win32
API较为常见,所以当这专题大校为我们具体介绍了何等用平台调用来调用Win32函数和调用过程遭到该专注的问题,上边就从一个具体的实例开端坚守专题的介绍。

第二、怎么着利用平台调用Win32 函数——从实例起首

使用.NET平台调用来调用非托管函数的步子如下:

(1). 得到非托管函数的消息,即dll的名,需要调用的非托管函数称等信息

(2). 在托管代码中针对非托管函数举行宣示,并且附加平台调用所急需属性

(3). 在托管代码中一贯调用第二步着宣示的托管函数

  不过调用Win32 API函数还有部分题材需要小心的地点, 首先, 因为过剩Win32
API函数都出ANSI和Unicode两单版,所以当托管代码表明时欲指定调用调用函数的本子。
不过博Win32
API函数有ANSI和Unicode两独版并无是随便说说的,而是发遵照的。我们从调用步骤中可见见,第一步就是需要驾驭非托管函数阐明,为了找到需要用调用的非托管函数,可以因两单器——Visual
Studio自带的dumpbin.exe和depends.exe,dumpbin.exe
是一个命令行工具,可以用来查看从非托管DLL中导出的函数等音讯,可以由此打开Visual
Studio 2010 Command Prompt(粤语版也Visual Studio
命令提醒(2010)),然后切换到DLL所于的目,输入 dummbin.exe/exports
dllName, 如 dummbin.exe/exports User32.dll
来查阅User32.dll中的函数评释,关于更多命令的参数可以参考MSDN; 不过depends.exe是一个可视化界面工具,我们可以从 “VS安装目录\Program Files
(x86)\Microsoft Visual Studio 10.0\Common7\Tools\Bin\”
这么些路子找到,然后双击 depends.exe
就可以出去一个可视化界面(要是某些人设置之VS没有附带这个家伙,也得以由官方网站下载:http://www.dependencywalker.com/),如下图:

图片 1

 上图被 我所以粉色标记出 MessageBox 有少只版,而MessageBoxA
代表的哪怕是ANSI版本,而MessageBoxW
代笔的饶是Unicode版本,这为是者所说的遵照。下面就看看
MessageBox的C++表明的(更多的函数的定义大家好自MSDN中找到,这里提供MessageBox的定义在MSDN中的链接:http://msdn.microsoft.com/en-us/library/windows/desktop/ms645505(v=vs.85).aspx
):

int WINAPI MessageBox(  
  _In_opt_  HWND hWnd,  
  _In_opt_  LPCTSTR lpText,  
  _In_opt_  LPCTSTR lpCaption,  
  _In_      UINT uType  
); 

今天已领会了用调用的Win32 API
函数的概念注解,下边就遵照平台调用的步子,在.NET
中实现对该非托管函数的调用,上面就是扣留看.NET中的代码的:

using System;  

// 使用平台调用技术进行互操作性之前,首先需要添加这个命名空间  
using System.Runtime.InteropServices;  

namespace 平台调用Demo  
{  
    class Program  
    {  
        // 在托管代码中对非托管函数进行声明,并且附加平台调用所需要属性  
        // 在默认情况下,CharSet为CharSet.Ansi  
        // 指定调用哪个版本的方法有两种——通过DllImport属性的CharSet字段和通过EntryPoint字段指定  
        // 在托管函数中声明注意一定要加上 static 和extern 这两个关键字  
        [DllImport("user32.dll")]  
        public static extern int MessageBox1(IntPtr hWnd, String text, String caption, uint type);  

        // 在默认情况下,CharSet为CharSet.Ansi  
        [DllImport("user32.dll")]  
        public static extern int MessageBoxA(IntPtr hWnd, String text, String caption, uint type);  

        // 在默认情况下,CharSet为CharSet.Ansi  
        [DllImport("user32.dll")]  
        public static extern int MessageBox(IntPtr hWnd, String text, String caption, uint type);  

        // 第一种指定方式,通过CharSet字段指定  
        [DllImport("user32.dll", CharSet = CharSet.Unicode)]  
        public static extern int MessageBox2(IntPtr hWnd, String text, String caption, uint type);  

        // 通过EntryPoint字段指定  
        [DllImport("user32.dll", EntryPoint="MessageBoxA")]  
        public static extern int MessageBox3(IntPtr hWnd, String text, String caption, uint type);  

        [DllImport("user32.dll", EntryPoint = "MessageBoxW")]  
        public static extern int MessageBox4(IntPtr hWnd, String text, String caption, uint type);  
        static void Main(string[] args)  
        {  
            // 在托管代码中直接调用声明的托管函数  
            // 使用CharSet字段指定的方式,要求在托管代码中声明的函数名必须与非托管函数名一样  
            // 否则就会出现找不到入口点的运行时错误  
            //MessageBox1(new IntPtr(0), "Learning Hard", "欢迎", 0);  

            // 下面的调用都可以运行正确  
            //MessageBoxA(new IntPtr(0), "Learning Hard", "欢迎", 0);  
            //MessageBox(new IntPtr(0), "Learning Hard", "欢迎", 0);  

            // 使用指定函数入口点的方式调用  
            //MessageBox3(new IntPtr(0), "Learning Hard", "欢迎", 0);  

            // 调用Unicode版本的会出现乱码  
            MessageBox4(new IntPtr(0), "Learning Hard", "欢迎", 0);  
        }  
    }  
} 

运转对的结果也:

图片 2

自从代码的诠释中得看来,第一只调用MessageBox1谋面面世运行时左,然则为何改调用会油然则生
“不能在 DLL“user32.dll”中找到名为吧“MessageBox1”的入口点。”的错也?
为了通晓为啥,这里就是用明白通过CharSet字段指定的这种艺术的中举办行为了。之所以会并发这一个错误,是因当指定CharSet为Ansi时,P/Invoke首先会面经过根函数号称以User32.dll中检索,即未带来后缀A的函数名MessageBox1
进行查找,假如找到与同函数一样名称的函数,就调用该函数;

  假设没有找到则使带来后缀为A的函数MessageBox1A举行检索,假若找到,则使用该函数,如若要没有找到,则会产出
“无法在
DLL“user32.dll”中找到名为吧“MessageBox1”的入口点。”的失实。把CharSet指定为Unicode时,搜索格局是均等的,只是没有找到根函数时会面加W后缀举行搜寻的。
从者的找调用函数的经过中好发现,因为user32.dll中既非设有MessageBox1部数也非设有MessageBox1A函数,所以才会面面世调用错误。(朋友看到出现谬误时,应该会生如此的问号——大家怎么捕捉错误来映现错误音信呢?这个问题将会当生一些分解。)

唯独以平台调用技术被,还索要小心上边4点:

(1).
DllImport属性的ExactSpelling字段假设安为true时,则当托管代码中宣示的函数称呼必须跟如调用的非托管函数誉为完全一致,因为从ExactSpelling字面意思可以见见为
“准确拼写”的意,当ExactSpelling设置为true时,此时碰面改变平台调用的行,此时平台调用只晤面基于根函数叫作举办搜,而寻不至之早晚不碰面补充加
A或者W来拓展重复找,. 例如,假使指定 MessageBox,则平台调用将寻找
MessageBox,假诺它寻找不至完全相同的拼写则会现出找不交入口函数的一无是处。
在此之后面的代码中可见见,我们以代码中并没点名 ExactSpelling
字段,然则代码中却从不起调用错误,这虽证实以C#以及托管C++语言中,
ExactSpelling
默认值就是false的,不过在VB。NET中,ExactSpelling的默认值就是true,
所以以上代码假使转正为Vb.NET时,就需要显式指定ExactSpelling
字段为false,不然就晤面油但是生 “找不顶函数入口的左”。
为了为我们进一步便于通晓地方的辩护,相信我们看下一布置图会更了解ExactSpelling字段的意义的:

图片 3

(2).
假如利用设置CharSet的值来控制调用函数的版时,则用以托管代码中注脚的函数称作必须同根函数号称相同,否则也相会调用出错,那一点起阳台调用过程中得生好地亮,假诺用调用非托管函数名叫吧
MessageBoxA,而你于托管代码申明也
MessageBox1,这样于寻找过程中肯定就是会指示找不交函数叫作的左,
也不怕是面代码中率先只调用出错的由来。

(3).
假诺经过点名DllImport属性的EntryPoint字段的法来调用函数版本时,此时必呼应地指定同的配合的CharSet设置,意思就是是——假诺指定EntryPoint为
MessageBoxW,那么得将CharSet指定为CharSet.Unicode,尽管指定EntryPoint为
MessageBoxA,那么必须以CharSet指定为CharSet.Ansi或者无点名,因为
CharSet默认值就是Ansi。上边代码MessageBox4的调用之所以碰面世乱码,是因CharSet指定为Ansi(也是默认值)时,
平台调用将字符串依据ANSI编码格局封送至非托管内存中(在.NET
中,字符串的编码模式默认为Unicode的),即每个字符仅占据一个字节,(而于Unicode编码的字符串来说,字符串中之每个字符都是使用有限单字节举办编码的),当非托管函数MessageBoxW先河举行时,它晤面管该内存中的多寡据Unicode编码处理,即各级半个字节当做是一个Unicode字符,知道境遇双字节的‘\0’
字符截至。所以非托管函数返回的结果为尽管起乱码了。 如若指定EntryPoint
字段的值也MessageBoxA,却把CharSet字段设置为CharSet.Unicode的事态下,也会现出同等的乱码问题,如下图所示:

图片 4

(4). CharSet还闹一个不过挑选字段为——CharSet.Auto,
假如把CharSet字段设置也CharSet.Auto,则平台调用会针对对象操作系统适当地自动封送字符串。在
Windows NT、Windows 2000、Windows XP 和 Windows Server 2003
连串及,默认值为 Unicode;在 Windows 98 和 Windows Me 上,默认值为
Ansi。虽然官语言运行时默认值为
Auto,但用语言可重复写此默认值。例如,默认意况下,C#
将具备方以及类且记为
Ansi。所以下的调用一样吧谋面产出乱码,原因在第三触及被既说了,下边直接沾测试例子和结果:

class Program  
{    
    [DllImport("user32.dll", EntryPoint = "MessageBoxA", CharSet =  CharSet.Auto)]  
    public static extern int MessageBox5(IntPtr hWnd, String text, String caption, uint type);  
    static void Main(string[] args)  
    {  
        MessageBox5(new IntPtr(0), "Learning Hard", "欢迎", 0);  
    }  
} 

运转结果吗:

图片 5

抓获由Win32函数本身重回分外的言传身教代码如下:

using System;  
using System.ComponentModel;  
// 使用平台调用技术进行互操作性之前,首先需要添加这个命名空间  
using System.Runtime.InteropServices;  

namespace 处理Win32函数返回的错误  
{  
    class Program  
    {  
        // Win32 API   
        //  DWORD WINAPI GetFileAttributes(  
        //  _In_  LPCTSTR lpFileName  
        //);  

        // 在托管代码中对非托管函数进行声明  
        [DllImport("Kernel32.dll",SetLastError=true,CharSet=CharSet.Unicode)]  
        public static extern uint GetFileAttributes(string filename);  

        static void Main(string[] args)  
        {  
            // 试图获得一个不存在文件的属性  
            // 此时调用Win32函数会发生错误  
            GetFileAttributes("FileNotexist.txt");  

            // 在应用程序的Bin目录下存在一个test.txt文件,此时调用会成功  
            //GetFileAttributes("test.txt");  

            // 获得最后一次获得的错误  
            int lastErrorCode = Marshal.GetLastWin32Error();  

            // 将Win32的错误码转换为托管异常  
            //Win32Exception win32exception = new Win32Exception();  
            Win32Exception win32exception = new Win32Exception(lastErrorCode);  
            if (lastErrorCode != 0)  
            {  
                Console.WriteLine("调用Win32函数发生错误,错误信息为 : {0}", win32exception.Message);  
            }  
            else 
            {  
                Console.WriteLine("调用Win32函数成功,返回的信息为: {0}", win32exception.Message);  
            }  

            Console.Read();  
        }  
    }  
} 

运行结果吗:

图片 6

 要怀恋拿到当调用Win32函数过程被冒出的错误信息,首先要用DllImport属性的SetLastError字段设置也true,唯有如此,平台调用才会面以最后一欠好调动用Win32起的错误码保存起来,然后会当托管代码调用Win32失利后,通过马尔斯hal类的静态方法GetLastWin32Error拿到由平台调用保存的错误码,从而对错举办对应的辨析与处理。那样即使可以赢得Win32中的错误音讯了。
  上面代码简单地示范了安当托管代码中得最终一浅闹的Win32错误音讯,可是还可因此调用Win32
API
提供的FormatMessage函数的计来取得错误音信,可是这种办法发生一个死显然的坏处(所以这里就是无演示了),当对FormatMessage函数调用退步时,这时候就来或得到不科学的错误音讯,所以,推荐采纳.NET提供的Win32Exception特别类来收获实际的错误音信。关于更多之FormatMessage函数可以参考MSDN:
http://msdn.microsoft.com/en-us/library/ms679351(v=vs.85).aspx

老三、当调用Win32函数发错时怎么惩罚?——拿到Win32函数的错误信息

  后边有吗我们演示了阳台调用的施用及以过程得专注的问题,
当我们探听了那多少个下,肯定会来这么的一个疑难,当调用Win32函数过程遭到遭逢由Win32函数回的失实而什么去处理也?
或者由于非托管函数的托管定义导致的荒唐或特别怎么捕捉,就设下边代码中调用MessageBox1出现非常时,怎么样捕捉并受用于一个和谐的指示信息呢?对于此点儿单问题,上边通过简单只实际的例子来演示。

捕捉由托管定义导致的大演示代码:

class Program  
    {  
        // 在托管代码中对非托管函数进行声明,并且附加平台调用所需要属性  
        // 在默认情况下,CharSet为CharSet.Ansi  
        // 指定调用哪个版本的方法有两种——通过DllImport属性的CharSet字段和通过EntryPoint字段指定  
        [DllImport("user32.dll")]  
        public static extern int MessageBox1(IntPtr hWnd, String text, String caption, uint type);  
        static void Main(string[] args)  
        {  
            try 
            {  
                MessageBox1(new IntPtr(0), "Learning Hard", "欢迎", 0);  
            }  
            catch (DllNotFoundException dllNotFoundExc)  
            {  
                Console.WriteLine("DllNotFoundException 异常发生,异常信息为: " + dllNotFoundExc.Message);  
            }  
            catch (EntryPointNotFoundException entryPointExc)  
            {  
                Console.WriteLine("EntryPointNotFoundException 异常发生,异常信息为: " + entryPointExc.Message);  
            }  
            Console.Read();  
       }  
} 

 

四、小结   

叙到此处,本专题的始末也即介绍了了,本专题只是略介绍了动平台调用技术来调用Win32函数,不过实际上的操作远远不是这般简单的,要控平台调用的艺,还需要大家在工作进程多多实践。因为于仍专题受关系了片数额封送一些知识,为了襄助我们还好理解数据封送处理,在一个专题将为大家带来平台调用中之多寡封送处理专题。运行结果为:

图片 7

 原文地址:http://learninghard.blog.51cto.com/6146675/1123130