你想知道如何编写能被其他编程语言(如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跨语言调用的资料请关注代码网其它相关文章!
发表评论