Microsoft MVP성태의 닷넷 이야기
.NET Framework: 972. DNNE가 출력한 NE DLL을 직접 생성하는 방법 [링크 복사], [링크+제목 복사],
조회: 15348
글쓴 사람
정성태 (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

비밀번호

댓글 작성자
 




... 136  137  138  139  140  141  142  143  144  145  [146]  147  148  149  150  ...
NoWriterDateCnt.TitleFile(s)
1403정성태1/14/201331976.NET Framework: 357. .NET 4.5의 2GB 힙 한계 극복
1402정성태1/14/201332540오류 유형: 166. SmtpClient.Send 오류 - net_io_connectionclosed
1401정성태1/11/201329866.NET Framework: 356. (공개키를 담은) 자바의 key 파일을 닷넷의 RSACryptoServiceProvider에서 사용하는 방법 [2]파일 다운로드1
1400정성태1/10/201329009Windows: 69. 작업표시줄의 터치 키보드(Touch Keyboard) 없애는 방법 [3]
1399정성태1/9/201324643.NET Framework: 355. 닷넷 환경이 왜 C/C++보다 느릴까요? [8]
1398정성태1/8/201325088오류 유형: 165. 새로 설치한 Visual Studio 2010의 Team Explorer 실행시 비정상 종료가 된다면?
1397정성태1/3/201328578Windows: 68. 윈도우 설치 ISO 이미지를 USB 하드에 적용하는 방법 [2]
1396정성태12/27/201229797사물인터넷: 2. 넷두이노 - 4.2.0 펌웨어 업데이트 방법 [1]파일 다운로드1
1395정성태12/26/201220657.NET Framework: 354. x64 - AspCompat과 STA COM 개체가 성능에 미치는 영향
1394정성태12/25/201222094.NET Framework: 353. x86 - AspCompat과 STA COM 개체가 성능에 미치는 영향
1393정성태12/25/201222471.NET Framework: 352. x64에서 필수로 지정하도록 바뀐 STAThread 특성 [2]
1392정성태12/21/201232480사물인터넷: 1. .NET Micro Framework - 넷두이노 플러스 [7]
1391정성태12/21/201225880.NET Framework: 351. JavaScriptSerializer, DataContractJsonSerializer, Json.NET [3]파일 다운로드1
1390정성태12/20/201223950.NET Framework: 350. String 데이터를 Stream으로 변환하는 방법 [2]
1389정성태12/12/201222271.NET Framework: 349. .NET Thread 인스턴스로부터 COM Apartment 유형 확인하는 방법파일 다운로드1
1388정성태12/12/201223335.NET Framework: 348. .NET x64 응용 프로그램에서 Teb 주소를 구하는 방법파일 다운로드1
1387정성태12/12/201228259VC++: 64. x64 Visual C++에서 TEB 주소 구하는 방법
1386정성태12/12/201229954디버깅 기술: 53. windbg - 덤프 파일로부터 네이티브 DLL을 추출하는 방법 [1]
1385정성태12/12/201225036디버깅 기술: 52. Windbg - The version of SOS does not match the version of CLR you are debugging.
1384정성태12/12/201229863개발 환경 구성: 178. System32 폴더의 64비트 DLL을 32비트 Depends.exe에서 보는 방법
1383정성태12/10/201225766개발 환경 구성: 177. 기업용 메신저를 위한 Office Communicator Server 2007 설치 [1]
1382정성태12/8/201228632개발 환경 구성: 176. WebPagetest 서버 - 설치 및 테스트
1381정성태12/5/201227121.NET Framework: 347. C# - 프로세스(EXE) 수준의 Singleton 개체 생성 [2]파일 다운로드1
1380정성태11/28/201237159.NET Framework: 346. 닷넷 개발자에게 Node.js의 의미 [17]
1379정성태11/26/201230313.NET Framework: 345. C# 부호(+, -)에 대한 비트 변환
1378정성태11/22/201231625Java: 14. 안드로이드 - Hello World 실습 [7]
... 136  137  138  139  140  141  142  143  144  145  [146]  147  148  149  150  ...