以前、医療保険インターフェースを作成していた時、C++やDelphiで書かれたネイティブ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
公開されたdllを現在のプロジェクトにコピーし、「ファイルを出力ディレクトリに常にコピーする」属性を設定します。次のコード は、上記のメソッドの呼び出し方と使用するパラメータおよび型です。
// 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}";
}
}
(Translated by GPT)