Microsoft MVP성태의 닷넷 이야기
.NET Framework: 972. DNNE가 출력한 NE DLL을 직접 생성하는 방법 [링크 복사], [링크+제목 복사],
조회: 15251
글쓴 사람
정성태 (techsharer at outlook.com)
홈페이지
첨부 파일
(연관된 글이 2개 있습니다.)
(시리즈 글이 7개 있습니다.)
.NET Framework: 969. .NET Framework 및 .NET 5 - UnmanagedCallersOnly 특성 사용
; https://www.sysnet.pe.kr/2/0/12412

.NET Framework: 970. .NET 5 / .NET Core - UnmanagedCallersOnly 특성을 사용한 함수 내보내기
; https://www.sysnet.pe.kr/2/0/12413

.NET Framework: 971. UnmanagedCallersOnly 특성과 DNNE 사용
; https://www.sysnet.pe.kr/2/0/12415

.NET Framework: 972. DNNE가 출력한 NE DLL을 직접 생성하는 방법
; https://www.sysnet.pe.kr/2/0/12421

.NET Framework: 973. .NET 5, .NET Framework에서만 허용하는 UnmanagedCallersOnly 사용예
; https://www.sysnet.pe.kr/2/0/12422

.NET Framework: 976. UnmanagedCallersOnly + C# 9.0 함수 포인터 사용 시 x86 빌드에서 오동작하는 문제
; https://www.sysnet.pe.kr/2/0/12431

닷넷: 2174. C# - .NET 7부터 UnmanagedCallersOnly 함수 export 기능을 AOT 빌드에 통합
; https://www.sysnet.pe.kr/2/0/13464




DNNE가 출력한 NE DLL을 직접 생성하는 방법

지난 글에서,

UnmanagedCallersOnly 특성과 DNNE 사용
; https://www.sysnet.pe.kr/2/0/12415

DNNE가 사실상 AOT가 아닌, 껍데기 native 함수를 만들어 주는 것에 불과한 것을 설명했는데요, 따라서 DNNE가 생성한 proxy dll을 우리도 직접 빌드해 만드는 것이 가능합니다. 실제로 DNNE는 그런 경우를 위해 자신이 생성한 헤더 파일에 DNNE_COMPILE_AS_SOURCE라는 옵션도 제공합니다.

한 번 직접 해볼까요? ^^




우선, Visual Studio로 C/C++ DLL 프로젝트를 생성 후, 지난번 예제 프로젝트 중 ClassLibrary2를 DNNE로 빌드한 폴더에서 아래의 2개 파일을 복사해 옵니다.

ClassLibrary2NE.h
dnne.h

그런 다음 우리가 만든 C++ 프로젝트의 MyProxy.cpp 파일에 ClassLibrary2NE.h를 포함시킨 후,

#include "pch.h"
#include "framework.h"
#include "MyProxy.h"

#define DNNE_COMPILE_AS_SOURCE
#include "ClassLibrary2NE.h"

빌드하면 이런 오류가 발생합니다.

Error C1083 Cannot open include file: 'dnne.h': No such file or directory

이전 글에도 설명한 그 에러로, 이 글에서는 간단하게 ClassLibrary2NE.h 파일을 열어 소스 코드를 수정하는 것으로 해결하겠습니다.

// #include <dnne.h>
#include "dnne.h"

다시 빌드하면 이번엔 get_callable_managed_function 코드에서,

static void (DNNE_CALLTYPE* mymethod_netcore_ptr)(intptr_t ptrText);
DNNE_API void DNNE_CALLTYPE mymethod_netcore(intptr_t ptrText)
{
    if (mymethod_netcore_ptr == NULL)
    {
        const char_t* methodName = DNNE_STR("MyMethod");
        const char_t* delegateType = DNNE_STR("ClassLibrary.Class1+MyMethodDelegate, ClassLibrary2");
        mymethod_netcore_ptr = get_callable_managed_function(t1_name, methodName, delegateType);
    }
    mymethod_netcore_ptr(ptrText);
}

형 변환이 안 된다는 오류가 발생합니다.

Error C2440 '=': cannot convert from 'void *' to 'void (__cdecl *)(intptr_t)'

이를 위해 MyProxy.cpp에 함수 포인터를 위한 타입을 하나 정의하고,

#include "pch.h"
#include "framework.h"
#include "MyProxy.h"

#include "dnne.h"
#define DNNE_COMPILE_AS_SOURCE
typedef void (DNNE_CALLTYPE* mymethod_netcore_proc)(intptr_t ptrText);

#include "ClassLibrary2NE.h"

get_callable_managed_function 함수의 반환값을 강제 형변환 해줍니다.

static void (DNNE_CALLTYPE* mymethod_netcore_ptr)(intptr_t ptrText);
DNNE_API void DNNE_CALLTYPE mymethod_netcore(intptr_t ptrText)
{
    if (mymethod_netcore_ptr == NULL)
    {
        const char_t* methodName = DNNE_STR("MyMethod");
        const char_t* delegateType = DNNE_STR("ClassLibrary.Class1+MyMethodDelegate, ClassLibrary2");
        mymethod_netcore_ptr = (mymethod_netcore_proc)get_callable_managed_function(t1_name, methodName, delegateType);
    }
    mymethod_netcore_ptr(ptrText);
}

이제 당연히 get_callable_managed_function이 없다는 오류가 발생할 텐데요, 이것은 DNNE가 설치된 NuGet cache에서 "platform.c" 파일로 찾을 수 있습니다.

F:\nuget_root\dnne\1.0.16\tools\platform\platform.c

따라서 이 파일을 복사해 DLL 프로젝트에 포함시키고 빌드하면... 휴... 또 에러가 발생합니다.

fatal error C1010: unexpected end of file while looking for precompiled header. Did you forget to add '#include "pch.h"' to your source?

이를 위해 platform.c 파일의 속성창으로 들어가 "Configuration Properties" / "C/C++" / "Precompiled Headers"에서 "Precompiled Header" 옵션을 "Use (/Yu)"에서 "Not Using Precompiled Headers"로 변경할 수 있지만, 여기서는 편의상 pch.h 파일을 platform.c에서 추가하는 걸로 변경하겠습니다.

#include "pch.h"

그리고 또 빌드하면, ^^;

Error C1189 #error:  Target assembly name must be defined. Set 'DNNE_ASSEMBLY_NAME'.

오류 수정을 위해 pch.h 전역 헤더 파일에 DNNE_ASSEMBLY_NAME 전처리 상수를 실제 구현을 담고 있는 .NET 어셈블리의 이름으로 설정합니다.

// pch.h

#ifndef PCH_H
#define PCH_H

// add headers that you want to pre-compile here
#include "framework.h"

#define DNNE_ASSEMBLY_NAME ClassLibrary2

#endif //PCH_H

또 빌드하면,

Error C1853 'x64\Debug\MyProxy.pch' precompiled header file is from a different version of the compiler, or the precompiled header is C++ and you are using it from C (or vice versa)

쉽게 갑시다. ^^ 그냥 platform.c를 platform.cpp로 변경합니다. 또 빌드하면,

Error C1083 Cannot open include file: 'nethost.h': No such file or directory

platform.cpp에 포함된 include 파일이 없는 것입니다. 이 파일은 각각의 .NET Core SDK에 포함되어 있는데,

C:\> dir /a/s nethost.h | findstr native
 Directory of C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Host.win-arm\3.1.9\runtimes\win-arm\native
 Directory of C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Host.win-arm\5.0.0\runtimes\win-arm\native
 Directory of C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Host.win-arm64\3.1.9\runtimes\win-arm64\native
 Directory of C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Host.win-arm64\5.0.0\runtimes\win-arm64\native
 Directory of C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Host.win-x64\3.1.9\runtimes\win-x64\native
 Directory of C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Host.win-x64\5.0.0\runtimes\win-x64\native
 Directory of C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Host.win-x86\3.1.9\runtimes\win-x86\native
 Directory of C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Host.win-x86\5.0.0\runtimes\win-x86\native

이 글에서는 "C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Host.win-x64\5.0.0\runtimes\win-x64\native" 폴더에 있는 nethost.h를 선택해 platform.cpp에 포함하겠습니다.

// Include the official nethost API and indicate
// consumption should be as a static library.
#define NETHOST_USE_AS_STATIC
// #include <nethost.h>
#include "C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Host.win-x64\5.0.0\runtimes\win-x64\native\nethost.h"

혹은, 해당 헤더 파일의 내용이 간단하므로 (윈도우만을 대상으로 한다면) pch.h에 그냥 nethost.h 대신 다음의 내용을 넣어도 됩니다.

typedef wchar_t char_t;

struct get_hostfxr_parameters {
    size_t size;
    const char_t* assembly_path;
    const char_t* dotnet_root;
};

extern "C" int __stdcall get_hostfxr_path(
    char_t * buffer,
    size_t * buffer_size,
    const struct get_hostfxr_parameters* parameters);

다시 빌드하면,

Error LNK2019 unresolved external symbol get_hostfxr_path referenced in function "void __cdecl load_hostfxr(void)" (?load_hostfxr@@YAXXZ)

당연히 구현체가 없으니 오류가 날 텐데, 이것 역시 nethost.dll에 정의가 있으므로 nethost.lib로 링크할 수도 있지만 DNNE의 경우 정적 링크를 하고 있으므로 이 글에서도 libnethost.lib에 정적 링크를 하겠습니다.

// pch.cpp: source file corresponding to the pre-compiled header

#include "pch.h"

// When you are using pre-compiled headers, this source file is necessary for compilation to succeed.

#pragma comment(lib, "C:\\Program Files\\dotnet\\packs\\Microsoft.NETCore.App.Host.win-x64\\5.0.0\\runtimes\\win-x64\\native\\libnethost.lib")

자, 이제 약간의 경고가 나오지만 빌드는 일단 잘 됩니다.

Build started...
1>------ Build started: Project: MyProxy, Configuration: Debug x64 ------
1>pch.cpp
1>dllmain.cpp
1>MyProxy.cpp
1>platform.cpp
1>Generating Code...
1>libnethost.lib(nethost.obj) : MSIL .netmodule or module compiled with /GL found; restarting link with /LTCG; add /LTCG to the link command line to improve linker performance
1>LINK : warning LNK4075: ignoring '/INCREMENTAL' due to '/LTCG' specification
1>   Creating library E:\temp\x64\Debug\MyProxy.lib and object E:\temp\x64\Debug\MyProxy.exp
1>MSVCRTD.lib(initializers.obj) : warning LNK4098: defaultlib 'libcmt.lib' conflicts with use of other libs; use /NODEFAULTLIB:library
1>Generating code
1>Finished generating code
1>libnethost.lib(nethost.obj) : warning LNK4099: PDB 'libnethost.pdb' was not found with 'libnethost.lib(nethost.obj)' or at 'E:\temp\x64\Debug\libnethost.pdb'; linking object as if no debug info
1>libnethost.lib(utils.obj) : warning LNK4099: PDB 'libnethost.pdb' was not found with 'libnethost.lib(utils.obj)' or at 'E:\temp\x64\Debug\libnethost.pdb'; linking object as if no debug info
1>libnethost.lib(fxr_resolver.obj) : warning LNK4099: PDB 'libnethost.pdb' was not found with 'libnethost.lib(fxr_resolver.obj)' or at 'E:\temp\x64\Debug\libnethost.pdb'; linking object as if no debug info
1>libnethost.lib(trace.obj) : warning LNK4099: PDB 'libnethost.pdb' was not found with 'libnethost.lib(trace.obj)' or at 'E:\temp\x64\Debug\libnethost.pdb'; linking object as if no debug info
1>libnethost.lib(pal.windows.obj) : warning LNK4099: PDB 'libnethost.pdb' was not found with 'libnethost.lib(pal.windows.obj)' or at 'E:\temp\x64\Debug\libnethost.pdb'; linking object as if no debug info
1>libnethost.lib(fx_ver.obj) : warning LNK4099: PDB 'libnethost.pdb' was not found with 'libnethost.lib(fx_ver.obj)' or at 'E:\temp\x64\Debug\libnethost.pdb'; linking object as if no debug info
1>libnethost.lib(longfile.windows.obj) : warning LNK4099: PDB 'libnethost.pdb' was not found with 'libnethost.lib(longfile.windows.obj)' or at 'E:\temp\x64\Debug\libnethost.pdb'; linking object as if no debug info
1>MyProxy.vcxproj -> E:\temp\x64\Debug\MyProxy.dll
1>Done building project "MyProxy.vcxproj".
========== Build: 1 succeeded, 0 failed, 0 up-to-date, 0 skipped ==========

빌드한 MyProxy.dll을 depends로 확인하면 이렇게 export된 것을 확인할 수 있습니다.

?mymethod_netcore@@YAX_J@Z
?preload_runtime@@YAXXZ
?set_failure_callback@@YAXP6AXW4failure_type@@H@Z@Z
get_hostfxr_path

"?mymethod_netcore@@YAX_J@Z"는 "void __cdecl mymethod_netcore(__int64)" 의미이므로 우리가 원했던 이름이 아닙니다. 따라서, 다시 "ClassLibrary2NE.h" 헤더 파일의 mymethod_netcore 선언에 'extern "C"'로 변경해 주고,

// Computed from ClassLibrary.Class1.MyMethod

extern "C"
{
    DNNE_API void DNNE_CALLTYPE mymethod_netcore(intptr_t ptrText);
}

다시 빌드하면, 마침내 우리가 원하는 양식으로 export가 되었습니다.

?preload_runtime@@YAXXZ
?set_failure_callback@@YAXP6AXW4failure_type@@H@Z@Z
get_hostfxr_path
mymethod_netcore




자, 그럼 DNNE가 하는 작업을 대신해 우리가 직접 생성한 프록시 DLL을 사용해 보겠습니다.

빌드한 디렉터리에 지난번 실습의 ClassLibrary2 어셈블리 파일과 runtimeconfig.json을 복사해 와,

  • ClassLibrary2.dll
  • ClassLibrary2.runtimeconfig.json

다음과 같은 C++ 콘솔 프로그램에서 실행하면,

#include "..\MyProxy\ClassLibrary2NE.h"
#pragma comment(lib, "..\\x64\\Debug\\MyProxy.lib")

int main()
{
    const wchar_t* pText = L"Hello World";
    mymethod_netcore((intptr_t)pText);
}

정상적으로 실행하는 것을 확인할 수 있습니다.

[NETCORE] 2020-11-15 오전 11:02:39 Hello World

(첨부 파일은 이 글의 예제 코드를 포함합니다.)




[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]

[연관 글]






[최초 등록일: ]
[최종 수정일: 11/23/2020]

Creative Commons License
이 저작물은 크리에이티브 커먼즈 코리아 저작자표시-비영리-변경금지 2.0 대한민국 라이센스에 따라 이용하실 수 있습니다.
by SeongTae Jeong, mailto:techsharer at outlook.com

비밀번호

댓글 작성자
 




1  2  3  4  5  6  7  8  9  10  [11]  12  13  14  15  ...
NoWriterDateCnt.TitleFile(s)
13667정성태7/7/20246622닷넷: 2273. C# - 리눅스 환경에서의 Hyper-V Socket 연동 (AF_VSOCK)파일 다운로드1
13666정성태7/7/20247700Linux: 74. C++ - Vsock 예제 (Hyper-V Socket 연동)파일 다운로드1
13665정성태7/6/20247880Linux: 73. Linux 측의 socat을 이용한 Hyper-V 호스트와의 vsock 테스트파일 다운로드1
13663정성태7/5/20247474닷넷: 2272. C# - Hyper-V Socket 통신(AF_HYPERV, AF_VSOCK)의 VMID Wildcards 유형파일 다운로드1
13662정성태7/4/20247491닷넷: 2271. C# - WSL 2 VM의 VM ID를 알아내는 방법 - Host Compute System API파일 다운로드1
13661정성태7/3/20247413Linux: 72. g++ - 다른 버전의 GLIBC로 소스코드 빌드
13660정성태7/3/20247522오류 유형: 912. Visual C++ - Linux 프로젝트 빌드 오류
13659정성태7/1/20247858개발 환경 구성: 715. Windows - WSL 2 환경의 Docker Desktop 네트워크
13658정성태6/28/20248235개발 환경 구성: 714. WSL 2 인스턴스와 호스트 측의 Hyper-V에 운영 중인 VM과 네트워크 연결을 하는 방법 - 두 번째 이야기
13657정성태6/27/20247911닷넷: 2270. C# - Hyper-V Socket 통신(AF_HYPERV, AF_VSOCK)을 위한 EndPoint 사용자 정의
13656정성태6/27/20248088Windows: 264. WSL 2 VM의 swap 파일 위치
13655정성태6/24/20247850닷넷: 2269. C# - Win32 Resource 포맷 해석파일 다운로드1
13654정성태6/24/20247787오류 유형: 911. shutdown - The entered computer name is not valid or remote shutdown is not supported on the target computer.
13653정성태6/22/20247937닷넷: 2268. C# 코드에서 MAKEINTREOURCE 매크로 처리
13652정성태6/21/20249247닷넷: 2267. C# - Linux 환경에서 (Reflection 없이) DLL AssemblyFileVersion 구하는 방법파일 다운로드2
13651정성태6/19/20248487닷넷: 2266. C# - (Reflection 없이) DLL AssemblyFileVersion 구하는 방법파일 다운로드1
13650정성태6/18/20248410개발 환경 구성: 713. "WSL --debug-shell"로 살펴보는 WSL 2 VM의 리눅스 환경
13649정성태6/18/20247957오류 유형: 910. windbg - !py 확장 명령어 실행 시 "failed to find python interpreter" (2)
13648정성태6/17/20248278오류 유형: 909. C# - DynamicMethod 사용 시 System.TypeAccessException
13647정성태6/16/20249344개발 환경 구성: 712. Windows - WSL 2의 네트워크 통신 방법 - 세 번째 이야기 (같은 IP를 공유하는 WSL 2 인스턴스) [1]
13646정성태6/14/20247758오류 유형: 908. Process Explorer - "Error configuring dump resources: The system cannot find the file specified."
13645정성태6/13/20248197개발 환경 구성: 711. Visual Studio로 개발 시 기본 등록하는 dev tag 이미지로 Docker Desktop k8s에서 실행하는 방법
13644정성태6/12/20248868닷넷: 2265. C# - System.Text.Json의 기본적인 (한글 등에서의) escape 처리 [1]
13643정성태6/12/20248319오류 유형: 907. MySqlConnector 사용 시 System.IO.FileLoadException 오류
13642정성태6/11/20248198스크립트: 65. 파이썬 - asgi 버전(2, 3)에 따라 달라지는 uvicorn 호스팅
13641정성태6/11/20248673Linux: 71. Ubuntu 20.04를 22.04로 업데이트
1  2  3  4  5  6  7  8  9  10  [11]  12  13  14  15  ...