记得之前在做医保接口时,经常要调一些C++或delphi写的本地dll(这些dll通常是用来读医保卡之类),这里简单介绍一下用C#生成本地可以直接调用dll,具体做法是通过UnmanagedCallersOnlyAttribute来完成。
dll项目文件如下
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<LangVersion>12</LangVersion>
<PublishAot>true</PublishAot>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.DotNet.ILCompiler" Version="8.0.0" />
</ItemGroup>
</Project>
定义了几个简单的方法,如下:
using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Text;
namespace classlib
{
//发布命令 dotnet publish /p:NativeLib=Shared --use-current-runtime
public static class DllTest
{
[UnmanagedCallersOnly(EntryPoint = "int_add")]
public static int Add(int a, int b)
{
return a + b;
}
[UnmanagedCallersOnly(EntryPoint = "string_add")]
public static IntPtr StringTest(IntPtr ptr1, IntPtr ptr22)
{
var s = Marshal.PtrToStringUTF8(ptr1) + Marshal.PtrToStringUTF8(ptr22);
return Marshal.StringToCoTaskMemUTF8(s);
}
[UnmanagedCallersOnly(EntryPoint = "get_doubles")]
public static IntPtr StringTest()
{
return Marshal.UnsafeAddrOfPinnedArrayElement(new double[] { 1.1, 1.2 }, 0);
}
[UnmanagedCallersOnly(EntryPoint = "set_doubles", CallConvs = new[] { typeof(CallConvCdecl) })]
public static double SetAll(IntPtr InItems, int InItemsLength)
{
var sum = 0d;
for (int i = 0; i < InItemsLength; i++)
{
sum += Marshal.PtrToStructure<double>(InItems + i * Marshal.SizeOf<double>());
}
return sum;
}
[UnmanagedCallersOnly(EntryPoint = "get_order")]
public static IntPtr get_order()
{
var order = new Order { ID = 1, Name = "订单1" };
IntPtr structPtr = Marshal.AllocHGlobal(Marshal.SizeOf(order));
Marshal.StructureToPtr(order, structPtr, false);
return structPtr;
}
}
public struct Order
{
public int ID { get; set; }
public string Name { get; set; }
}
}
然后在命令行中发布应用:
dotnet publish /p:NativeLib=Shared --use-current-runtime
现在创建测试的.NET项目,使用.NET中的P/Invocke来调用。把发布出来的dll,复制到当前项目,并设置文件“复制到输出目录”属性为始终复制。下面的代码就是分别调用上面方法的方式和用到的参数和类型 。
项目中分别测试了,int,double[],string,自定义结构等几种类型的进出。
// 堆代码 duidaima.com
// See https://aka.ms/new-console-template for more information
using System.Reflection;
using System.Runtime.InteropServices;
using System.Text;
Console.WriteLine(MyClass.int_add(1, 2));
Console.WriteLine(MyClass.string_add("abcd", "efg"));
var resultPtr = MyClass.get_doubles();
var Items = new double[2];
for (int j = 0; j < 2; j++)
{
Items[j] = Marshal.PtrToStructure<double>(resultPtr + j * Marshal.SizeOf<double>());
Console.WriteLine(Items[j]);
}
var sum = MyClass.set_doubles(new double[] { 5.6, 6.7 }, 2);
Console.WriteLine(sum);
var orderP = MyClass.get_order();
var order = Marshal.PtrToStructure<Order>(orderP);
Console.WriteLine(order);
Console.ReadLine();
public class MyClass
{
// 定义非托管函数的签名
[DllImport("classlib.dll")] // 指定DLL文件名
public static extern int int_add(int a, int b);
[DllImport("classlib.dll")] // 指定DLL文件名
public static extern string string_add(string a, string b);
[DllImport("classlib.dll")] // 指定DLL文件名
public static extern IntPtr get_doubles();
[DllImport("classlib.dll")] // 指定DLL文件名
public static extern double set_doubles(double[] arr, int length);
[DllImport("classlib.dll")] // 指定DLL文件名
public static extern IntPtr get_order();
}
public struct Order
{
public int ID { get; set; }
public string Name { get; set; }
public override string ToString()
{
return $"Order ID:{ID},Name:{Name}";
}
}