Microsoft MVP성태의 닷넷 이야기
.NET Framework: 972. DNNE가 출력한 NE DLL을 직접 생성하는 방법 [링크 복사], [링크+제목 복사]
조회: 8481
글쓴 사람
정성태 (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)
13531정성태1/16/20242059닷넷: 2204. C# - TCP KeepAlive에 새로 추가된 Retry 옵션파일 다운로드1
13530정성태1/15/20242035닷넷: 2203. C# - Python과의 AES 암호화 연동파일 다운로드1
13529정성태1/15/20241914닷넷: 2202. C# - PublishAot의 glibc에 대한 정적 링킹하는 방법
13528정성태1/14/20242046Linux: 68. busybox 컨테이너에서 실행 가능한 C++, Go 프로그램 빌드
13527정성태1/14/20241979오류 유형: 892. Visual Studio - Failed to launch debug adapter. Additional information may be available in the output window.
13526정성태1/14/20242062닷넷: 2201. C# - Facebook 연동 / 사용자 탈퇴 처리 방법
13525정성태1/13/20242031오류 유형: 891. Visual Studio - Web Application을 실행하지 못하는 IISExpress
13524정성태1/12/20242081오류 유형: 890. 한국투자증권 KIS Developers OpenAPI - GW라우팅 중 오류가 발생했습니다.
13523정성태1/12/20241899오류 유형: 889. Visual Studio - error : A project with that name is already opened in the solution.
13522정성태1/11/20242043닷넷: 2200. C# - HttpClient.PostAsJsonAsync 호출 시 "Transfer-Encoding: chunked" 대신 "Content-Length" 헤더 처리
13521정성태1/11/20242122닷넷: 2199. C# - 한국투자증권 KIS Developers OpenAPI의 WebSocket Ping, Pong 처리
13520정성태1/10/20241868오류 유형: 888. C# - Unable to resolve service for type 'Microsoft.Extensions.ObjectPool.ObjectPool`....'
13519정성태1/10/20241965닷넷: 2198. C# - Reflection을 이용한 ClientWebSocket의 Ping 호출파일 다운로드1
13518정성태1/9/20242190닷넷: 2197. C# - ClientWebSocket의 Ping, Pong 처리
13517정성태1/8/20242038스크립트: 63. Python - 공개 패키지를 이용한 위성 이미지 생성 (pystac_client, odc.stac)
13516정성태1/7/20242122닷넷: 2196. IIS - AppPool의 "Disable Overlapped Recycle" 옵션의 부작용
13515정성태1/6/20242401닷넷: 2195. async 메서드 내에서 C# 7의 discard 구문 활용 사례 [1]
13514정성태1/5/20242068개발 환경 구성: 702. IIS - AppPool의 "Disable Overlapped Recycle" 옵션
13513정성태1/5/20242025닷넷: 2194. C# - WebActivatorEx / System.Web의 PreApplicationStartMethod 특성
13512정성태1/4/20241997개발 환경 구성: 701. IIS - w3wp.exe 프로세스의 ASP.NET 런타임을 항상 Warmup 모드로 유지하는 preload Enabled 설정
13511정성태1/4/20242017닷넷: 2193. C# - ASP.NET Web Application + OpenAPI(Swashbuckle) 스펙 제공
13510정성태1/3/20241952닷넷: 2192. C# - 특정 실행 파일이 있는지 확인하는 방법 (Linux)
13509정성태1/3/20241971오류 유형: 887. .NET Core 2 이하의 프로젝트에서 System.Runtime.CompilerServices.Unsafe doesn't support netcoreapp2.0.
13508정성태1/3/20242028오류 유형: 886. ORA-28000: the account is locked
13507정성태1/2/20242716닷넷: 2191. C# - IPGlobalProperties를 이용해 netstat처럼 사용 중인 Socket 목록 구하는 방법파일 다운로드1
13506정성태12/29/20232201닷넷: 2190. C# - 닷넷 코어/5+에서 달라지는 System.Text.Encoding 지원
1  2  3  [4]  5  6  7  8  9  10  11  12  13  14  15  ...