引言
在Windows平台开发中,动态链接库(DLL)是代码复用和系统功能调用的重要方式。C#作为.NET平台的主力语言,提供了多种灵活的方式来调用DLL中的功能。本文将全面介绍C#中调用DLL的各种方法、技术细节和最佳实践。
一、DLL调用基础
1. P/Invoke基础调用
using System;
using System.Runtime.InteropServices;
class Program
{
// 声明DLL函数
[DllImport("user32.dll", CharSet = CharSet.Auto)]
public static extern int MessageBox(IntPtr hWnd, string text, string caption, uint type);
static void Main()
{
// 调用DLL函数
MessageBox(IntPtr.Zero, "Hello from DLL!", "Message", 0);
}
}
2. 常用DLL属性参数
属性 | 说明 | 示例 |
---|---|---|
DllImport | 指定DLL名称 | [DllImport(“kernel32.dll”)] |
CharSet | 字符集控制 | CharSet = CharSet.Auto |
CallingConvention | 调用约定 | CallingConvention = CallingConvention.StdCall |
EntryPoint | 指定入口点名称 | EntryPoint = “MyFunction” |
ExactSpelling | 精确匹配名称 | ExactSpelling = false |
SetLastError | 设置最后错误码 | SetLastError = true |
二、参数类型映射
1. 基本数据类型对应表
C/C++ 类型 | C# 类型 | 说明 |
---|---|---|
int | int | 32位整数 |
long | int | 32位整数(Windows) |
long long | long | 64位整数 |
float | float | 单精度浮点 |
double | double | 双精度浮点 |
char* | string | ANSI字符串 |
wchar_t* | string | Unicode字符串 |
void* | IntPtr | 指针类型 |
HANDLE | IntPtr | 句柄类型 |
BOOL | bool | 布尔值 |
2. 复杂类型处理
// 结构体传递
[StructLayout(LayoutKind.Sequential)]
public struct RECT
{
public int Left;
public int Top;
public int Right;
public int Bottom;
}
[DllImport("user32.dll")]
public static extern bool GetWindowRect(IntPtr hWnd, out RECT lpRect);
// 回调函数
public delegate bool EnumWindowsProc(IntPtr hWnd, IntPtr lParam);
[DllImport("user32.dll")]
public static extern bool EnumWindows(EnumWindowsProc lpEnumFunc, IntPtr lParam);
三、高级调用技术
1. 显式加载DLL(运行时加载)
using System;
using System.Runtime.InteropServices;
class Program
{
[DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
public static extern IntPtr LoadLibrary(string dllToLoad);
[DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
public static extern IntPtr GetProcAddress(IntPtr hModule, string procedureName);
[DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
public static extern bool FreeLibrary(IntPtr hModule);
// 定义委托匹配DLL函数签名
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
private delegate int MyFunctionDelegate(int x, int y);
static void Main()
{
IntPtr pDll = LoadLibrary(@"MyDll.dll");
if (pDll == IntPtr.Zero)
{
Console.WriteLine("无法加载DLL");
return;
}
IntPtr pAddressOfFunction = GetProcAddress(pDll, "MyFunction");
if (pAddressOfFunction == IntPtr.Zero)
{
Console.WriteLine("找不到函数");
FreeLibrary(pDll);
return;
}
MyFunctionDelegate myFunction = (MyFunctionDelegate)Marshal.GetDelegateForFunctionPointer(
pAddressOfFunction, typeof(MyFunctionDelegate));
int result = myFunction(5, 10);
Console.WriteLine($"结果: {result}");
FreeLibrary(pDll);
}
}
2. 处理C++类
// C++/CLI包装器方式
public ref class ManagedWrapper
{
private:
NativeClass* nativeInstance;
public:
ManagedWrapper() : nativeInstance(new NativeClass()) {}
~ManagedWrapper() { this->!ManagedWrapper(); }
!ManagedWrapper() { delete nativeInstance; }
int ManagedMethod(int param)
{
return nativeInstance->NativeMethod(param);
}
};
// C#调用
ManagedWrapper wrapper = new ManagedWrapper();
int result = wrapper.ManagedMethod(42);
四、COM组件调用
1. 通过COM Interop调用
// 添加COM引用后直接使用
using Excel = Microsoft.Office.Interop.Excel;
class Program
{
static void Main()
{
var excelApp = new Excel.Application();
excelApp.Visible = true;
Excel.Workbook workbook = excelApp.Workbooks.Add();
Excel.Worksheet worksheet = workbook.ActiveSheet;
worksheet.Cells[1, 1] = "Hello from C#";
// 释放COM对象
Marshal.ReleaseComObject(worksheet);
Marshal.ReleaseComObject(workbook);
Marshal.ReleaseComObject(excelApp);
}
}
2. 动态COM调用
Type excelType = Type.GetTypeFromProgID("Excel.Application");
dynamic excelApp = Activator.CreateInstance(excelType);
excelApp.Visible = true;
dynamic workbook = excelApp.Workbooks.Add();
dynamic worksheet = workbook.ActiveSheet;
worksheet.Cells[1, 1] = "Dynamic COM";
// 不需要显式释放,GC会自动处理
五、安全与最佳实践
- DLL安全加载
// 使用绝对路径防止DLL劫持
string fullPath = Path.GetFullPath("MyDll.dll");
IntPtr moduleHandle = LoadLibrary(fullPath);
- 错误处理
[DllImport("kernel32.dll", SetLastError = true)]
public static extern IntPtr OpenProcess(int dwDesiredAccess, bool bInheritHandle, int dwProcessId);
static void Main()
{
IntPtr hProcess = OpenProcess(0x1F0FFF, false, 1234);
if (hProcess == IntPtr.Zero)
{
int errorCode = Marshal.GetLastWin32Error();
throw new Win32Exception(errorCode);
}
}
- 性能优化
- 缓存DLL句柄和函数指针
- 减少托管/非托管转换次数
- 使用blittable类型提高封送处理效率
- 跨平台考虑
#if WINDOWS
[DllImport("kernel32.dll")]
private static extern void WindowsOnlyFunction();
#else
// 其他平台的替代实现
#endif
六、常见问题解决方案
1. 内存管理问题
// 正确释放非托管内存
[DllImport("mydll.dll")]
private static extern IntPtr AllocateBuffer(int size);
[DllImport("mydll.dll")]
private static extern void FreeBuffer(IntPtr ptr);
void UseBuffer()
{
IntPtr buffer = AllocateBuffer(1024);
try
{
// 使用buffer...
}
finally
{
FreeBuffer(buffer);
}
}
2. 字符串编码问题
// 显式指定字符串编码
[DllImport("mydll.dll", CharSet = CharSet.Unicode)]
private static extern void UnicodeFunction(string input);
[DllImport("mydll.dll", CharSet = CharSet.Ansi)]
private static extern void AnsiFunction(string input);
3. 64位/32位兼容性
// 条件编译处理平台差异
[DllImport("kernel32.dll")]
private static extern IntPtr GetModuleHandle(string lpModuleName);
#if WIN64
private const string DLL_NAME = "mydll64.dll";
#else
private const string DLL_NAME = "mydll32.dll";
#endif
结语
C#调用DLL方法是一项强大但需要谨慎使用的技术。通过本文介绍的各种方法,您应该能够:
- 熟练使用P/Invoke调用标准Windows API
- 处理复杂数据类型和回调函数
- 动态加载和调用DLL函数
- 安全高效地与COM组件交互
- 避免常见陷阱和内存问题
在实际项目中,建议:
- 优先考虑使用官方提供的.NET库而非直接调用DLL
- 为频繁调用的DLL函数创建包装类
- 编写详细的文档说明DLL依赖和调用约定
- 对关键DLL操作添加单元测试
通过合理运用这些技术,您可以在C#应用程序中充分利用丰富的Windows平台功能和现有代码库。