公共語言運行庫(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 條指令,算起來開銷也很多,因此咱們要儘可能少調用事務
  若是非用不可,建議本着一次調用執行多個動做,而不是屢次調用每次只執行少許動做的原則c++

  • 爲何要使用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).c#

        dwDuration [in]
        Return value

        If the function succeeds, the return value is nonzero.windows

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

        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++。app

  • 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簡介》 百度能夠搜索到相關學習資料 我收集資料並不全官方不提供下載了 如今只有視頻提供下載 第三方地址 學習

相關文章
相關標籤/搜索