当前位置:必发365电子游戏 > 操作系统 > 直接使用平台调用技术调用C/C++,基础知识之DllImport特性)
直接使用平台调用技术调用C/C++,基础知识之DllImport特性)
2019-12-19

一、引言

  .NET平台下完结互操作性有二种手艺——平台调用,C++ Interop和COM Interop,上边介绍第生机勃勃种本领,即平台调用。可是朋友们应当会好似此的疑难,平台调用到底有哪些用吧? 为啥我们要用平台调用的本领了?对于那多个难点的答案正是——平台调用能够帮助我们兑未来.NET平台下(也正是指用C#、VB.net语言写的应用程序下卡塔尔(英语:State of Qatar)能够调用非托管函数(钦定的是C/C++语言写的函数)。这样风华正茂旦我们在.NET平台下完毕的机能有现成的C/C++ 函数完成了那般的机能,此时大家完全无需自身再用托管语言(如C#、vb.net)去完毕一个这样的效果,此时大家理应想到 “拿来主义”,直接行使平台调用才能调用C/C++ 完毕的函数。但是在其实使用中,使用平台调用技艺来调用Win32 API较为广阔,所以在这里个专项论题大校为我们具体介绍了哪些运用平台调用来调用Win32函数以至调用进程中应当小心的标题,上边就从三个切实可行的实例开首本专项论题的牵线。

二、怎么样使用平台调用Win32 函数——从实例最早

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

(1卡塔尔. 得到非托管函数的音讯,即dll的名目,必要调用的非托管函数名等消息

(2卡塔尔国. 在托管代码中对非托管函数举办宣示,并且附加平台调用所要求属性

(3卡塔尔(英语:State of Qatar). 在托管代码中央直属机关接调用第二步中宣示的托管函数

  不过调用Win32 API函数还应该有黄金时代对主题材料要求注意的地点, 首先, 因为众多Win32 API函数都有ANSI和Unicode五个本子,所以在托管代码表明时要求钦赐调用调用函数的本子。 然则众多Win32 API函数有ANSI和Unicode八个版本并不是随意说说的,而是有依赖的。咱们从调用步骤中得以见见,第一步就须求掌握非托管函数证明,为了找到要求须求调用的非托管函数,能够借助三个工具——Visual Studio自带的dumpbin.exe和depends.exe,dumpbin.exe 是多个命令行工具,能够用来查看从非托管DLL中程导弹出的函数等信息,能够透过打开Visual Studio 2008 Command Prompt(汉语版为Visual Studio 命令提醒(二零一零卡塔尔卡塔尔(英语:State of Qatar),然后切换成DLL所在的目录,输入 dummbin.exe/exports dllName, 如 dummbin.exe/exports User32.dll 来查看User32.dll中的函数证明,关于越来越多命令的参数能够参见MSDN; 可是depends.exe是一个可视化界面工具,大家能够从 “VS安装目录Program Files (x86)Microsoft Visual Studio 10.0Common7ToolsBin” 那一个门路找到,然后双击 depends.exe 就足以出来多个可视化分界面(假使有些人设置的VS未有附带那个工具,也得以从官方网站下载:),如下图:

图片 1

 上海体育场合中 笔者用蓝灰标示出 MessageBox 有多少个本子,而MessageBoxA 代表的正是ANSI版本,而MessageBoxW 代笔的就是Unicode版本,那也是地点所说的基于。上面就看看 MessageBox的C++注解的(越来越多的函数的定义大家能够从MSDN中找到,这里提供MessageBox的概念在MSDN中的链接: ):

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

从代码的疏解中得以见见,第叁个调用Message博克斯1会现身运营时不当,然则为啥改调用会现出 “不也许在 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卡塔尔(英语:State of Qatar). 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。上边代码Message博克斯4的调用之所以会冒出乱码,是因为CharSet钦定为Ansi(也是暗中同意值卡塔尔国时, 平台调用将字符串根据ANSI编码形式封送到非托管内部存款和储蓄器中(在.NET 中,字符串的编码方式私下认可为Unicode的卡塔尔,即每一个字符只占多个字节,(而对此Unicode编码的字符串来讲,字符串中的每种字符都以使用七个字节进行编码的卡塔尔(قطر‎,当非托管函数MessageBoxW带头实施时,它会把该内部存款和储蓄器中的数码遵照Unicode编码管理,即每七个字节充任是三个Unicode字符,知道境遇双字节的‘’ 字符甘休。所以非托管函数重回的结果也就应际而生乱码了。 如若钦赐EntryPoint 字段的值为Message博克斯A,却把CharSet字段设置为CharSet.Unicode的情况下,也会冒出同等的乱码难点,如下图所示:

图片 4

(4卡塔尔(英语:State of Qatar). CharSet还应该有叁个可选字段为——CharSet.Auto, 假设把CharSet字段设置为CharSet.Auto,则平台调用会针对对象操作系统适本地活动封送字符串。在 Windows NT、Windows 2001、Windows XP 和 Windows Server 二〇〇二类别上,私下认可值为 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功亏后生可畏篑后,通过Marshal类的静态方法GetLastWin32Error拿到由平台调用保存的错误码,进而对错误举行相应的分析和管理。那样就足以获得Win3第22中学的错误音信了。
  上边代码轻巧地示范了哪些在托管代码中收获最后贰遍发生的Win32错误新闻,然则仍然是能够经过调用Win32 API 提供的FormatMessage函数的格局来获得错误音信,可是这种方法有二个很名扬四海标坏处(所以那边就不演示了卡塔尔(قطر‎,当对FormatMessage函数调用退步时,当时就有极大概率获得不正确的错误音讯,所以,推荐选择.NET提供的Win32Exception至极类来博取实际的错误音讯。关于越来越多的FormatMessage函数可以参谋MSDN:

.NET简谈互操作(生机勃勃:开篇介绍卡塔尔(قطر‎

三、当调用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();  
       }  
} 

 

.NET简谈互操作(二:向阳花木卡塔尔(英语:State of Qatar)

四、小结   

直接使用平台调用技术调用C/C++,基础知识之DllImport特性)。讲到这里,本专项论题的内容也就介绍完了,本专项论题只是简单介绍了应用平台调用能力来调用Win32函数,然则实际上的操作远远不是这么轻松的,要领悟平台调用的本事,还索要大家在办事经过多多执行。因为在本专项论题中提到了意气风发部分数量封送一些知识,为了协助大家越来越好精通数据封送处理,在三个专项论题将为大家带给平台调用中的数据封送管理专项论题。运营结果为:

图片 7

 最先的小说地址:

.NET简谈互操作(三:根基知识之DllImport本性卡塔尔(قطر‎

.NET简谈互操作(四:底工知识之Dispose非托管内部存储器卡塔尔(英语:State of Qatar)

.NET简谈互操作(五:底工知识之Dynamic平台调用卡塔尔国

.NET简谈互操作(六:底蕴知识之升高平台调用质量卡塔尔国

.NET简谈互操作(七:数据封送之介绍卡塔尔