隨著軟體開發的不斷進步,我們今天可以做到的事情昨天或者是前幾年是不可能的,但是很多時候這些進步並不為我們所知,而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