C# DllImport调用非托管DLL完整指南及动态加载方案

码头散步
码头散步
发布时间:2026-10-14| 3人浏览过
原创

Dllimport是C#进行平台调用(P/Invoke)的核心属性,用于连接托管代码与非托管的动态链接库。本文将深入解析其用法、常见陷阱,并提供一种在Web环境下动态加载任意路径DLL的稳健方案。

DllImport基本语法与核心参数

DllImportAttribute 位于 System.Runtime.InteropServices 命名空间下。使用该属性修饰的方法必须同时使用 extern 修饰符。调用者至少需要提供包含入口点的 DLL 名称。

其定义结构如下:

[AttributeUsage(AttributeTargets.Method)]
public class DllImportAttribute : System.Attribute
{
    public DllImportAttribute(string dllName); // 必需:DLL文件名

    public CallingConvention CallingConvention; // 调用约定,默认 Winapi
    public CharSet CharSet;                     // 字符集,默认 Auto
    public string EntryPoint;                 // 入口点名称,默认同方法名
    public bool ExactSpelling;                // 是否精确匹配拼写,默认 false
    public bool PreserveSig;                  // 保留签名还是转换,默认 true
    public bool SetLastError;                 // 是否保留Win32错误码,默认 false
}

第1步:声明非托管方法

首先需要引入命名空间并使用静态外部方法声明。以下是一个调用 Windows API 写入 INI 文件的示例:

using System.Runtime.InteropServices;

public class IniHelper
{
    // 声明 kernel32.dll 中的 WritePrivateProfileString 函数
    [DllImport("kernel32")]
    private static extern long WritePrivateProfileString(
        string section,
        string key,
        string val,
        string filePath);
}

数据类型映射规则

C# 与 C/C++ 之间的数据交互需要遵循特定的映射关系,否则会导致内存错误或程序崩溃:

  • DWORD / unsigned int: 映射为 C# 的 int 或 uint。
  • BOOL: 映射为 C# 的 bool。
  • 预定义常量: 建议使用 enum 提高可读性。
  • 结构体 (struct): 需标记 [StructLayout(LayoutKind.Sequential)] 以保持内存布局一致。

DLL 搜索路径与加载限制

当仅传递 dllName 而不提供绝对路径时,CLR 会按以下顺序搜索 DLL:

  1. 可执行文件(.exe)所在目录。
  2. 系统目录(如 System32)。
  3. 环境变量 PATH 中列出的目录。

ASP.NET 环境下的特殊挑战

在 ASP.NET 应用程序中,直接使用相对路径或 Server.MapPath 作为 DllImport 的参数往往无效。这是因为:
1. DllImport 只能接受编译时常量字符串。
2. IIS 将应用部署在临时目录下运行,且 CLR 默认只拷贝托管程序集到 bin 目录,非托管 DLL 若放在 bin 下可能被忽略或导致“模块找不到”错误。

虽然可以将 DLL 放置在 System32 或修改服务器 PATH 环境变量,但这在虚拟主机环境中通常不可行。

替代方案:动态加载非托管DLL

为了解决路径不确定和权限受限的问题,可以使用 LoadLibrary 和 GetProcAddress API 动态加载 DLL,并结合委托(Delegate)进行调用。这种方法允许使用 Server.MapPath 获取物理路径。

第2步:构建通用 DLL 加载类

创建一个封装了 Windows API 调用的工具类,负责加载 DLL、获取函数地址并生成委托。

using System;
using System.Runtime.InteropServices;
using System.Reflection;

public class DllInvoke
{
    // 加载库
    [DllImport("kernel32.dll", CharSet = CharSet.Auto)]
    private static extern IntPtr LoadLibrary(string path);

    // 获取函数地址
    [DllImport("kernel32.dll", CharSet = CharSet.Ansi)]
    private static extern IntPtr GetProcAddress(IntPtr lib, String funcName);

    // 释放库
    [DllImport("kernel32.dll", CharSet = CharSet.Auto)]
    private static extern bool FreeLibrary(IntPtr lib);

    private IntPtr hLib;

    // 构造函数:加载指定路径的 DLL
    public DllInvoke(String DLLPath)
    {
        hLib = LoadLibrary(DLLPath);
        if (hLib == IntPtr.Zero)
        {
            throw new Exception($"Failed to load DLL: {DLLPath}");
        }
    }

    // 析构函数:自动释放资源
    ~DllInvoke()
    {
        if (hLib != IntPtr.Zero)
            FreeLibrary(hLib);
    }

    /// <summary>
    /// 根据函数名获取对应类型的委托
    /// </summary>
    /// <param name="APIName">DLL 中的函数名</param>
    /// <param name="t">委托类型</param>
    /// <returns>委托实例</returns>
    public Delegate Invoke(String APIName, Type t)
    {
        IntPtr api = GetProcAddress(hLib, APIName);
        if (api == IntPtr.Zero)
        {
            throw new Exception($"Function '{APIName}' not found in loaded DLL.");
        }
        return Marshal.GetDelegateForFunctionPointer(api, t);
    }
}

第3步:定义委托并执行调用

你需要根据目标 DLL 导出的函数签名,定义对应的 C# 委托。注意委托的参数和返回类型必须与非托管函数的签名严格一致。

// 假设 Judge.dll 中有一个名为 Compile 的函数:
// int Compile(char* command, char* outputBuffer, int bufferSize)
// 为了简化演示,这里定义一个简单的委托

public delegate int CompileFunc(string command, StringBuilder info);

public void CallNativeMethod()
{
    // 1. 获取 DLL 的物理路径(适用于 Web 环境)
    // 注意:在实际生产环境中,建议缓存此路径以避免重复计算
    string dllPath = Server.MapPath(@"~/Bin/Judge.dll");

    try
    {
        // 2. 初始化加载器
        using (var loader = new DllInvoke(dllPath))
        {
            // 3. 将 DLL 中的 'Compile' 函数转换为委托
            // 类型需完全匹配:返回值 int,参数 string, StringBuilder
            CompileFunc compile = (CompileFunc)loader.Invoke("Compile", typeof(CompileFunc));

            // 4. 执行调用
            StringBuilder resultInfo = new StringBuilder(256);
            int hr = compile("gcc test.c -o test.exe", resultInfo);

            Console.WriteLine($"Result: {hr}, Info: {resultInfo.ToString()}");
        }
    }
    catch (Exception ex)
    {
        Console.WriteLine($"Error loading or calling DLL: {ex.Message}");
    }
}

通过上述方式,开发者可以灵活地在任意可用路径下加载非托管库,规避了 DllImport 静态绑定带来的局限性,特别适用于插件化架构或部署环境受限的场景。

以上就是C# DllImport使用方法及动态加载DLL的详细内容,更多关于C#互操作技术资料请关注本站其它相关文章!

相关标签:

免责声明:本文内容仅供参考,不代表本站观点。转载或引用请注明出处。

Python 相关产品和应用

全部下载
Selenium

Selenium

4.51.0 · Windows

用于浏览器自动化和 Web 测试的 Python 工具。

scikit-learn

scikit-learn

1.9.1 · Windows

常用 Python 机器学习算法与模型评估工具库。

Black

Black

26.10.1 · Windows

强调一致性的 Python 自动代码格式化工具。

Ruff

Ruff

0.17.1 · macOS

由 Rust 编写的高速 Python 代码检查和格式化工具。

pytest

pytest

9.1.1 · Windows

简洁而强大的 Python 自动化测试框架。

FastAPI

FastAPI

0.146.0 · Windows

高性能 Python API 框架,支持类型提示和自动接口文档。

Flask

Flask

3.1.3 · Windows

轻量灵活的 Python Web 应用框架。

Django

Django

6.1.2 · Windows

成熟的 Python Web 框架,适合构建内容站点和业务系统。

Spyder

Spyder

6.1.7 · Windows

面向科学计算与数据分析的 Python 集成开发环境。

SciPy

SciPy

1.18.1 · Windows

面向科学、工程和技术计算的 Python 算法库。