在.Net 6 生成Native库,让C/C++调用
起因
突然想起CoreRT,上次使用还是在2019年,写这篇 .Net Core生成静态库,C程序调用 ,便在Github看CoreRT项目,才知道CoreRT已经改名迁移到runtimelab下.至于标题为什么是生成Native库,因为Native分为静态库(Windows以lib后缀名,Linux以a后缀 名)和动态库(Windows以dll为后缀名,Linux以so后缀名).
这里不得不说一下, .Net程序生成Native可执行程序大小只有4.25兆.本文顺序先从生成Native程序.然后是动态库,最后是静态库(可以调用,但不能成功).
.Net 生成Native(可执行/静态库/动态库) 所需以下步骤
如果项目下没有nuget.config文件,那需要创建一个nuget.config//创建nuget文件,知道格式的话,可以手动创建
dotnet new nuget
修改nuget.config内容为:
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<packageSources>
<!--To inherit the global NuGet package sources remove the <clear/> line below -->
<!--添加以下内容-->
<clear />
<add key="dotnet-experimental" value="https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-experimental/nuget/v3/index.json" />
<add key="nuget.org" value="https://api.nuget.org/v3/index.json" protocolVersion="3" />
<!--添加以上内容-->
</packageSources>
</configuration>
在项目中,引用ILCompiler:
# 引用ILCompiler库, 指定为版本为6.0.0-*(*号匹配最新版本)
dotnet add package Microsoft.DotNet.ILCompiler -v 6.0.0-*
.Net 生成Native可执行程序
测试代码:using System;
namespace HelloWorld
{
class Program
{
static void Main(string[] args)
{
Console.WriteLine("Hello World!");
Console.WriteLine("It's a c# application");
Console.ReadKey();
}
}
}
#指定程序发布Windows 64位程序,目前只支持生成64位程序,不支持生成32位程序
dotnet publish -r win-x64 -c release
不得不说一下,不支持32位程序,还是很坑的,我原先所在的医疗行业,还是有很多Windows XP系统,甚至还有Windows XP SP2版本的.下面看了一下生成的可执行程序.
生成的文件大小,还是可以接受的,应该和Go生成的大小,相差不大了.
下面我们看看生成的程序,依赖的文件(依赖很少也很干净,如果支持生成32位程序,那应该是很完美的):
.Net 生成Native动态库
测试代码:// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System;
using System.Runtime.InteropServices;
namespace NativeLibrary
{
public class Class1
{
[UnmanagedCallersOnly(EntryPoint = "add")]
public static int Add(int a, int b)
{
//Console.WriteLine($"a={a} b={b}");
return a + b;
}
[UnmanagedCallersOnly(EntryPoint = "write_line")]
public static int WriteLine(IntPtr pString)
{
// The marshalling code is typically auto-generated by a custom tool in larger projects.
try
{
// UnmanagedCallersOnly methods only accept primitive arguments. The primitive arguments
// have to be marshalled manually if necessary.
string str = Marshal.PtrToStringAnsi(pString);
Console.WriteLine(str);
}
catch
{
// Exceptions escaping out of UnmanagedCallersOnly methods are treated as unhandled exceptions.
// The errors have to be marshalled manually if necessary.
return -1;
}
return 0;
}
[UnmanagedCallersOnly(EntryPoint = "sumstring")]
public static IntPtr sumstring(IntPtr first, IntPtr second)
{
// Parse strings from the passed pointers
string my1String = Marshal.PtrToStringAnsi(first);
string my2String = Marshal.PtrToStringAnsi(second);
// Concatenate strings
string sum = my1String + my2String;
// Assign pointer of the concatenated string to sumPointer
IntPtr sumPointer = Marshal.StringToHGlobalAnsi(sum);
// Return pointer
return sumPointer;
}
}
}
如果项目没有nuget.config和引用ILCompiler库,可按照上方的步骤执行.这里就不多说了.
#指定NativeLib为Shared(动态库)
dotnet publish /p:NativeLib=Shared /p:SelfContained=true -r win-x64 -c release
新建C语言项目:
#include <stdio.h>
#include <stdlib.h>
#include <windows.h>
int main(int arg, char* argv[])
{
int a = 100;
int b = 50;
printf("a=%d b=%d\n", a, b);
HMODULE handle = LoadLibraryA("NativeLibrary.dll");
if (handle != NULL)
{
//为add声明函数指针Add
typedef int (*Add)(int, int);
//为write_line声明函数指针Write_line
typedef int (*Write_Line)(char* str);
//为sumstring声明函数指针Sumstring
typedef char* (*SumString)(char* first, char* second);
Add sum = (Add)GetProcAddress(handle, "add");
printf("sum=%d\n", sum(a, b));
char* first = "hello";
Write_Line write_line = (Write_Line)GetProcAddress(handle, "write_line");
printf("result=%s\n", write_line(first) > -1 ? "ok" : "error");
char second[] = " world";
SumString sumstring = (SumString)GetProcAddress(handle, "sumstring");
printf("sumstring=%s\n", sumstring(first, second));
//句柄使用结束,要记得释放,避免句柄泄露
CloseHandle(handle);
}
system("pause");
return 0;
}
看一下在C语言运行结果:
注意: VS新建项目,默认是生成32位程序,要改为x64才可以,不然获取dll在内存中地址会失败的.
.Net生成静态库
这一块一直放着没有写,是因为C调用.Net生成的静态库,一直没法正常调用.不知道等到.Net 6发布,可以正常调用吗?#生成静态库 NativeLib改为Static
dotnet publish /p:NativeLib=Static /p:SelfContained=true -r win-x64 -c release
#include <stdio.h>
#include <stdlib.h>
//还是需要引入Runtime.ServerGC.lib,将Runtime.ServerGC.lib放到项目的根目录
#pragma comment(lib,"Runtime.ServerGC.lib")
#pragma comment(lib,"NativeLibrary.lib")
//在连接器输入bcrypt.lib这个系统静态库,不然编译不会通过
extern int add(int a, int b);
extern int write_line(char* str);
int main(int arg, char* argv[])
{
int a = 100;
int b = 50;
printf("a=%d b=%d\n", a, b);
printf("a+b=%d\n", add(a, b));
system("pause"); //暂停,让控制台显示输出结果
return 0;
}
调用.Net 6生成的静态库,是可以进入函数内容,使用监视器查看变量的值,提示被优化.
最近要是时间充足的话,可以自己编译.Net Runtime,到时候替换到ILCompiler内的Runtime.ServerGC.lib,再试试.看看可以正常调用不.
秋风
2021-05-09