• 前言

  让拖管代码对象和非托管对象协同工作的过程称为互用性(Interoperability),通常简称为 Interop。
  P/Invoke在托管代码与非托管代码交互式时产生一个事务(Transition),这通常发生在使用平台调用服务(Platfrom Invocation Services)即P/Invoke。允许托管代码调用平台(Platfrom)相关的非托管代码(c++、VB、Delphi....)
  Com Interop 一种服务,它使 .NET Framework 对象能够与 COM 对象通信。
  如调用系统的 API 或与 COM 对象打交道,通过 System.Runtime.InteropServices 命名空间
  虽然使用 Interop 非常方便,但据估计每次调用事务都要执行 10 到 40 条指令,算起来开销也不少,所以我们要尽量少调用事务
  如果非用不可,建议本着一次调用执行多个动作,而不是多次调用每次只执行少量动作的原则

  • 为什么要使用P/Invoke

    • 速度

      • .NET下的JIT(即时编译器)和GC(垃圾回收)通常性能十分优秀
      • 某些特定对性能非常敏感的情况下,C/C++/汇编仍然有着性能上的优势
        • 使用汇编可以手动优化机器代码
        • 可以手动管理内存
        • 部门情况下C/C++编译器(他们在build过程中已经将优化做完)比JIT优化做的更好 (运行时才生成对应机器代码)
        • 注意:使用C/C++会导致生产率下降。
        • 好坏需要根据适用场景权衡
    • 功能

      • .NET Framework的类库缺少某些功能

        • 部分Windows API并没有对应的.NET API
        • 新的Windows的功能暂时并没有对应的.NET API
    • 重用

      • 已有的代码是使用非托管代码编写保护投资
      • 第三方组件仅提供非托管API
      • 如果做程序迁移可以先迁移部分简单到C#上核心依然可暂时继续使用c++加快迁移进度,平滑迁移。
    • P/Invoke速度较慢

      • 从托管代码进入非托管代码CLR(Common Language Runtime)内部需要切换很多状态。
      • 托管类型和非托管类型之间的转换。 数据转换非常耗时转换过程中可能还会产生内存的管理和分配。 例如:C# 的string是用unicode 宽字符表示的 C/C++就是一个字符指针char*指向以一个已经结尾的内存。
    • 一些建议

      • 应当尽量减少对P/Invoke的调用
      • P/Invoke调用最好是粗粒度的,这个和网络通信所遵守的原则一样,尽量减少往返一次性做更多的事情。
  • 编写函数原型    

    • 非托管函数原型

      • BOOL WINAPI Beep(_in DWORD dwFreq,_in DWORD dwDuration) 
        Parameters
        dwFreq [in]

        The frequency of the sound, in hertz. This parameter must be in the range 37 through 32,767 (0x25 through 0x7FFF).

        dwDuration [in]
        Return value

        If the function succeeds, the return value is nonzero.

        If the function fails, the return value is zero. To get extended error information, call

        Requirements

        DLL  Kernel32.dll

    • 托管原型

      • public static extern bool Beep([In]uint dwFreq, [In] uint dwDuration);
  • 关于[In]/[Out]

    • [In] 从调用者传递到被调用者
    • [Out]从被调用者传递到调用者
    • [In,Out]先从调用者传递到被调用者,然后从被调用者传回到调用者
  • 添加Attribute

    • 代码段

      [DllImportAttribute("kernel32.dll")]
      public static extern bool Beep([In]uint dwFreq, [In] uint dwDuration);

      CLR会找到kernel32.dll使用loadlibrary函数加载起来,然后通过GetProcAddress函数查找到Beep入口点地址,然后就可以通过入口点地址调用Beep函数。在调用之前CLR会做一些状态切换,需要进行参数转换.NET的32int 转换为 c++的32整型(在这里由于.NET的int 和c++的32整形是一样的所以不用转换可以直接传递)。但是也有复杂的情况,在字符串的情况下CRl需要把字符串内容备份拷贝,转换编码以\0结尾的字符串内存传递给c++。

  • DllImportAttribute

      DllImportAttribute(string dllName) dllName指定要调用的API所位于的DLL

    • CallingConvention=调用约定   调用者和被调用者之间的约定

      • 参数

        • 是放在栈上还是寄存器上?
        • 参数以什么顺序放?
        • 谁放这些参数?(调用者放)
        • 谁负责清理堆栈?
          • 不同编译器设定的栈结构不尽相同,跨开发平台时由函数调用者清除栈内数据不可行。
          • 某些函数的参数是可变的,如printf函数,这样的函数只能由函数调用者清除栈内数据。
          • 由调用者清除栈内数据时,每次调用都包含清除栈内数据的代码,故可执行文件较大。
        • 进栈顺序?  
      • 对函数名也有影响
      • C语言编译器函数名称修饰规则
               __stdcall:编译后,函数名被修饰为“_functionname@number”。
               __cdecl:编译后,函数名被修饰为“_functionname”。
               __fastcall:编译后,函数名给修饰为“@functionname@nmuber”。
               注:“functionname”为函数名,“number”为参数字节数。
               注:函数实现和函数定义时如果使用了不同的函数调用协议,则无法实现函数调用。
         C++语言编译器函数名称修饰规则
               __stdcall:编译后,函数名被修饰为“?functionname@@YG******@Z”。
               __cdecl:编译后,函数名被修饰为“?functionname@@YA******@Z”。
               __fastcall:编译后,函数名被修饰为“?functionname@@YI******@Z”。
               注:“******”为函数返回值类型和参数类型表。
               注:函数实现和函数定义时如果使用了不同的函数调用协议,则无法实现函数调用。
               C语言和C++语言间如果不进行特殊处理,也无法实现函数的互相调用。
      • WinAPI 此成员实际上不是调用约定,而是使用了默认平台的调用约定。例如Windows上默认StdCall,在WindowsCE.NET上默认为Cdecl
      • Cdecl 调用方法清理堆栈。这使您能够调用具有varargs(可变参数)的函数(如Printf),C/C++默认的函数调用协议,函数参数由右向左入栈,函数调用结束后由函数调用者清除栈内数据。
      • FastCall  适用于对性能要求较高的场合。从左开始不大于4字节的两个参数放入CPU的ECX和EDX寄存器,其余参数从右向左入栈。函数调用结束后由被调用函数清除栈内数据。在寄存器中放入不大于4字节的参数,故性能较高,适用于需要高性能的场合。
      • StdCall  被调用方法清理堆栈。这是使用平台invoke调用非托管函数的默认约定。 Windows API默认的函数调用协议,函数参数由右向左入栈。函数调用结束后由被调用函数清除栈内数据。
      • ThisCall 第一个参数是this指针,它存储在寄存器ECX中。其他参数从右往左被推送到堆栈上。此调用约定用于对从非托管DLL导出的类调用方法 ,被调用者清理。 
    • CharSet=字符集

      • ANSI、Unicode(c#为16位)、Auto  
    • EntryPoint=入口点

      • 函数入口点     

      • 缺省为托管函数的名字   

    • ExactSpelling=准确命名(精确查找)

      • 使用指定名称查找函数,关闭Probing(探测)特性  
      • Probing指对函数正确名称进行猜测行为  
    • SetLastError=上次错误值

      • 保存上次的错误值 防止其他API调用冲掉P/Invoke的Error值
      • 使用Marshal.GetlastWin32Error API获得
    • 其他

      • PreserveSig=保持函数参数,是否自动进行retval返回值和HRESULT到异常转换
      • BestFitMapping=字符最佳映射(如∞转换到8)
      • ThrowOnUnmappableChar=无映射时抛出异常    
  • Demo

    •  using System;
      using System.Collections.Generic;
      using System.Linq;
      using System.Runtime.InteropServices;
      using System.Text;
      using System.Threading.Tasks; namespace demo1
      { [StructLayoutAttribute(LayoutKind.Sequential)]
      public struct HWND__
      { /// int
      public int unused;
      } public partial class NativeMethods
      { /// Return Type: int
      ///hWnd: HWND->HWND__*
      ///lpText: LPCSTR->CHAR*
      ///lpCaption: LPCSTR->CHAR*
      ///uType: UINT->unsigned int
      [DllImportAttribute("user32.dll", EntryPoint = "MessageBoxA")]
      public static extern int MessageBoxA([InAttribute()] System.IntPtr hWnd, [InAttribute()] [MarshalAsAttribute(UnmanagedType.LPStr)] string lpText, [InAttribute()] [MarshalAsAttribute(UnmanagedType.LPStr)] string lpCaption, uint uType); }
      class Program
      {
      /// <summary>
      /// 用于生成简单的声音
      /// </summary>
      /// <param name="dwFreq"> Long,声音频率(从37Hz到32767Hz)。在windows95中忽略</param>
      /// <param name="dwDuration"> Long,声音的持续时间,以毫秒为单位。如为-1,表示一直播放声音,直到再次调用该函数为止。在windows95中会被忽略</param>
      /// <returns>Long,TRUE(非零)表示成功,否则返回零。会设置GetLastError</returns>
      [DllImport("kernel32.dll", CallingConvention = CallingConvention.Winapi, CharSet = CharSet.Unicode, EntryPoint = "Beep", ExactSpelling = true, SetLastError = true)]
      public static extern bool Beep([In]int dwFreq, [In] int dwDuration); [DllImport("user32.dll", CharSet = CharSet.Unicode, EntryPoint = "MessageBox")]
      public static extern bool MsgBox(IntPtr hwnd,string text, string caption ,int type); static void Main(string[] args)
      {
      NativeMethods.MessageBoxA(IntPtr.Zero, "", "", );
      // Marshal.GetLastWin32Error();
      MsgBox(IntPtr.Zero, "", "",); int[] ss = new int[] { , , , , , , }; for (int i = ; i < ; i++)
      {
      Beep(ss[i-] / , );
      Console.WriteLine("低" + i);
      Beep(, ); }
      for (int i = ; i < ; i++)
      {
      Beep(ss[i - ] , );
      Console.WriteLine("中" + i);
      Beep(, );
      }
      for (int i = ; i < ; i++)
      {
      Beep(ss[i - ] * , );
      Console.WriteLine("高" + i);
      Beep(, );
      } //C#自带的应该也是通过上面的手段调用winapi
      int x = ;
      //
      if (
      ((x >= ) && (x <= )))
      {
      for (int i = ; i <= x; i++)
      {
      Console.WriteLine("Beep number {0}.", i);
      Console.Beep(, );
      }
      }
      else
      Console.WriteLine("Usage: Enter the number of times (between 1 and 9) to beep."); } }
      }
  • 推荐工具

   PInvokeInteropAssistant

  • 声明

    本文为学习笔记 如有侵犯请联系我 ,学习课程名 由张羿主讲的《公共语言运行库(CLR)开发系列课程(1):Pinvoke简介》 百度可以搜索到相关学习资料 我收集资料并不全官方不提供下载了 现在只有视频提供下载 第三方地址

公共语言运行库(CLR)开发系列课程(1):Pinvoke 简介 学习笔记的更多相关文章

  1. 公共语言运行库(CLR)开发系列课程(3):COM Interop基础 学习笔记

    上章地址 什么是COM Component Object Model 组建对象模型 基于接口(Interface) 接口=协议 IID 标识接口 V-table 虚表 方式调用 单继承 对象(Obje ...

  2. 公共语言运行库(CLR)开发系列课程(2):Pinvoke 进阶 学习笔记

    上一章地址 API版本 具有字符串参数的API通常有两种版本 GetWindowText GetWindowTextA GetWindowTextW 缺省情况下CLR会自动寻找合适的匹配 CharSe ...

  3. CLR 公共语言运行库

    1..支持多语言..只是语言是面向CLR的..均可以在此基础上运行. 2..程序集加载..程序打包之后的Dll文件由CLR(公共语言运行库)来编译并加载到可以执行状态..由CLR(公共语言运行库)加载 ...

  4. 公共语言运行库(CLR)和中间语言(IL)(一)

    公共语言运行库(.net运行库)即CLR 1.C#先编译为IL,IL为ms的中间语言,IL是平台无关性的. 2.CLR再将IL编译为平台专用语言. 3.CLR在编译IL时为即时编译(JIT) VB.V ...

  5. Silverlight for Windows Phone开发系列课程

    Silverlight for Windows Phone开发系列课程(1):Windows Phone平台概况         课程简介:本节开始介绍系列课程的概况,包括课程内容,先决条件,学习目的 ...

  6. ASP.NET MVC框架开发系列课程 (webcast视频下载)

    课程讲师: 赵劼 MSDN特邀讲师 赵劼(网名“老赵”.英文名“Jeffrey Zhao”,技术博客为http://jeffreyzhao.cnblogs.com),微软最有价值专家(ASP.NET ...

  7. CLR(Common Language Runtime) 公共语言运行库

    .NET Core 使用 CoreCLR .NET Framework 使用CLR. 1. 将代码编译为IL (Intermediate Language) 2. CLR 把IL 编译为平台专用的本地 ...

  8. iOS开发系列课程预告

    近期在Mac和iOS上做开发,认为应该写一点东西分享给感兴趣的童鞋们.在此之前.以前有非常多同行们都在埋怨苹果Objective-C的复杂和难以上手,为此也有非常多人对今年(2014年)刚推出的Swi ...

  9. 【VS开发】Windows平台下Makefile学习笔记

    作者:朱金灿 来源:http://blog.csdn.net/clever101 决心学习Makefile,一方面是为了解决编译开源代码时需要跨编译平台的问题(发现一些开源代码已经在使用VS2010开 ...

随机推荐

  1. Daily Scrum - 11/26

    Meeting Minutes 今天是Sprint 3第一天,任烁向我们交代了他那边的代码情况,他这两天回学校有点事,人千和章玮暂时先熟悉一下他写的那部分,正在试图将代码merge起来.重阳实现了进度 ...

  2. .net 开源组件推荐 之 StackExchange

    已经两年没更新过博客了!!! StackExchange,地址:https://github.com/StackExchange,开源的这些项目都是在StackOverflow线上使用的. 说起Sta ...

  3. 《当大数据遇见网络:大数据与SDN》

    总体结构: <当大数据遇见网络:大数据与SDN> 摘要 大数据和SDN无论是对于学术界还是工业界来说都极具吸引力.传统上人们都是分别在最前沿工作中研究这两个重要的领域.然而一方面,SDN的 ...

  4. Oracle 导入单表数据

    1. 测试一下 删除某一张表,然后 通过 expdp 数据库泵的备份来恢复数据. 测试过程 ) from bizlog COUNT() ---------- 151 drop table bizlog ...

  5. 【转】mybatis如何防止sql注入

    sql注入大家都不陌生,是一种常见的攻击方式,攻击者在界面的表单信息或url上输入一些奇怪的sql片段,例如“or ‘1’=’1’”这样的语句,有可能入侵参数校验不足的应用程序.所以在我们的应用中需要 ...

  6. layout图形化界面看不到内容 Failed to find the style corresponding to the id

    1.问题 在创建新的工程的时候,选择目标SDK为api21,编译SDK为api23.创建出来的layout文件图形化界面中看不到,并且报错: Failed to find the style corr ...

  7. Java获取当前运行方法所在的类和方法名

    很简单,直接看代码: public void showClassAndMethod() { System.out.println(this.getClass().getSimpleName() + & ...

  8. 实体框架自定义代码优先约定(EF6以后)

    仅限EF6仅向前 - 此页面中讨论的功能,API等在实体框架6中引入.如果您使用的是早期版本,则部分或全部信息不适用. 使用Code First时,您的模型是使用一组约定从您的类计算的.默认的Code ...

  9. contentInsetAdjustmentBehavior各个值之间的区别

    iOS11也出了不少时候了网上说适配的文章一大堆.关于contentInsetAdjustmentBehavior这个参数之间的区别,好像没什么人能说明.往下看的前提是你已经知道什么是安全区域,没看明 ...

  10. SSM 中 BaseController 使用 session

    转载: http://blog.sina.com.cn/s/blog_7085382f0102v9jg.html public class BaseController { //region Http ...