[原创分享]导出.net dll中的方法,让其他任意语言调用

stherix 2019-04-28 04:45:57
大家都知道.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上开源

这是作者自己写的导出文档
引用
dnlib supports exporting managed methods so the managed DLL file can be loaded by native code and then executed. .NET Framework supports this feature, but there's no guarantee that other CLRs (eg. .NET Core or Mono/Unity) support this feature.

The MethodDef class has an ExportInfo property. If it gets initialized, the method gets exported when saving the module. At most 65536 (2^16) methods can be exported. This is a PE file limitation, not a dnlib limitation.

Exported methods should not be generic.

The method's calling convention should be changed to eg. stdcall, or cdecl, by adding an optional modifier to MethodDef.MethodSig.RetType. It must be a System.Runtime.CompilerServices.CallConvCdecl, System.Runtime.CompilerServices.CallConvStdcall, System.Runtime.CompilerServices.CallConvThiscall, or a System.Runtime.CompilerServices.CallConvFastcall, eg.:

var type = method.MethodSig.RetType;
type = new CModOptSig(module.CorLibTypes.GetTypeRef("System.Runtime.CompilerServices", "CallConvCdecl"), type);
method.MethodSig.RetType = type;
Requirements:

The assembly platform must be x86, x64, IA-64 or ARM (ARM64 isn't supported at the moment). AnyCPU assemblies are not supported. This is as simple as changing (if needed) ModuleWriterOptions.PEHeadersOptions.Machine when saving the file. x86 files should set 32-bit required flag and clear 32-bit preferred flag in the COR20 header.
ModuleWriterOptions.Cor20HeaderOptions.Flags: The IL Only bit must be cleared.
It must be a DLL file (see ModuleWriterOptions.PEHeadersOptions.Characteristics). The file will fail to load at runtime if it's an EXE file.


下面是个简单的例子

先创建一个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的同理,否则会报错
...全文
545 4 打赏 收藏 转发到动态 举报
AI 作业
写回复
用AI写文章
4 条回复
切换为时间正序
请发表友善的回复…
发表回复
stherix 2019-04-28
  • 打赏
  • 举报
回复
忘了说了 调用导出的dll,还是需要.net framework支持,并不是说完全转成native code了
果然C 2019-04-28
  • 打赏
  • 举报
回复
OrdinaryCoder 2019-04-28
  • 打赏
  • 举报
回复
谢谢分享,感觉这个特别有用
stherix 2019-04-28
  • 打赏
  • 举报
回复
最后c++的函数签名和名称没写对,不过改一下就能正常运行

17,747

社区成员

发帖
与我相关
我的任务
社区描述
.NET技术 .NET Framework
社区管理员
  • .NET Framework社区
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
暂无公告

试试用AI创作助手写篇文章吧