.NET 5 / .NET Core - UnmanagedCallersOnly 특성을 사용한 함수 내보내기
지난 글에서,
.NET Framework 및 .NET Core 3.1 이하 - UnmanagedCallersOnly 특성 사용
.NET Framework 및 .NET 5 - UnmanagedCallersOnly 특성 사용
; https://www.sysnet.pe.kr/2/0/12412
다룬 UnmanagedCallersOnly 특성은, .NET Core와 .NET 5 환경에서 추가적인 의미를 갖는데, 바로 해당 함수를 export하는 용도로도 사용할 수 있다는 점입니다. 사실 이에 대해서는 약간의 이력이 있는데요, 원래는 그런 용도로 예전에 NativeCallable 특성이 있었습니다.
C# DLL에서 Win32 C/C++처럼 dllexport 함수를 제공하는 방법 - 두 번째 이야기
; https://www.sysnet.pe.kr/2/0/11884
당시, myget.org에서 배포하던 Microsoft.DotNet.ILCompiler 구성 요소는 NativeCallable 특성을 이용해 함수 내보내기를 구현했는데 이후 CoreRT 측으로 넘어가면서,
dotnet / corert
; https://github.com/dotnet/corert
배포 피드도 "
https://dotnetfeed.blob.core.windows.net/dotnet-core/index.json"로 변경됩니다. 그리고 다시 한번 해당 팀이 NativeAOT로 바뀌면서,
dotnet / runtimelab
; https://github.com/dotnet/runtimelab/tree/feature/NativeAOT
최종 배포 피드는 "
https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-experimental/nuget/v3/index.json"로 바뀌고 버전도 6.0.0-*으로 바뀌었습니다.
새롭게 바뀌었으니, 예제도 새롭게 ^^ 실습을 해보겠습니다.
프로젝트는 .NET Core, .NET Standard, 또는 .NET 5 대상으로 생성합니다. 이 글에서는 .NET Standard 1.1로 실습을 하겠습니다. 그런 다음, Microsoft.DotNet.ILCompiler 참조를 6.0 버전으로 추가합니다.
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard1.1</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.DotNet.ILCompiler" Version="6.0.0-*" />
</ItemGroup>
</Project>
그리고,
(명령행에서 "dotnet new nuget" 등으로 만들거나, 직접 메모장을 이용해 생성한) nuget.config 파일에 Microsoft.DotNet.ILCompiler 구성 요소를 내려받을 수 있는 피드를 추가합니다.
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<packageSources>
<!--To inherit the global NuGet package sources remove the <clear/> line below -->
<clear />
<add key="nuget.org" value="https://api.nuget.org/v3/index.json" />
<add key="dotnet-experimental" value="https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-experimental/nuget/v3/index.json" />
</packageSources>
</configuration>
환경 준비는 끝났고, 이제 다음과 같이 UnmanagedCallersOnly 특성을 이용해 코드를 작성하시면 됩니다.
using System;
using System.Runtime.InteropServices;
public class Class1
{
// 반드시 EntryPoint 속성에 값을 설정!
[UnmanagedCallersOnly(EntryPoint = "mymethod")]
public static void MyMethod(IntPtr ptrText)
{
string text = Marshal.PtrToStringUni(ptrText);
// Console.WriteLine($"{DateTime.Now} {text}");
}
}
namespace System.Runtime.InteropServices
{
#if !NET5_0
[AttributeUsage(AttributeTargets.Method)]
public sealed class UnmanagedCallersOnlyAttribute : Attribute
{
public string EntryPoint;
public CallingConvention CallingConvention;
public UnmanagedCallersOnlyAttribute() { }
}
#endif
}
남은 작업은, "dotnet publish" 명령어로 바이너리를 생성하는 것으로 끝!
dotnet publish /p:NativeLib=Shared -r win-x64 -c Release
이후 depends.exe 또는 dumpbin.exe 등의 도구로 export시킨 함수가 잘 나열되어 있는지 확인하시면 됩니다.
여기서 중요한 것은, 해당 DLL 파일이 "AOT(Ahead-of-time)" 빌드가 되었다는 점입니다. 따라서, 자체 실행 가능한 native 모듈이므로 이 DLL과 관련해서는 어떠한 부가 모듈도 필요가 없습니다. 덕분에, C++에서 사용할 때도 사용법이 다음과 같이 매우 간단합니다.
#include <iostream>
#include <windows.h>
typedef void(__stdcall* mymethod_proc)(intptr_t text);
static void* load_library(const char* path)
{
HMODULE h = LoadLibraryA(path);
return (void*)h;
}
static void* get_export(void* h, const char* name)
{
void* f = GetProcAddress((HMODULE)h, name);
return f;
}
int main()
{
const wchar_t* pText = L"Hello World";
void* mod = load_library("ClassLibrary.dll");
mymethod_proc mymethod = (mymethod_proc)get_export(mod, "mymethod");
mymethod((intptr_t)pText);
}
(
첨부 파일은 이 글의 예제 코드를 포함합니다.)
[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]