.Net 8 生成Native库,让C/C++调用

前言

这篇博文是 在.Net 6 中生成Native库,让C/C++调用 更新,在.Net 8使用Native AOT生成动态库(Native原生)更方便,下面一起看看如何使用吧!

1. 创建C#类库

# 1. 创建目录名,这个不是必须的,
mkdir NativeLibrary

# 2. 创建c#类库
dotnet new classlib

创建c#类库,当然也可以使用VS创建类库

2. 测试源码(没有调整)

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;
        }
    }
}

3. 修改项目工程文件(重要)

在.Net 8如果想使用Native AOT,在项目工程文件,添加以下配置:
<PublishAot>true</PublishAot>

在.Net 8中启用AOT配置

4. 生成Native动态库

# -r 后跟win-x64 / linux-x64,.Net AOT目前好像不支持交叉编译(x86->arm/loongarch/risc-v)
# 如果有跨cpu AOT的话,最好准备好一个对应的真实环境,在该环境中编译

dotnet publish -r win-x64 -c Release

在生成Native动态库的时候,实际上还是需要ILCompiler,在.Net 8中只是简化配置,改为自动获取ILCompiler了.

在.Net 8中生成AOT,和普通生成是一样的,只是不支持跨cpu架构交叉编译

5. 在C++中调用C#

#include <iostream>
#include <string>
#include <Windows.h>


int main(int argc, char* argv[])
{

	int a = 100;
	int b = 50;
	std::cout << "a=" << a << " b=" << b << std::endl;


	HMODULE handle = LoadLibraryA("NativeLibrary.dll");
	if (handle != NULL)
	{
		//为add声明函数指针Add
		typedef int (*Add)(int, int);

		//为write_line声明函数指针Write_line
		typedef int (*Write_Line)(std::string str);

		//为sumstring声明函数指针Sumstring
		typedef char* (*SumString)(const char* first, const char* second);


		Add sum = (Add)GetProcAddress(handle, "add");

		std::cout << "sum=" << sum(a, b) << std::endl;


		char first[] = "hello";
		Write_Line write_line = (Write_Line)GetProcAddress(handle, "write_line");
		std::string result = write_line(first) > -1 ? "ok" : "error";

		std::cout << "result=" << result << std::endl;

		char second[] = " world";

		SumString sumstring = (SumString)GetProcAddress(handle, "sumstring");
		std::cout << "sumstring=" << sumstring(first, second) << std::endl;


		//句柄使用结束,要记得释放,避免句柄泄露
		CloseHandle(handle);
	}
	std::cout << std::cin.get();
	return 0;
}

这里不直接使用VS编译,简化操作.使用VS中的命令行工具.找到这个工具:


#使用cl编译c++文件 /std:c++17 指定使用c++ 17

cl main.cpp /std:c++17

#使用cl编译c文件, /std:c11 指定使用c11
cl c.c /std:c11

c++调用c# AOT后的动态库:

c++调用c#动态库(AOT后)

c语言调用c# AOT后的动态库:

c语言调用c#动态库(AOT后)

最后对比一下AOT后的文件大小

c语言调用c#动态库(AOT后)
这两年,.Net Native AOT进步还是很明显的,不过目前AOT还不支持Asp.Net Core MVC/API,只支持minimal(最小)API.还有就是Native AOT不支持32位程序.
秋风 2023-08-23