PInvoke——一個強大而又不太為人所知的編程技術

隨著軟體開發的不斷進步,我們今天可以做到的事情昨天或者是前幾年是不可能的,但是很多時候這些進步並不為我們所知,而PInvoke就是其中一個值得我們深入了解的技術。

一、PInvoke庫

PInvoke(Platform Invoke) 是一個強有力的 .NET Framework 功能,它提供了一種在 .NET 應用程序中調用非託管 DLL 函數的方法。PInvoke 的一項主要功能就是能夠讓 .NET Framework 應用程序直接使用基於 C 或 C++ 的非託管代碼,而這些代碼通常是通過相應 DLL 導出的。

在實際編程中,我們會遇到很多需要調用非託管代碼的情況。比如,我們需要調用操作系統的 API 功能,調用硬體驅動程序的函數等等。這些非託管代碼往往是以 C 或 C++ 等語言編寫的,它們不受 .NET Framework 的管理,與 .NET 應用程序的運行環境也是非常不同的。因此,我們在使用這些非託管代碼時,需要藉助 PInvoke 技術,實現 .NET 應用程序與這些非託管代碼之間的互操作。


[DllImport("User32.dll")]
static extern int MessageBox(int h, string m, string c, int type);

以上是一個C# 調用MessageBox的示例,包含DllImport Attribute,它用於指定需要哪個動態鏈接庫(DLL)和其內部函數,在應用程序中使用 unmanaged API 。使用 DllImport 可以達到以下目的:

  • 將參數從一個數據類型轉化到另一個數據類型
  • 指定 DLL 文件和函數的入口點,因為它們是 unmanaged 的
  • 返回調用相關 API 函數的結果。

二、PInvoke gdi32

GDI+是圖形設備界面(GDI)內置圖形渲染引擎的後續版本,最近的版本是 DirectX。

下面的 P/Invoke 函數用於打開並顯示圖像文件:


[DllImport("gdi32.dll")]
public static extern bool DeleteObject(IntPtr hDc);
 
public static void DrawImg()
{
    OpenFileDialog openFileDialog = new OpenFileDialog();
    openFileDialog.Filter = "Image Files(*.bmp;*.jpg;*.jpeg;*.png;)|*.bmp;*.jpg;*.jpeg;*.png;";//過濾器
    if (openFileDialog.ShowDialog() == DialogResult.OK)
    {
        using (Bitmap bitmap = new Bitmap(openFileDialog.FileName))
        {
            IntPtr hBitmap = bitmap.GetHbitmap();
            try
            {
                using(Graphics g = Graphics.FromHwnd(IntPtr.Zero))
                {
                    IntPtr hDC = g.GetHdc();
                    try
                    {
                        using( Graphics imageGrpahics = Graphics.FromImage(bitmap))
                        {
                            IntPtr hdcImage = imageGrpahics.GetHdc();
                            try
                            {
                                BitBlt(hDC, 0, 0, bitmap.Width, bitmap.Height, hdcImage, 0, 0, SrcCopy);
                            }
                            finally
                            {
                                imageGrpahics.ReleaseHdc(hdcImage);
                            }
                        }
                    }
                    finally
                    {
                        g.ReleaseHdc(hDC);
                    }
                }
            }
            finally
            {
                DeleteObject(hBitmap);
            }
        }
    }
}

以上代碼可以在 openfiledialog 中選取圖片文件,並將其顯示在程序界面中。

三、PInvoke DLL

P/Invoke 在非託管代碼中調用託管(.Net)代碼,通過 C++/CLI 支持實現混合編程,通過導出 C++ 靜態類導出成非託管 DLL 供其他工程(包括非 .Net 工程)引用,從而實現託管代碼的動態鏈接。

下面是一個使用 P/Invoke 的示例:

在 Visual Studio 中,創建一個新的 C++ CLR 項目,我們可以編寫以下代碼導出一個以 System.String 為參數,返回 System.Int32 的函數 Multiply:


#include "pch.h"
#include "tchar.h" 
 
using namespace System;
 
namespace ExampleCppDll
{
    public ref class CppWrapper
    {
    public:
        static int Multiply(String^ operand)
        {
            try
            {
                int i_Result = 1;
                for each(Char ch in operand)
                {
                    i_Result *= Convert::ToInt32(ch.ToString());
                }
                return i_Result;
            }
            catch(...)
            {
                return -1;
            }
        }
     };
}
 
extern "C"
{
    __declspec(dllexport) 
    int __stdcall Multiply(LPCTSTR operand)
    {
        return ExampleCppDll::CppWrapper::Multiply(gcnew String(operand));
    }
}

然後編譯生成 DLL ,在C# 中,我們可以使用 P/Invoke 調用剛剛寫的 Multiply 函數:


[DllImport("ExampleCppDll.dll", CharSet = CharSet.Unicode)]
public static extern int Multiply(string strNumbers); 

四、PInvoke vb6

通過使用 PInvoke 在 VB6 中調用 unmanaged 代碼。

下面是一個使用 PInvoke 的示例:


Private Declare Function GetVersionEx Lib "kernel32" Alias "GetVersionExA" ( _
    ByRef lpVersionInformation As OSVERSIONINFO _
) As Long
 
Private Type OSVERSIONINFO
    dwOSVersionInfoSize As Long
    dwMajorVersion As Long
    dwMinorVersion As Long
    dwBuildNumber As Long
    dwPlatformId As Long
    szCSDVersion As String * 128       ' Size of string buffer in Bytes
End Type
 
Private Sub Form_Load()
    Dim os_version As OSVERSIONINFO
    os_version.dwOSVersionInfoSize = LenB(os_version)
    Call GetVersionEx(os_version)
    Debug.Print "Windows version: " & os_version.dwMajorVersion & "." & os_version.dwMinorVersion
End Sub 

以上代碼可以在 VB6 中調用 GetVersionEx 函數,獲取 Windows 版本信息,並輸出到調試窗口中。

五、PInvoke文檔

P/Invoke 的參考文檔支持許多方面:

  • MDSN 使用 MSDN Library 搜索或 MSDN 中的 P/Invoke 索引
  • Pinvoke.net 它是開發人員社區,提供 P/Invoke 不斷更新的文檔庫
  • PInvoke Interop Assistant 可以幫助您將 C、C 和 C++ 頭文件轉換為 C# PInvoke 或 VB declarative DLL 函數聲明。

六、PInvoke技術

你可以通過PInvoke 技術將合適的編程語言組合實現所需的操作,同時明白一些坑點。

以下是一些 PInvoke 技術示例:

  • 使用 PInvoke 和 Callback 委託將 Windows 程序代碼轉換為 C# 中的代碼
  • 在嵌入式 Windows CE 中使用 PInvoke 和 .Net Compact Framework / Windows Mobile
  • 在 WPF 中使用 PInvoke、SetWindowLong 和 HwndSource 實現 WindowTransparency
  • PCreate ProcessAsUser with SeIncreaseQuotaPrivilege

七、PInvokeStackImbalance

PInvokeStackimbalance 是對於 P/Invoke 傳遞參數而產生的錯誤類型的名字,主要由於目標函數參數數目或類型不匹配而導致。針對該錯誤,可以通過調整P/Invoke 聲明中的參數數量或類型來解決。

以下是解決棧平衡錯誤的 P/Invoke 聲明示例:


[DllImport("User32.dll")]
public static extern bool MoveWindow(IntPtr hWnd, int X, int Y, int nWidth, int nHeight, bool bRepaint);

在調用 MoveWindow 時,需要設置第 5 個參數為 bool 類型而不是 int 類型,以下是正確的聲明:


[DllImport("User32.dll")]
public static extern bool MoveWindow(IntPtr hWnd, int X, int Y, int nWidth, bool bRepaint);

八、PInvoke openfiledialog

PInvoke OpenFileDialog

以下是 P/Invoke OpenFileDialog 的示例代碼:


[DllImport("user32.dll", CharSet = CharSet.Auto)]
static extern IntPtr GetForegroundWindow();
 
[STAThread]
static void Main()
{
    var opDlg= new OpenFileDialog();
    opDlg.ShowDialog(GetForegroundWindow);
}

以上代碼通過 P/Invoke OpenFileDialog 在提示框架之間提供彈出式窗口。

結論

通過 PInvoke,在非託管代碼中調用託管代碼,我們可以更簡單的處理 unmanaged 代碼。無論我們想在 .Net 中調用現有的 C/C++ 動態鏈接庫或使 C# 代碼跨越語言邊界工作,在經驗和技術上,P/Invoke 都是不可或缺的一項功能。

原創文章,作者:小藍,如若轉載,請註明出處:https://www.506064.com/zh-tw/n/282604.html

(0)
打賞 微信掃一掃 微信掃一掃 支付寶掃一掃 支付寶掃一掃
小藍的頭像小藍
上一篇 2024-12-22 08:05
下一篇 2024-12-22 08:05

相關推薦

發表回復

登錄後才能評論