C#调用DLL方法完全指南


引言

在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# 类型说明
intint32位整数
longint32位整数(Windows)
long longlong64位整数
floatfloat单精度浮点
doubledouble双精度浮点
char*stringANSI字符串
wchar_t*stringUnicode字符串
void*IntPtr指针类型
HANDLEIntPtr句柄类型
BOOLbool布尔值

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会自动处理

五、安全与最佳实践

  1. DLL安全加载
   // 使用绝对路径防止DLL劫持
   string fullPath = Path.GetFullPath("MyDll.dll");
   IntPtr moduleHandle = LoadLibrary(fullPath);
  1. 错误处理
   [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);
       }
   }
  1. 性能优化
  • 缓存DLL句柄和函数指针
  • 减少托管/非托管转换次数
  • 使用blittable类型提高封送处理效率
  1. 跨平台考虑
   #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方法是一项强大但需要谨慎使用的技术。通过本文介绍的各种方法,您应该能够:

  1. 熟练使用P/Invoke调用标准Windows API
  2. 处理复杂数据类型和回调函数
  3. 动态加载和调用DLL函数
  4. 安全高效地与COM组件交互
  5. 避免常见陷阱和内存问题

在实际项目中,建议:

  • 优先考虑使用官方提供的.NET库而非直接调用DLL
  • 为频繁调用的DLL函数创建包装类
  • 编写详细的文档说明DLL依赖和调用约定
  • 对关键DLL操作添加单元测试

通过合理运用这些技术,您可以在C#应用程序中充分利用丰富的Windows平台功能和现有代码库。


发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注