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

비밀번호

댓글 작성자
 




... 16  17  18  19  20  21  22  23  [24]  25  26  27  28  29  30  ...
NoWriterDateCnt.TitleFile(s)
13026정성태4/11/20228529.NET Framework: 1191. C 언어로 작성된 FFmpeg Examples의 C# 포팅 전체 소스 코드 [3]
13025정성태4/11/20227890.NET Framework: 1190. C# - ffmpeg(FFmpeg.AutoGen)를 이용한 vaapi_encode.c, vaapi_transcode.c 예제 포팅
13024정성태4/7/20226402.NET Framework: 1189. C# - 런타임 환경에 따라 달라진 AppDomain.GetCurrentThreadId 메서드
13023정성태4/6/20226688.NET Framework: 1188. C# - ffmpeg(FFmpeg.AutoGen)를 이용한 transcoding.c 예제 포팅 [3]
13022정성태3/31/20226586Windows: 202. 윈도우 11 업그레이드 - "PC Health Check"를 통과했지만 여전히 업그레이드가 안 되는 경우 해결책
13021정성태3/31/20226762Windows: 201. Windows - INF 파일을 이용한 장치 제거 방법
13020정성태3/30/20226479.NET Framework: 1187. RDP 접속 시 WPF UserControl의 Unloaded 이벤트 발생파일 다운로드1
13019정성태3/30/20226479.NET Framework: 1186. Win32 Message를 Code로부터 메시지 이름 자체를 구하고 싶다면?파일 다운로드1
13018정성태3/29/20227009.NET Framework: 1185. C# - Unsafe.AsPointer가 반환한 포인터는 pinning 상태일까요? [5]
13017정성태3/28/20226854.NET Framework: 1184. C# - GC Heap에 위치한 참조 개체의 주소를 알아내는 방법 - 두 번째 이야기 [3]
13016정성태3/27/20227677.NET Framework: 1183. C# 11에 추가된 ref 필드의 (우회) 구현 방법파일 다운로드1
13015정성태3/26/20229047.NET Framework: 1182. C# 11 - ref struct에 ref 필드를 허용 [1]
13014정성태3/23/20227620VC++: 155. CComPtr/CComQIPtr과 Conformance mode 옵션의 충돌 [1]
13013정성태3/22/20225956개발 환경 구성: 641. WSL 우분투 인스턴스에 파이썬 2.7 개발 환경 구성하는 방법
13012정성태3/21/20225292오류 유형: 803. C# - Local '...' or its members cannot have their address taken and be used inside an anonymous method or lambda expression
13011정성태3/21/20226764오류 유형: 802. 윈도우 운영체제에서 웹캠 카메라 인식이 안 되는 경우
13010정성태3/21/20225707오류 유형: 801. Oracle.ManagedDataAccess.Core - GetTypes 호출 시 "Could not load file or assembly 'System.DirectoryServices.Protocols...'" 오류
13009정성태3/20/20227283개발 환경 구성: 640. docker - ibmcom/db2 컨테이너 실행
13008정성태3/19/20226582VS.NET IDE: 176. 비주얼 스튜디오 - 솔루션 탐색기에서 프로젝트를 선택할 때 csproj 파일이 열리지 않도록 만드는 방법
13007정성태3/18/20226205.NET Framework: 1181. C# - Oracle.ManagedDataAccess의 Pool 및 그것의 연결 개체 수를 알아내는 방법파일 다운로드1
13006정성태3/17/20227270.NET Framework: 1180. C# - ffmpeg(FFmpeg.AutoGen)를 이용한 remuxing.c 예제 포팅
13005정성태3/17/20226103오류 유형: 800. C# - System.InvalidOperationException: Late bound operations cannot be performed on fields with types for which Type.ContainsGenericParameters is true.
13004정성태3/16/20226130디버깅 기술: 182. windbg - 닷넷 메모리 덤프에서 AppDomain에 걸친 정적(static) 필드 값을 조사하는 방법
13003정성태3/15/20226304.NET Framework: 1179. C# - (.NET Framework를 위한) Oracle.ManagedDataAccess 패키지의 성능 카운터 설정 방법
13002정성태3/14/20227069.NET Framework: 1178. C# - ffmpeg(FFmpeg.AutoGen)를 이용한 http_multiclient.c 예제 포팅
13001정성태3/13/20227434.NET Framework: 1177. C# - 닷넷에서 허용하는 메서드의 매개변수와 호출 인자의 최대 수
... 16  17  18  19  20  21  22  23  [24]  25  26  27  28  29  30  ...