大家都知道.net可以通过PInvoke来调用C/C++/其他本地编译器产生的dll
但是有时候有这样的需求,用c#写dll,其他语言来调用
一般的实现方法都比较麻烦,比如注册成COM组件,用c++/cli再写个dll来export
其实.net framework是支持导出dll里面的方法的,直接就可以用其他语言调用,不过如果手动修改IL和meta data则非常麻烦
这里我们要用到dnlib,是一个类似于mono.cecil的库,可以支持对托管程序集进行读取和修改
其作者也开发了dnspy,de4dot等知名工具,它们用的底层库都是dnlib
dnlib可以在nuget中获取,在github上开源
这是作者自己写的导出文档
下面是个简单的例子
先创建一个c#的动态库TestDll,用于导出方法,注意exe文件无法用于导出
里面就一个类Class1
写一个方法,必须是静态的,因为只有静态方法才能被导出
public static int Add(int value1, int value2) => value1 + value2;
然后CPU架构必须手工选择x86或者x64, AnyCPU的话无法被导出,然后选择Release编译模式,我自己测试的时候Debug也是无法正常调用的
下面是用dnlib修改导出的代码
byte[] data = File.ReadAllBytes("TestDll.dll");
ModuleDefMD module = ModuleDefMD.Load(data);
TypeDef Class1 = module.Find("TestDll.Class1", false);
foreach (MethodDef method in Class1.Methods.Where(x => x.IsStatic))
{
method.ExportInfo = new MethodExportInfo();
TypeSig type = method.MethodSig.RetType;
type = new CModOptSig(module.CorLibTypes.GetTypeRef("System.Runtime.CompilerServices", "CallConvStdcall"), type);
method.MethodSig.RetType = type;
}
var options = new dnlib.DotNet.Writer.ModuleWriterOptions(module);
options.Cor20HeaderOptions.Flags = module.Cor20HeaderFlags & ~ComImageFlags.ILOnly;
module.Write("TestDll_Exported.dll", options);
首先是将dll用dnlib加载进来,找到类(TestDll.Class1)
然后循环处理类里面的所有静态方法
method.ExportInfo = new MethodExportInfo(); //这里是声明此方法需要被导出,一般来初始化ExportInfo就可以了,如果需要自定义导出函数名或者顺序之类,那么可以对其成员进行赋值
之后还要修改导出函数的返回值,用System.Runtime.CompilerServices.CallConvStdcall修饰它,声明这是用于stdcall调用
当然你也可以用System.Runtime.CompilerServices.CallConvCdecl,对应的是cdecl调用
最后是将这个修改后的dll保存,这里需要注意的是需要将Cor20HeaderOptions里的ILOnly去掉,因为导出的dll不完全是托管代码了,可以用非托管代码访问
修改完后,可以用Depends查看这个dll,可以看到导出表里已经有Add函数
接下来就是调用了
[DllImport("TestDll_Exported.dll", CallingConvention = CallingConvention.StdCall)]
public static extern int Add(int value1, int value2);
别吐槽为什么用c#来调用,这只是个例子,用c++也是同样的效果
#include <Windows.h>
#include <stdio.h>
typedef int(__stdcall *AddFunc)(int);
int main() {
HMODULE handle = LoadLibraryA("TestDll_Exported.dll");
if (handle) {
AddFunc Add = (AddFunc)GetProcAddress(handle, "IncNumber");
if (Add) {
printf("%d", Add(1));
}
}
FreeLibrary(handle);
return 0;
}
有一点是,如果你的dll是x86的,那么调用者也必须是x86,x64的同理,否则会报错