logo MSJO.kr

C# Interop with .NET Native AOT

2023-04-18
MsJ

닷넷은 Win32 API와 같은 Native DLL을 using System.Runtime.InteropServices 네임스페이스를 통해 호출하는 방법을 제공한다. C++1, Delphi2에서 작성한 DLL 파일을 호출하는 방법은 이미 살펴보았다.

이번 글에서 살펴볼 것은 .NET 7에서 제공하는 Native AOT 컴파일을 사용하여 C#으로 Native DLL을 만들고 이를 불러와 사용하는 간단한 예제이다3. 아래의 소스처럼 ‘NativeLibrary’ 이름으로 프로젝트(DLL, .NET7)를 만든다. 특히 UnmanagedCallersOnly는 바로 해당 함수를 export 하는 용도로 사용할 수 있게 해준다.

NativeLibrary 예제
using System.Runtime.InteropServices;

namespace NativeLibrary;

public class Class1
{
    [UnmanagedCallersOnly(EntryPoint = "add")]
    public static int Add(int a, int b)
    {
        return a + b;
    }

    [UnmanagedCallersOnly(EntryPoint = "sumstring")]
    public static nint SumString(nint first, nint second)
    {
        string? my1String = Marshal.PtrToStringAuto(first);
        string? my2String = Marshal.PtrToStringAuto(second);
        string sum = my1String + my2String;
        nint sumPointer = Marshal.StringToHGlobalAuto(sum);
        return sumPointer;
    }
}

nint 타입은 Native int를 말하는데 플랫폼(32/64bit)에 따라 다른 크기를 갖는 정수이다. 위의 소스를 아래의 프로젝트 컴파일 옵션을 사용하여 dll 파일을 생성한다. 자세한 내용은 Building Native Libraries with NativeAOT를 참고한다.

AOT 컴파일 옵션

프로젝트 옵션에 다음을 추가하여 컴파일 하거나

<PropertyGroup>
    <PublishAot>true</PublishAot>
</PropertyGroup>

아래와 같이 빌드 옵션을 사용한다.

dotnet publish /p:NativeLib=Static /p:SelfContained=true -r <RID> -c <Configuration>
dotnet publish /p:NativeLib=Shared /p:SelfContained=true -r win-x64 -c release
C#에서 Native dll 활용
using System;
using System.Runtime.InteropServices;
using System.Windows;

namespace WpfApp;

public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();
    }

    [DllImport(@"NativeLibrary.dll", EntryPoint = "sumstring", CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Unicode)]
    [return: MarshalAs(UnmanagedType.SysInt)]
    private static extern IntPtr LibPingTest([MarshalAs(UnmanagedType.LPWStr)] string inputString1, [MarshalAs(UnmanagedType.LPWStr)] string inputString2);

    public static string? GetLibPingTest(string inputString1, string inputString2)
    {
        return Marshal.PtrToStringAuto(LibPingTest(inputString1, inputString2));
    }

    private void ButtonTest_OnClick(object sender, RoutedEventArgs e)
    {
        string? str = GetLibPingTest("가나닭", "마바삵");

        TextBlockTest.Text = str;
    }
}

위 소스는 WPF 기본 생성 예제에서 버튼 클릭으로 ‘NativeLibrary.dll’ 파일을 호출하여 사용하는 예제이다.

Lazarus(free pascal) dll
Library NativeLibrary;

{$mode objfpc}{$H+}

Uses
  Classes,
  SysUtils;

  function LibTest(inputString: PUnicodeChar): PUnicodeChar; stdcall; export;
  var
    buffer: UnicodeString;
  begin
    try
      buffer := inputString;
      result := PUnicodeChar(UTF8ToString('abc가나닭') + buffer);
    except
      on e: Exception do
      begin
        result := PUnicodeChar(e.Message);
      end;
    end;
  end;

exports LibTest;

begin
end.      

참고로 위 소스는 Lazarus(free pascal)에서 ‘NativeLibrary.dll’ 파일을 작성하는 예제이다.

Reference
  1. msjo.kr, “C# Interop with C/C++”
  2. msjo.kr, “C# Interop with Delphi”
  3. github.com/dotnet, “NativeAOT samples”

Prεv(Θld)   Nεxt(Nεw)
Content
Search     RSS Feed     BY-NC-ND