程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> .NET網頁編程 >> C# >> C#入門知識 >> C# 程序員參考--平台調用教學文章

C# 程序員參考--平台調用教學文章

編輯:C#入門知識

平台調用服務 (PInvoke) 允許托管代碼調用在 DLL 中實現的非托管函數。

本教程說明使用什麼方法才能從 C# 調用非托管 DLL 函數。該教程所討論的屬性允許您調用這些函數並使數據類型得到正確封送。

教程

C# 代碼有以下兩種可以直接調用非托管代碼的方法:

  • 直接調用從 DLL 導出的函數。
  • 調用 COM 對象上的接口方法(有關更多信息,請參見 COM Interop 第一部分:C# 客戶端教程)。

對於這兩種技術,都必須向 C# 編譯器提供非托管函數的聲明,並且還可能需要向 C# 編譯器提供如何封送與非托管代碼之間傳遞的參數和返回值的說明。

該教程由下列主題組成:

  • 直接從 C# 調用 DLL 導出
  • 默認封送處理和為非托管方法的參數指定自定義封送處理
  • 為用戶定義的結構指定自定義封送處理
  • 注冊回調方法

該教程包括下列示例:

  • 示例 1 使用 DllImport
  • 示例 2 重寫默認封送處理
  • 示例 3 指定自定義封送處理

直接從 C# 調用 DLL 導出

若要聲明一個方法使其具有來自 DLL 導出的實現,請執行下列操作:

  • 使用 C# 關鍵字 staticextern 聲明方法。
  • DllImport 屬性附加到該方法。DllImport 屬性允許您指定包含該方法的 DLL 的名稱。通常的做法是用與導出的方法相同的名稱命名 C# 方法,但也可以對 C# 方法使用不同的名稱。
  • 還可以為方法的參數和返回值指定自定義封送處理信息,這將重寫 .NET Framework 的默認封送處理。

示例 1

本示例顯示如何使用 DllImport 屬性通過調用 msvcrt.dll 中的 puts 輸出消息。

// PInvokeTest.cs



using System;



using System.Runtime.InteropServices;







class PlatformInvokeTest



{



    [DllImport("msvcrt.dll")]



    public static extern int puts(string c);



    [DllImport("msvcrt.dll")]



    internal static extern int _flushall();







    public static void Main() 



    {



        puts("Test");



        _flushall();



    }



}

輸出

Test

代碼討論

前面的示例顯示了聲明在非托管 DLL 中實現的 C# 方法的最低要求。PlatformInvokeTest.puts 方法用 staticextern 修飾符聲明並且具有 DllImport 屬性,該屬性使用默認名稱 puts 通知編譯器此實現來自 msvcrt.dll。若要對 C# 方法使用不同的名稱(如 putstring),則必須在 DllImport 屬性中使用 EntryPoint 選項,如下所示:

[DllImport("msvcrt.dll", EntryPoint="puts")]

有關 DllImport 屬性的語法的更多信息,請參見 DllImportAttribute 類。

默認封送處理和為非托管方法的參數指定自定義封送處理

當從 C# 代碼中調用非托管函數時,公共語言運行庫必須封送參數和返回值。

對於每個 .NET Framework 類型均有一個默認非托管類型,公共語言運行庫將使用此非托管類型在托管到非托管的函數調用中封送數據。例如,C# 字符串值的默認封送處理是封送為 LPTSTR(指向 TCHAR 字符緩沖區的指針)類型。可以在非托管函數的 C# 聲明中使用 MarshalAs 屬性重寫默認封送處理。

示例 2

本示例使用 DllImport 屬性輸出一個字符串。它還顯示如何通過使用 MarshalAs 屬性重寫函數參數的默認封送處理。

// Marshal.cs



using System;



using System.Runtime.InteropServices;







class PlatformInvokeTest



{



    [DllImport("msvcrt.dll")]



    public static extern int puts(



        [MarshalAs(UnmanagedType.LPStr)]



        string m);



    [DllImport("msvcrt.dll")]



    internal static extern int _flushall();











    public static void Main() 



    {



        puts("Hello World!");



        _flushall();



  

[1] [2] [3] 下一頁  

} }

輸出

運行此示例時,字符串

Hello World!

將顯示在控制台上。

代碼討論

在前面的示例中,puts 函數的參數的默認封送處理已從默認值 LPTSTR 重寫為 LPSTR。

MarshalAs 屬性可以放置在方法參數、方法返回值以及結構和類的字段上。若要設置方法返回值的封送處理,請將 MarshalAs 屬性與返回屬性位置重寫一起放置在方法上的屬性塊中。例如,若要顯式設置 puts 方法返回值的封送處理:

...



[DllImport("msvcrt.dll")] 



[return : MarshalAs(UnmanagedType.I4)]



public static extern int puts( 



...

有關 MarshalAs 屬性的語法的更多信息,請參見 MarshalAsAttribute 類。

注意   InOut 屬性可用於批注非托管方法的參數。它們與 MIDL 源文件中的 inout 修飾符的工作方式類似。請注意,Out 屬性與 C# 參數修飾符 out 不同。有關 InOut 屬性的更多信息,請參見 InAttribute 類和 OutAttribute 類。

為用戶定義的結構指定自定義封送處理

可以為傳遞到非托管函數或從非托管函數返回的結構和類的字段指定自定義封送處理屬性。通過向結構或類的字段中添加 MarshalAs 屬性可以做到這一點。還必須使用 StructLayout 屬性設置結構的布局,還可以控制字符串成員的默認封送處理,並設置默認封裝大小。

示例 3

本示例說明如何為結構指定自定義封送處理屬性。

請考慮下面的 C 結構:

typedef struct tagLOGFONT 



{ 



   LONG lfHeight; 



   LONG lfWidth; 



   LONG lfEscapement; 



   LONG lfOrientation; 



   LONG lfWeight; 



   BYTE lfItalic; 



   BYTE lfUnderline; 



   BYTE lfStrikeOut; 



   BYTE lfCharSet; 



   BYTE lfOutPrecision; 



   BYTE lfClipPrecision; 



   BYTE lfQuality; 



   BYTE lfPitchAndFamily; 



   TCHAR lfFaceName[LF_FACESIZE]; 



} LOGFONT; 

在 C# 中,可以使用 StructLayoutMarshalAs 屬性描述前面的結構,如下所示:

// logfont.cs



// compile with: /target:module



using System;



using System.Runtime.InteropServices;







[StructLayout(LayoutKind.Sequential)]



public class LOGFONT 



{ 



    public const int LF_FACESIZE = 32;



    public int lfHeight; 



    public int lfWidth; 



    public int lfEscapement; 



    public int lfOrientation; 



    public int lfWeight; 



    public byte lfItalic; 



    public byte lfUnderline; 



    public byte lfStrikeOut; 



    public byte lfCharSet; 



    public byte lfOutPrecision; 



    public byte lfClipPrecision; 



    public byte lfQuality; 



    public byte lfPitchAndFamily;



    [MarshalAs(UnmanagedType.ByValTStr, SizeConst=LF_FACESIZE)]



    public string lfFaceName; 



}

有關 StructLayout 屬性的語法的更多信息,請參見 StructLayoutAttribute 類。

然後即可將該結構用在 C# 代碼中,如下所示:

// pinvoke.cs



// compile with: /addmodule:logfont.netmodule



using System;



using System.Runtime.InteropServices;



 



class PlatformInvokeTest



{   



      [DllImport("gdi32.dll", CharSet=CharSet.Auto)]



      public static extern IntPtr CreateFontIndirect(



            [In, MarshalAs(UnmanagedType.LPStruct)]



            LOGFONT lplf   // characteristics



            );



 



      [DllImport("gdi32.dll")]



      public static extern bool DeleteObject(



            IntPtr handle



            );



 



      public static void Main() 



      {



            LOGFONT lf = new LOGFONT();



            lf.lfHeight = 9;



            lf.lfFaceName = "Arial"

上一頁  [1] [2] [3] 下一頁  

; IntPtr handle = CreateFontIndirect(lf); if (IntPtr.Zero == handle) { Console.WriteLine("Can't creates a logical font."); } else { if (IntPtr.Size == 4) Console.WriteLine("{0:X}", handle.ToInt32()); else Console.WriteLine("{0:X}", handle.ToInt64()); // Delete the logical font created. if (!DeleteObject(handle)) Console.WriteLine("Can't delete the logical font"); } } }

運行示例

C30A0AE5

代碼討論

在前面的示例中,CreateFontIndirect 方法使用了一個 LOGFONT 類型的參數。MarshalAsIn 屬性用於限定此參數。程序將由此方法返回的數值顯示為十六進制大寫字符串。

注冊回調方法

若要注冊調用非托管函數的托管回調,請用相同的參數列表聲明一個委托並通過 PInvoke 傳遞它的一個實例。在非托管端,它將顯示為一個函數指針。有關 PInvoke 和回調的更多信息,請參見平台調用詳解。

例如,考慮以下非托管函數 MyFunction,此函數要求 callback 作為其參數之一:

typedef void (__stdcall *PFN_MYCALLBACK)();



int __stdcall MyFunction(PFN_ MYCALLBACK callback);

若要從托管代碼調用 MyFunction,請聲明該委托,將 DllImport 附加到函數聲明,並根據需要封送任何參數或返回值:

public delegate void MyCallback();



[DllImport("MYDLL.DLL")]



public static extern void MyFunction(MyCallback callback);

同時,請確保委托實例的生存期覆蓋非托管代碼的生存期;否則,委托在經過垃圾回收後將不再可用。

 

上一頁  [1] [2] [3] 

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved