C# DLL跨语言调用的两种实现方法
你想知道如何编写能被其他编程语言(如C++、Python、Java等)调用的C# DLL,这需要解决不同语言间的互操作(Interop)问题,核心是让C# DLL符合跨语言调用的标准。
一、核心思路:让C# DLL支持COM/非托管调用
普通C# DLL基于.NET托管代码,非.NET语言(如C++原生、Python)无法直接调用,需要通过以下两种主流方式实现跨语言调用:
- 方式1:将C# DLL注册为COM组件(兼容绝大多数语言:C++、VB6、Python、Java等);
- 方式2:通过P/Invoke封装非托管导出函数(主要适配C/C++等原生语言)。
下面优先讲解最通用的COM组件方式,再补充P/Invoke方式。
方式1:创建可注册为COM的C# DLL(通用跨语言)
步骤1:创建类库项目并配置COM兼容
- 打开Visual Studio,创建「类库(.NET Framework)」项目(.NET Core/.NET 5+需额外配置,推荐先用.NET Framework做兼容),命名为
ComInteropDll。 - 右键项目→「属性」→「应用程序」→「程序集信息」,勾选「使程序集COM可见」。
- 右键项目→「属性」→「生成」,勾选「为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.dll和ComInteropDll.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;
}关键注意事项
- 数据类型兼容:跨语言调用时,优先使用基础数据类型(int、double、string),避免复杂自定义类型;字符串需注意编码(ANSI/UTF-8/UTF-16)。
- 框架选择:
- .NET Framework:对COM/P/Invoke支持最好,兼容老语言;
- .NET Core/.NET 5+:需用
ComHost或NativeAOT编译为原生DLL,步骤更复杂。
- 错误处理:跨语言调用时,C#中的异常无法直接传递给非托管语言,建议返回错误码而非抛出异常。
- 64位/32位适配:编译DLL和调用程序时,需保持位数一致(均为x86或x64),否则会调用失败。
总结
- 通用跨语言方案:将C# DLL注册为COM组件,适配Python、Java、C++等绝大多数语言,核心是通过
ComVisible和Guid暴露接口,并注册COM。 - C/C++专属方案:使用
UnmanagedExports库导出非托管函数,通过P/Invoke调用,更轻量但仅适配C/C++。 - 核心要点:保持数据类型简单、位数一致、处理好内存释放(尤其是字符串),是跨语言调用的关键。
以上就是C# DLL跨语言调用的两种实现方法的详细内容,更多关于C# DLL跨语言调用的资料请关注脚本之家其它相关文章!


最新评论