zoukankan      html  css  js  c++  java
  • 公共语言运行库(CLR)开发系列课程(1):Pinvoke 简介 学习笔记

    • 前言

      让拖管代码对象和非托管对象协同工作的过程称为互用性(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需要把字符串内容备份拷贝,转换编码以结尾的字符串内存传递给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

      •  1 using System;
         2 using System.Collections.Generic;
         3 using System.Linq;
         4 using System.Runtime.InteropServices;
         5 using System.Text;
         6 using System.Threading.Tasks;
         7 
         8 namespace demo1
         9 {
        10 
        11 
        12     [StructLayoutAttribute(LayoutKind.Sequential)]
        13     public struct HWND__
        14     {
        15 
        16         /// int
        17         public int unused;
        18     }
        19 
        20     public partial class NativeMethods
        21     {
        22 
        23         /// Return Type: int
        24         ///hWnd: HWND->HWND__*
        25         ///lpText: LPCSTR->CHAR*
        26         ///lpCaption: LPCSTR->CHAR*
        27         ///uType: UINT->unsigned int
        28         [DllImportAttribute("user32.dll", EntryPoint = "MessageBoxA")]
        29         public static extern int MessageBoxA([InAttribute()] System.IntPtr hWnd, [InAttribute()] [MarshalAsAttribute(UnmanagedType.LPStr)] string lpText, [InAttribute()] [MarshalAsAttribute(UnmanagedType.LPStr)] string lpCaption, uint uType);
        30 
        31     }
        32     class Program
        33     {
        34         /// <summary>
        35         /// 用于生成简单的声音
        36         /// </summary>
        37         /// <param name="dwFreq">    Long,声音频率(从37Hz到32767Hz)。在windows95中忽略</param>
        38         /// <param name="dwDuration">    Long,声音的持续时间,以毫秒为单位。如为-1,表示一直播放声音,直到再次调用该函数为止。在windows95中会被忽略</param>
        39         /// <returns>Long,TRUE(非零)表示成功,否则返回零。会设置GetLastError</returns>
        40         [DllImport("kernel32.dll", CallingConvention = CallingConvention.Winapi, CharSet = CharSet.Unicode, EntryPoint = "Beep", ExactSpelling = true, SetLastError = true)]
        41         public static extern bool Beep([In]int dwFreq, [In] int dwDuration);
        42 
        43         [DllImport("user32.dll", CharSet = CharSet.Unicode, EntryPoint = "MessageBox")]
        44         public static extern bool MsgBox(IntPtr hwnd,string text, string  caption ,int type);
        45 
        46 
        47         static void Main(string[] args)
        48         {
        49             NativeMethods.MessageBoxA(IntPtr.Zero, "123", "321", 0);
        50           //  Marshal.GetLastWin32Error();
        51             MsgBox(IntPtr.Zero, "123", "321",2);
        52           
        53             int[] ss = new int[7] { 262, 294, 330, 349, 392, 440, 494 };
        54 
        55             for (int i = 1; i < 8; i++)
        56             {
        57                 Beep(ss[i-1] / 2, 500);
        58                 Console.WriteLine("" + i);
        59                 Beep(0, 1000);
        60  
        61              
        62             }
        63             for (int i = 1; i < 8; i++)
        64             {
        65                 Beep(ss[i - 1] , 500);
        66                 Console.WriteLine("" + i);
        67                 Beep(0, 1000);
        68             }
        69             for (int i = 1; i < 8; i++)
        70             {
        71                 Beep(ss[i - 1] * 2, 500);
        72                 Console.WriteLine("" + i);
        73                 Beep(0, 1000);
        74             }
        75 
        76 
        77             //C#自带的应该也是通过上面的手段调用winapi
        78             int x = 9;
        79             //
        80             if (
        81                 ((x >= 1) && (x <= 9)))
        82             {
        83                 for (int i = 1; i <= x; i++)
        84                 {
        85                     Console.WriteLine("Beep number {0}.", i);
        86                     Console.Beep(1111, 1111);
        87                 }
        88             }
        89             else
        90                 Console.WriteLine("Usage: Enter the number of times (between 1 and 9) to beep.");
        91 
        92         }
        93  
        94     }
        95 }
    • 推荐工具

       PInvokeInteropAssistant

    • 声明


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

  • 相关阅读:
    使用Python的Mock库进行PySpark单元测试
    库龄报表的相关知识
    使用PlanViz进行ABAP CDS性能分析
    Spark SQL中列转行(UNPIVOT)的两种方法
    Spark中的一些概念
    使用Visual Studio Code进行ABAP开发
    2019年的几个目标
    Dom--样式操作
    Dom选择器--内容文本操作
    Javascript面向
  • 原文地址:https://www.cnblogs.com/kubimiantiao/p/6088339.html
Copyright © 2011-2022 走看看