在.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版本的.下面看了一下生成的可执行程序.

在.Net 6生成Native可执行程序,运行结果,和文件大小

在.Net 6生成Native可执行程序,文件真正大小

生成的文件大小,还是可以接受的,应该和Go生成的大小,相差不大了.

下面我们看看生成的程序,依赖的文件(依赖很少也很干净,如果支持生成32位程序,那应该是很完美的):

查看.Net生成Native可执行程序,所依赖的动态库,还是很少很干净的.


.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

.Net 6生成Native动态库,文件大小

新建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语言运行结果:

.Net 6生成Native动态库,c语言调用

注意: VS新建项目,默认是生成32位程序,要改为x64才可以,不然获取dll在内存中地址会失败的.

.Net 6生成Native动态库,c语言调用,要设置64位编译

.Net生成静态库

这一块一直放着没有写,是因为C调用.Net生成的静态库,一直没法正常调用.不知道等到.Net 6发布,可以正常调用吗?
#生成静态库 NativeLib改为Static
dotnet publish /p:NativeLib=Static /p:SelfContained=true  -r win-x64 -c release

在.Net 6生成Native原生的静态库

#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 6生成Native原生的静态库,可以进入.Net6函数内,变量提示被优化掉.

在.Net 6生成Native原生的静态库,可以进入.Net6函数内,由于ILCompiler内的Runtime.ServerGC.lib没有调试文件,还不确定是在哪里被优化掉的

最近要是时间充足的话,可以自己编译.Net Runtime,到时候替换到ILCompiler内的Runtime.ServerGC.lib,再试试.看看可以正常调用不.

秋风 2021-05-09