公共語言運行庫(CLR)開發系列課程(2):Pinvoke 進階 學習筆記

  • 上一章地址

  • API版本

    • 具備字符串參數的API一般有兩種版本

      • GetWindowText
        • GetWindowTextA
        • GetWindowTextW
    • 缺省狀況下CLR會自動尋找合適的匹配

      • CharSet=Ansi時尋找A版本
      • CharSet=Unicode尋找W版本
      • CharSet=Auto和平臺相關
    • DLLImportAttribute(ExactSpelling=true)會關掉這種行爲

  • Ref/Out參數 (C#關鍵字)

    • out=[Out]&

      • C#:out int a
      • IL:[Out] int &a
      • C++:int *pa;
    • ref=[In,Out]&

      • C#:ref int a
      • IL:[In,Out] int &a
      • C++:int *pa;
    • 我的感受 使用Ref/Out 參數P/Invoke CRL會對根據對應關鍵字添加對應IL特性 System.Runtime.InteropServices下面的InAttribute、OutAttribute   

  • 引用類型和值類型

    • 非託管C/C++ 代碼:struct A{int a;} html

      託管代碼:struct A{int a;}  class A{int a;}
    • 非託管C/C++代碼 A A* A**
      A爲值類型 A ref A IntPtr  a
      A爲引用類型 N/A A ref A

 

 

 

  • 自定義參數轉換

    • 託管類型和非託管類型之間是一對多關係

      • String
        • char */wchar_t *  
        • char[N]/wchar_t[N] 固定長度的字符串
        • BSTR, Ansi BSTR
    • MarshalAsAttribute(UnmanagedType) 
    • UnmanagedType 枚舉指定對應非託管類型
    • [MarshalAsAttribute(UnmanagedType.LPWSTR)] string A
    • 非託管函數:void Func(wchar_t *)
    • 託管函數:Public static extern void  Func([MarshalAs(UnmanagdType.LPWSTR)] string);
  • 內存管理

    • 在數據轉換中須要進行內存分配

    • 舉例:

      • C#調用Func(string)      Marshal.StringToHGlobalAnsi("balabala")
      • C#調用Func(ref string)
      • C#調用string Func()
    • CoTaskMemAlloc / CoTaskMemFree (分配/釋放 內存) 內存交換才使用  

  • 實例分析CreateProcess

    • API原型    

      BOOL WINAPI CreateProcess(
        _In_opt_    LPCTSTR               lpApplicationName,
        _Inout_opt_ LPTSTR                lpCommandLine,
        _In_opt_    LPSECURITY_ATTRIBUTES lpProcessAttributes,
        _In_opt_    LPSECURITY_ATTRIBUTES lpThreadAttributes,
        _In_        BOOL                  bInheritHandles,
        _In_        DWORD                 dwCreationFlags,
        _In_opt_    LPVOID                lpEnvironment,
        _In_opt_    LPCTSTR               lpCurrentDirectory,
        _In_        LPSTARTUPINFO         lpStartupInfo,
        _Out_       LPPROCESS_INFORMATION lpProcessInformation
      );

           (opt 可選的輸入參數)c++

    • C#  

        Public static extern bool CreateProcess(
           [In] string applicationName,
           [In,Out] StringBuilder commandName,
           [In] ref SECURITY_ATTRIBUTES processAttributes,
           [In] ref SECURITY_ATTRIBUTES threadAttributes,
           [In] bool inheritHandles,
           [In] uint creationFlags,
           [In] IntPtr lpEnvironment,
           [In] string currentDirectory,
           [In] ref STARTUP_INFO startupInfo,
           [Out] out PROCESS_INFORMATION processInformation
      ); api

  • DLL生命週期

    • Dll在第一次調用的時候自動加載,不會自動被釋放,加載不到或者找不到函數就會拋出異常app

    • Dll不會自動被卸載,建議作法生命一個外部調用類 DLL會伴隨外部調用類一塊兒被清理卸載函數

    • 若是須要手段管理DLL生命週期,只能手動調用LoadLibrary/GetProcAddressui

  • 逆向P/Invoke

    非託管代碼到託管代碼這種叫逆向P/Invoke
    • 部分API接收函數指針做爲參數spa

    • EnumWindows(WNDENUMPROC IpEnumFunc,LPARAM lPararm) 指針

    • .NET 程序能夠經過P/Invoke調用 EnumWindows API調試

    • CLR可以將Delegate 轉換爲函數指針,EnumWindows 來回調這個函數指針code

    • [UnmanagedFunctionPointer(CallingConvention.Winapi)]
      delegate bool EnumWindowProc(IntPtr hwnd, IntPtr lParam);
      EnumWindow(new EnumWindowProc(MyEnumWindow), IntPtr.Zero);
  • DEMO

     class Program
        { 
            /// Return Type: BOOL->int
            ///hwnd: HWND->HWND__*
            ///lParam: LPARAM->LONG_PTR->int
            [UnmanagedFunctionPointerAttribute(CallingConvention.Winapi)]
            public delegate bool WNDENUMPROC(IntPtr hwnd, IntPtr lParam);
             
            class NativeMethods 
            {
                [DllImport("User32.dll",CallingConvention=CallingConvention.Winapi)]
                public static extern int GetWindowText(IntPtr hwnd, StringBuilder text, int count);
                 
                /// Return Type: BOOL->int
                ///lpEnumFunc: WNDENUMPROC
                ///lParam: LPARAM->LONG_PTR->int
                [DllImportAttribute("user32.dll", EntryPoint = "EnumWindows")]
                [return: MarshalAsAttribute(UnmanagedType.Bool)]
                public static extern bool EnumWindows(WNDENUMPROC lpEnumFunc, int lParam); 
            }
    
            private static bool WNDENUMPROC_Callback(IntPtr hwnd, IntPtr lParam) 
            {
                StringBuilder text = new StringBuilder(255);
                NativeMethods.GetWindowText(hwnd, text, 255);
                Console.WriteLine("窗口名:" + text + ",id=" + hwnd.ToInt32() + ",lParam=" + lParam);
                return true;
            }
            static void Main(string[] args)
            {
                WNDENUMPROC myWNDENUMPROC = new WNDENUMPROC(WNDENUMPROC_Callback); 
                NativeMethods.EnumWindows(myWNDENUMPROC,22);
                Console.Read(); 
            }
             
        }
  • 常見問題

    • 拋出DllNotFoundException 

      找不到DLL通常他會到當前目錄去找 或者系統目錄去找 DLL若是實在沒法差錯可使用全路徑+dll名不推薦最好是放相對路徑

    • 拋出AccessViolationException

      訪問違規,通常是內存不能夠讀不能夠寫或者一塊兒讀一塊兒寫形成的,一般緣由參數轉換錯誤,類型不對應,長度問題。
    • 拋出EntryPointNotFoundException

      API方法入口找不到通常都是方法名寫錯或者調用協定不對
    • MDA報錯StackImbalanceMDA

      VS提示報錯,參數錯位調用協定不對
    • 返回數據錯誤

      參數是否正確,MarshalAs轉換是否成功,開啓非託管調試查看。
  • 進階DEMO

    • c++
      #include <stdio.h>
      #include <stdlib.h>
      #include <string.h>
      #include <Windows.h>
      /*自定義*/
      typedef struct Mytype
      {
          int i;
          char *s;
          double d;
          struct Mytype *p;
      }Mytype;
      
      int TestInt(int a,int b)
      {
          return a + b;
      }
      void TestIntPtr(int *i)
      {
          *i = *i * -1;
      }
      char * TestCharPtr(char *a,char *b)
      {
          return strcat(a, b);
      }
      void TestStructPtr(Mytype *p)
      {
          p->i++;
          p->d++;
      }
      void TestPtrPtr(int **a,int length)
      {
          *a = (int*)malloc(sizeof(int)*length);
          memset(*a, 0, sizeof(int)* length);
      }
      void TestArray(int *a, int length)
      {
          for (int i = 0; i < length; i++)
          {
              a[i]++;
          }
      }
      
      void TestCallback(void(*pf)())
      {
          pf();
      }
      int TestCharWidth(char *s)
      {
          return strlen(s);
      }

      文件名 Source.cpp
      LIBRARY "PInvoke"
          EXPORTS
              TestInt
              TestIntPtr
              TestCharPtr
              TestStructPtr
              TestPtrPtr
              TestArray
              TestCallback
              TestCharWidth
      文件名 Source.def

       

    • C#
       class Program
          {
      
              [StructLayoutAttribute(LayoutKind.Sequential)]
              public struct Mytype
              {
      
                  /// int
                  public int i;
      
                  /// char*
                  [MarshalAsAttribute(UnmanagedType.LPStr)]
                  public string s;
      
                  /// double
                  public double d;
      
                  /// Mytype*
                  public System.IntPtr p;
              }
      
              /// Return Type: void 
              public delegate void Callback();
      
              static void CallbackFunction()
              {
                  Console.WriteLine("callback invoked");
              }
              /// Return Type: int
              ///a: int
              ///b: int
              [DllImportAttribute("Pinvoke.dll", EntryPoint = "TestInt", CallingConvention = CallingConvention.Cdecl)]
              public static extern int TestInt(int a, int b);
      
      
              /// Return Type: void
              ///i: int*
              [DllImportAttribute("Pinvoke.dll", EntryPoint = "TestIntPtr", CallingConvention = CallingConvention.Cdecl)]
              public static extern void TestIntPtr(ref int i);
      
      
              /// Return Type: char*
              ///a: char*
              ///b: char*
              [DllImportAttribute("Pinvoke.dll", EntryPoint = "TestCharPtr", CallingConvention = CallingConvention.Cdecl)]
              public static extern System.IntPtr TestCharPtr(System.IntPtr a, System.IntPtr b);
      
      
              /// Return Type: void
              ///p: Mytype*
              [DllImportAttribute("Pinvoke.dll", EntryPoint = "TestStructPtr", CallingConvention = CallingConvention.Cdecl)]
              public static extern void TestStructPtr(ref Mytype p);
      
      
              /// Return Type: void
              ///a: int**
              ///length: int
              [DllImportAttribute("Pinvoke.dll", EntryPoint = "TestPtrPtr", CallingConvention = CallingConvention.Cdecl)]
              public static extern void TestPtrPtr(ref System.IntPtr a, int length);
      
      
              /// Return Type: void
              ///a: int*
              ///length: int
              [DllImportAttribute("Pinvoke.dll", EntryPoint = "TestArray", CallingConvention = CallingConvention.Cdecl)]
              public static extern void TestArray(int[] a, int length);
      
      
              /// Return Type: void
              ///pf: Anonymous_5afb5371_1680_4be9_99a9_ab5bd7ded029
              [DllImportAttribute("Pinvoke.dll", EntryPoint = "TestCallback", CallingConvention = CallingConvention.Cdecl )]
              public static extern void TestCallback(Callback pf);
      
      
              /// Return Type: int
              ///s: char*
              [DllImportAttribute("Pinvoke.dll", EntryPoint = "TestCharWidth", CallingConvention = CallingConvention.Cdecl)]
              public static extern int TestCharWidth( IntPtr s);
      
               
      
              static void Main(string[] args)
              {
                 
      
                  Console.WriteLine("TestInt:" + TestInt(11, 2));
      
                  // TestIntPtr
                  int i = 22;
                  TestIntPtr(ref i);
                  Console.WriteLine("TestIntPtr:" + i);
      
      
      
                  // TestCharPtr
                  IntPtr helloPtr = Marshal.StringToHGlobalAnsi("啊阿斯達斯的阿薩德+");
                  IntPtr worldPtr = Marshal.StringToHGlobalAnsi("+阿薩德阿薩德");
                  IntPtr helloWorldPtr = TestCharPtr(helloPtr, worldPtr);
                  string helloWorld = Marshal.PtrToStringAnsi(helloWorldPtr);
                  Console.WriteLine("TestCharPtr:" + helloWorld);
       
                  //helloPtr = Marshal.StringToHGlobalUni("啊阿斯蒂芬a");
                  //worldPtr = Marshal.StringToHGlobalUni("阿薩德阿薩德");
                  //helloWorldPtr = TestCharPtr(helloPtr, worldPtr);
                  //helloWorld = Marshal.PtrToStringUni(helloWorldPtr);
                  //Console.WriteLine("TestCharPtr01:" + helloWorld);
                //  Marshal.FreeCoTaskMem()
                  Marshal.FreeHGlobal(helloPtr);
                  Marshal.FreeHGlobal(worldPtr);
           
                  // Marshal.FreeHGlobal(helloWorldPtr);  // 由於helloWorldPtr和helloPtr指向的是同一地址,因此再次釋放會報錯
      
                  // TestCharWidth
                  string a = "a的";
                  IntPtr aPtr = Marshal.StringToHGlobalAnsi(a); // Ansi
                  int len = TestCharWidth(aPtr);
                  Console.WriteLine("TestCharWidth:" + len);
                  a = Marshal.PtrToStringAnsi(aPtr);
                  Marshal.FreeHGlobal(aPtr);
      
      
      
                  aPtr = Marshal.StringToHGlobalUni(a); // Unicode
                  len = TestCharWidth(aPtr); // 值是1,strlen沒有正確處理unicode,因此不要使用strlen測量unicode字符串的長度
                  Console.WriteLine("TestCharWidth:" + len);
                  a = Marshal.PtrToStringUni(aPtr);
                  Marshal.FreeHGlobal(aPtr);
      
                  // TestStructPtr
                  Mytype myType = new Mytype { i = 0, d = 1.1, s = a, p = IntPtr.Zero };
                  TestStructPtr(ref myType);
                  Console.WriteLine("Mytype->i:" + myType.i);
                  Console.WriteLine("Mytype->d:" + myType.d);
      
      
      
                  // TestArray
                  int[] array = new int[] { 1, 2, 3 };
                  TestArray(array, array.Length);
      
                  for (int z = 0; z < array.Length; z++)
                  {
                      Console.WriteLine("array[{0}]" + array[z], z);
                  }
      
                  // TestCallback
                  TestCallback(CallbackFunction);
      
                  Console.Read();
              }
      
      
          }
相關文章
相關標籤/搜索