随着软件开发的不断进步,我们今天可以做到的事情昨天或者是前几年是不可能的,但是很多时候这些进步并不为我们所知,而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 都是不可或缺的一项功能。