C#引用非托管.dll


C#里調用非托管的Dll

    今天花了一些精力來調查了一下C#里調用非托管的Dll,C#里調用非托管的Dll要使用P/Invoke平台調用技術, 這里先簡單介紹一下P/Invoke平台調用技術。
    由於開發程序轉到托管代碼,所以開發過程中會經常研究底層的一些關鍵功能,通過 P/Invoke(平台調用)即 公共語言運行庫 (CLR) 的 interop 功能,
來進行底層或者其他平台dll的調用。

C#語言聲明外部方法,基本形式是:
[DLLImport(“DLL文件”,……)]
修飾符 extern 返回變量類型 方法名稱 (參數列表)

DLL文件:包含定義外部方法的庫文件。

修飾符: 訪問修飾符,除了abstract以外在聲明方法時可以使用的修飾符。

extern:extern 修飾符用於聲明在外部實現的方法,常見用法是在使用 Interop 服務調入非托管代碼時與 DllImport 屬性一起使用

返回變量類型:在DLL文件中你需調用方法的返回變量類型。

方法名稱:在DLL文件中你需調用方法的名稱。

參數列表:在DLL文件中你需調用方法的列表。

C# 的規則之一是
它的調用語法只能訪問 CLR 數據類型,例如 System.UInt32 和 System.Boolean。
C# 顯然不識別 Windows API 中使用的基於 C 的數據類型(例如 UINT 和 BOOL),這些類型只是 C 語言類型的類型定義而已。所以當 Windows API 函數如果按以下方式編寫時
BOOL MessageBeep( UINT uType )
外部方法就必須使用 CLR 類型來定義,如前面的代碼片段中所看到的。
需要使用與基礎 API 函數類型不同但與之兼容的 CLR 類型是 P/Invoke 較難使用的一個方面。(數據封送處理)

可選的 DllImportAttribute 屬性
除了指出宿主 DLL 外,DllImportAttribute 還包含了一些可選屬性,
包括:EntryPointCharSetSetLastError CallingConvention

EntryPoint
在不希望外部托管方法具有與 DLL 導出相同的名稱的情況下,可以設置該屬性來指示導出的 DLL 函數的入口點名稱。
當定義兩個調用相同非托管函數的外部方法時,這特別有用。
另外,在 Windows 中還可以通過它們的序號值綁定到導出的 DLL 函數。
如果需要這樣做,則諸如“#1”或“#129”的 EntryPoint 值指示 DLL 中非托管函數的序號值而不是函數名,
( 通常這個很少有相同名稱)

ExactSpelling

指示 EntryPoint 是否必須與指示的入口點的拼寫完全匹配,如:ExactSpelling=false;

CharSet

指示用在入口點中的字符集,如:CharSet=CharSet.Ansi;
如果沒有顯式地設置 CharSet 屬性,則其默認值為 CharSet.Ansi。這個默認值是有缺點的,
因為對於在 Windows 2000、Windows XP 和 Windows NT 上進行的 interop 調用,它會消極地影響文本參數封送處理的性能。
如果CharSet 屬性設置為 CharSet.Auto。這樣可以使 CLR 根據宿主 OS 使用適當的字符集。
這個同時根據操作系統具體設置 例如:基於 Windows NT 的操作系統中,並且只支持 Unicode ,所以我們就要設置為 CharSet.Unicode。

SetLastError

指示方法是否保留 Win32"上一錯誤",如:SetLastError=true;
如果使用 GetLastError 來查找擴展的錯誤信息,
則應該在外部方法的 DllImportAttribute 中將 SetLastError 屬性設置為 true
這將導致 CLR 在每次調用外部方法之后緩存由 API 函數設置的錯誤
在包裝方法中,
可以通過調用類庫的 System.Runtime.InteropServices.Marshal 類型中定義的 Marshal.GetLastWin32Error 方法來獲取緩存的錯誤值

PreserveSig

指示方法的簽名應當被保留還是被轉換, 如:PreserveSig=true;

CallingConvention

指示入口點的調用約定, 如:CallingConvention=CallingConvention.Winapi;
通過此屬性,可以給 CLR 指示應該將哪種函數調用約定用於堆棧中的參數。
CallingConvention.Winapi 的默認值是最好的選擇,它在大多數情況下都可行
在 c,c++運行時 DLL 函數和少數函數中,可能需要將約定更改為 CallingConvention.Cdecl。

“數據封送處理”及“封送數字和邏輯標量”

這個我在學習當中,如果有興趣的話,可以更加深入。

下面舉了一個簡單的 P/Invoke 示例

c# 調用 c++  .dll 】
首先需要添加using System.Runtime.InteropServices; //交互服務的命名空間
靜態加載.dll
   1. 首先把被加載.dll拷貝到運行目標bin/debug目錄下(如果路徑要求不被限制,就可以選擇動態加載)
   2.  聲明調用方法名
   [DllImport("xxxx.dll", EntryPoint = "xxx方法名xxx", ExactSpelling = false, CallingConvention = CallingConvention.Cdecl)]
   public static extern int OperatePlus(int a, int b); 
   3.執行調用
       static void Main(string[] args)
       {
           Console.WriteLine(OperatePlus(100, 102));
           Console.Read();
       }
   4.靜態調用成功
動態加載.dll
    1. 
        聲明調用kernel32.dll中調用方法
        /// <summary>
        /// 裝載動態庫
        /// </summary>
        /// <param name="lpLibFileName">DLL 文件名</param>
        /// <returns>函數庫模塊的句柄 </returns>
        [DllImport("kernel32.dll", EntryPoint = "LoadLibrary")]
        public static extern int LoadLibrary(
            [MarshalAs(UnmanagedType.LPStr)] string lpLibFileName);

        /// <summary>
        /// 獲取要引入的函數,將符號名或標識號轉換為DLL內部地址。
        /// </summary>
        /// <param name="hModule">包含需調用函數的函數庫模塊的句柄</param>
        /// <param name="lpProcName">調用函數的名稱</param>
        /// <returns>函數指針</returns>
        [DllImport("kernel32.dll", EntryPoint = "GetProcAddress")]
        public static extern IntPtr GetProcAddress(int hModule,
            [MarshalAs(UnmanagedType.LPStr)] string lpProcName);

        /// <summary>
        /// 釋放動態鏈接庫。
        /// </summary>
        /// <param name="hModule">需釋放的函數庫模塊的句柄</param>
        /// <returns>是否已釋放指定的 Dll</returns>
        [DllImport("kernel32.dll", EntryPoint = "FreeLibrary")]
        public static extern bool FreeLibrary(int hModule);
      
    2. 聲明委托        

    /// <summary>
        ///函數指針封裝成委托
        /// </summary>
        /// <param name="a"></param>
        /// <param name="b"></param>
        /// <returns></returns>
        [UnmanagedFunctionPointerAttribute(CallingConvention.Cdecl)] //控制作為非托管函數指針傳入或傳出非托管代碼的委托簽名的封送行為
        delegate int OperatePlus(int a, int b);
        
   3. 調用聲明的方法
        static void Main(string[] args)
        {
            int hModule = LoadLibrary(@"path\xxx.dll");
            if (hModule == 0)
                return;
            IntPtr intPtr = GetProcAddress(hModule, "xxx方法名xxx");
            //xxx委托名xxx OperatePlusFunction = (xxx委托名xxx )Marshal.GetDelegateForFunctionPointer(intPtr, typeof(xxx委托名xxx ));//函數指針封裝成委托
            OperatePlus OperatePlusFunction = (OperatePlus)Marshal.GetDelegateForFunctionPointer(intPtr, typeof(OperatePlus));//函數指針封裝成委托
            Console.WriteLine(OperatePlusFunction(2, 2));
            Console.Read();
        }
       
  同樣C#中調用Delphi.dll c.dll 等這些操作方式應該是一樣。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM