• 如何用C#生成本地可以直接调用的dll
  • 发布于 2个月前
  • 173 热度
    0 评论
记得之前在做医保接口时,经常要调一些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}";
    }
}

用户评论