C# DLL跨语言调用的两种实现方法

 更新时间:2026年03月02日 09:39:34   作者:蝈蝈(GuoGuo)  
这篇文章主要介绍了如何编写能被其他编程语言调用的C# DLL,包括两种主流方式:通过COM接口或P/Invoke封装非托管导出函数,重点在于让C# DLL符合跨语言调用的标准,包括数据类型兼容性、框架选择和错误处理等关键要点,需要的朋友可以参考下

你想知道如何编写能被其他编程语言(如C++、Python、Java等)调用的C# DLL,这需要解决不同语言间的互操作(Interop)问题,核心是让C# DLL符合跨语言调用的标准。

一、核心思路:让C# DLL支持COM/非托管调用

普通C# DLL基于.NET托管代码,非.NET语言(如C++原生、Python)无法直接调用,需要通过以下两种主流方式实现跨语言调用:

  1. 方式1:将C# DLL注册为COM组件(兼容绝大多数语言:C++、VB6、Python、Java等);
  2. 方式2:通过P/Invoke封装非托管导出函数(主要适配C/C++等原生语言)。

下面优先讲解最通用的COM组件方式,再补充P/Invoke方式。

方式1:创建可注册为COM的C# DLL(通用跨语言)

步骤1:创建类库项目并配置COM兼容

  1. 打开Visual Studio,创建「类库(.NET Framework)」项目(.NET Core/.NET 5+需额外配置,推荐先用.NET Framework做兼容),命名为ComInteropDll
  2. 右键项目→「属性」→「应用程序」→「程序集信息」,勾选「使程序集COM可见」。
  3. 右键项目→「属性」→「生成」,勾选「为COM互操作注册」(仅开发环境需要,发布时手动注册)。

步骤2:编写COM兼容的C#代码

COM要求必须通过接口 暴露方法,且需要给接口/类添加Guid(唯一标识),示例代码如下:

using System;
using System.Runtime.InteropServices;

// 1. 为接口添加唯一Guid(可通过VS工具生成:工具→创建GUID→选注册表格式,去掉{})
[Guid("12345678-1234-1234-1234-1234567890AB")]
// 2. 标记为COM可见的接口
[ComVisible(true)]
public interface ICalculator
{
    // COM接口方法不能有重载,参数/返回值尽量用基础类型(int、string、double等)
    int Add(int a, int b);
    string ReverseString(string input);
}

// 3. 为实现类添加唯一Guid
[Guid("87654321-4321-4321-4321-BA0987654321")]
[ComVisible(true)]
public class Calculator : ICalculator
{
    public int Add(int a, int b)
    {
        return a + b;
    }

    public string ReverseString(string input)
    {
        if (string.IsNullOrEmpty(input))
            throw new ArgumentNullException(nameof(input));
        
        char[] arr = input.ToCharArray();
        Array.Reverse(arr);
        return new string(arr);
    }
}

步骤3:编译并注册COM组件

按F6编译项目,生成ComInteropDll.dllComInteropDll.tlb(类型库文件,供其他语言识别)。

注册COM组件(管理员权限运行CMD):

# 替换为你的DLL路径(Release版建议用Release目录)
regasm /codebase "C:\ComInteropDll\bin\Debug\ComInteropDll.dll"

卸载COM组件(如需):regasm /u "你的DLL路径"

步骤4:不同语言调用该COM DLL示例

示例1:Python调用(使用pywin32库)

# 先安装依赖:pip install pywin32
import win32com.client

# 创建COM对象(参数是接口/类的ProgID,或直接用Guid)
# 方式1:用类名(需确保项目属性中配置了ProgID,或手动指定)
calculator = win32com.client.Dispatch("ComInteropDll.Calculator")

# 调用方法
sum_result = calculator.Add(10, 25)
reverse_result = calculator.ReverseString("Python Call C# DLL")

print(f"两数之和:{sum_result}")  # 输出:35
print(f"反转字符串:{reverse_result}")  # 输出:LLD #C llaC nohtyP

示例2:C++原生调用(使用COM接口)

#include <iostream>
#include <Windows.h>
// 导入类型库(替换为你的tlb文件路径)
#import "C:\ComInteropDll\bin\Debug\ComInteropDll.tlb" no_namespace

int main()
{
    // 初始化COM库
    CoInitialize(NULL);

    // 创建COM对象
    ICalculator* pCalculator = NULL;
    HRESULT hr = CoCreateInstance(
        __uuidof(Calculator),  // 类的Guid
        NULL,
        CLSCTX_INPROC_SERVER,
        __uuidof(ICalculator), // 接口的Guid
        (void**)&pCalculator
    );

    if (SUCCEEDED(hr))
    {
        // 调用方法
        int sum = 0;
        pCalculator->Add(100, 200, &sum);
        
        BSTR input = SysAllocString(L"C++ Call C# COM DLL");
        BSTR reversed = NULL;
        pCalculator->ReverseString(input, &reversed);

        // 输出结果
        std::cout << "Sum: " << sum << std::endl; // 输出:300
        wprintf(L"Reversed: %s\n", reversed);    // 输出:LLD MOC #C llaC ++C

        // 释放资源
        SysFreeString(input);
        SysFreeString(reversed);
        pCalculator->Release();
    }

    // 释放COM库
    CoUninitialize();
    return 0;
}

方式2:P/Invoke导出非托管函数(适配C/C++)

如果仅需给C/C++调用,可通过DllExport库直接导出非托管函数(C#原生不支持直接导出函数,需第三方库)。

步骤1:安装DllExport库

在NuGet包管理器中安装:UnmanagedExports(注意:仅支持.NET Framework)。

步骤2:编写导出函数的C#代码

using System;
using System.Runtime.InteropServices;
using RGiesecke.DllExport;

namespace PInvokeDll
{
    public class MyExports
    {
        // 导出Add函数,供C/C++调用
        [DllExport("Add", CallingConvention = CallingConvention.Cdecl)]
        public static int Add(int a, int b)
        {
            return a + b;
        }

        // 导出反转字符串函数(注意字符编码适配)
        [DllExport("ReverseString", CallingConvention = CallingConvention.Cdecl)]
        public static IntPtr ReverseString([MarshalAs(UnmanagedType.LPStr)] string input)
        {
            if (string.IsNullOrEmpty(input))
                return IntPtr.Zero;
            
            char[] arr = input.ToCharArray();
            Array.Reverse(arr);
            string result = new string(arr);
            
            // 分配非托管内存并返回(调用方需释放)
            return Marshal.StringToHGlobalAnsi(result);
        }

        // 提供释放内存的函数,供调用方使用
        [DllExport("FreeString", CallingConvention = CallingConvention.Cdecl)]
        public static void FreeString(IntPtr ptr)
        {
            if (ptr != IntPtr.Zero)
                Marshal.FreeHGlobal(ptr);
        }
    }
}

步骤3:C++调用该DLL

#include <iostream>
#include <Windows.h>

// 声明导入的函数
extern "C" __declspec(dllimport) int Add(int a, int b);
extern "C" __declspec(dllimport) char* ReverseString(const char* input);
extern "C" __declspec(dllimport) void FreeString(char* ptr);

int main()
{
    // 调用Add函数
    int sum = Add(5, 8);
    std::cout << "Add result: " << sum << std::endl; // 输出:13

    // 调用ReverseString函数
    char* reversed = ReverseString("Hello C++");
    std::cout << "Reversed: " << reversed << std::endl; // 输出:++C olleH
    FreeString(reversed); // 释放内存

    return 0;
}

关键注意事项

  1. 数据类型兼容:跨语言调用时,优先使用基础数据类型(int、double、string),避免复杂自定义类型;字符串需注意编码(ANSI/UTF-8/UTF-16)。
  2. 框架选择
    • .NET Framework:对COM/P/Invoke支持最好,兼容老语言;
    • .NET Core/.NET 5+:需用ComHostNativeAOT编译为原生DLL,步骤更复杂。
  3. 错误处理:跨语言调用时,C#中的异常无法直接传递给非托管语言,建议返回错误码而非抛出异常。
  4. 64位/32位适配:编译DLL和调用程序时,需保持位数一致(均为x86或x64),否则会调用失败。

总结

  1. 通用跨语言方案:将C# DLL注册为COM组件,适配Python、Java、C++等绝大多数语言,核心是通过ComVisibleGuid暴露接口,并注册COM。
  2. C/C++专属方案:使用UnmanagedExports库导出非托管函数,通过P/Invoke调用,更轻量但仅适配C/C++。
  3. 核心要点:保持数据类型简单、位数一致、处理好内存释放(尤其是字符串),是跨语言调用的关键。

以上就是C# DLL跨语言调用的两种实现方法的详细内容,更多关于C# DLL跨语言调用的资料请关注脚本之家其它相关文章!

相关文章

  • C#使用Twain协议实现扫描仪连续扫描功能

    C#使用Twain协议实现扫描仪连续扫描功能

    这篇文章主要介绍了C#使用Twain协议实现扫描仪连续扫描,只需一行代码,就可实现一次扫描多张,且不需要更改扫描仪的任何设置,需要的朋友可以参考下
    2022-01-01
  • C#调用Oracle存储过程的方法

    C#调用Oracle存储过程的方法

    这篇文章主要介绍了C#调用Oracle存储过程的方法,包含数据库及C#对应的调用代码,具有一定参考借鉴价值,需要的朋友可以参考下
    2015-01-01
  • C#使用SQL Dataset数据集代码实例

    C#使用SQL Dataset数据集代码实例

    今天小编就为大家分享一篇关于的文章,小编觉得内容挺不错的,现在分享给大家,具有很好的参考价值,需要的朋友一起跟随小编来看看吧
    2018-10-10
  • C#中IList 与 List 的区别小结

    C#中IList 与 List 的区别小结

    IList 接口和 List 类是 C# 中用于集合操作的两个重要的类型,本文主要介绍了C#中IList 与 List 的区别小结,具有一定的参考价值,感兴趣的可以了解一下
    2024-04-04
  • C#串口通信工具类的封装

    C#串口通信工具类的封装

    这篇文章主要为大家详细介绍了C#串口通信工具类封装,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2022-02-02
  • WPF实现绘制折线图的示例代码

    WPF实现绘制折线图的示例代码

    这篇文章主要为大家详细介绍了如何使用WPF实现绘制简单的折线图,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下
    2024-04-04
  • unity实现简单计算器

    unity实现简单计算器

    这篇文章主要为大家详细介绍了unity实现简单计算器,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2021-08-08
  • P/Invoke之C#调用动态链接库DLL示例详解

    P/Invoke之C#调用动态链接库DLL示例详解

    这篇文章主要为大家介绍了P/Invoke之C#调用动态链接库DLL示例详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2023-03-03
  • C# WINFORM 强制让窗体获得焦点的方法代码

    C# WINFORM 强制让窗体获得焦点的方法代码

    C# WINFORM 强制让窗体获得焦点的方法代码,需要的朋友可以参考一下
    2013-04-04
  • c# 值类型实例构造器

    c# 值类型实例构造器

    CLR总是允许创建值类型的实例。另外值类型不一定需要定义构造器,c#编译器不会为值类型生成默认的无参构造器
    2012-10-10

最新评论