Microsoft MVP성태의 닷넷 이야기
.NET Framework: 972. DNNE가 출력한 NE DLL을 직접 생성하는 방법 [링크 복사], [링크+제목 복사]
조회: 8514
글쓴 사람
정성태 (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)
13482정성태12/13/20232896닷넷: 2183. C# - eFriend Expert OCX 예제를 .NET Core/5+ Console App에서 사용하는 방법 [2]파일 다운로드1
13481정성태12/13/20232281개발 환경 구성: 693. msbuild - .NET Core/5+ 프로젝트에서 resgen을 이용한 리소스 파일 생성 방법파일 다운로드1
13480정성태12/12/20232649개발 환경 구성: 692. Windows WSL 2 + Chrome 웹 브라우저 설치
13479정성태12/11/20232335개발 환경 구성: 691. WSL 2 (Ubuntu) + nginx 환경 설정
13477정성태12/8/20232526닷넷: 2182. C# - .NET 7부터 추가된 Int128, UInt128 [1]파일 다운로드1
13476정성태12/8/20232261닷넷: 2181. C# - .NET 8 JsonStringEnumConverter의 AOT를 위한 개선파일 다운로드1
13475정성태12/7/20232323닷넷: 2180. .NET 8 - 함수 포인터에 대한 Reflection 정보 조회파일 다운로드1
13474정성태12/6/20232172개발 환경 구성: 690. 닷넷 코어/5+ 버전의 ilasm/ildasm 실행 파일 구하는 방법 - 두 번째 이야기
13473정성태12/5/20232377닷넷: 2179. C# - 값 형식(Blittable)을 메모리 복사를 이용해 바이트 배열로 직렬화/역직렬화파일 다운로드1
13472정성태12/4/20232191C/C++: 164. Visual C++ - InterlockedCompareExchange128 사용 방법
13471정성태12/4/20232269Copilot - To enable GitHub Copilot, authorize this extension using GitHub's device flow
13470정성태12/2/20232563닷넷: 2178. C# - .NET 8부터 COM Interop에 대한 자동 소스 코드 생성 도입파일 다운로드1
13469정성태12/1/20232283닷넷: 2177. C# - (Interop DLL 없이) CoClass를 이용한 COM 개체 생성 방법파일 다운로드1
13468정성태12/1/20232224닷넷: 2176. C# - .NET Core/5+부터 달라진 RCW(Runtime Callable Wrapper) 대응 방식파일 다운로드1
13467정성태11/30/20232317오류 유형: 882. C# - Unhandled exception. System.Runtime.InteropServices.COMException (0x800080A5)파일 다운로드1
13466정성태11/29/20232489닷넷: 2175. C# - DllImport 메서드의 AOT 지원을 위한 LibraryImport 옵션
13465정성태11/28/20232240개발 환경 구성: 689. MSBuild - CopyToOutputDirectory가 "dotnet publish" 시에는 적용되지 않는 문제파일 다운로드1
13464정성태11/28/20232374닷넷: 2174. C# - .NET 7부터 UnmanagedCallersOnly 함수 export 기능을 AOT 빌드에 통합파일 다운로드1
13463정성태11/27/20232302오류 유형: 881. Visual Studio - NU1605: Warning As Error: Detected package downgrade
13462정성태11/27/20232342오류 유형: 880. Visual Studio - error CS0246: The type or namespace name '...' could not be found
13461정성태11/26/20232377닷넷: 2173. .NET Core 3/5+ 기반의 COM Server를 registry 등록 없이 사용하는 방법파일 다운로드1
13460정성태11/26/20232330닷넷: 2172. .NET 6+ 기반의 COM Server 내에 Type Library를 내장하는 방법파일 다운로드1
13459정성태11/26/20232302닷넷: 2171. .NET Core 3/5+ 기반의 COM Server를 기존의 regasm처럼 등록하는 방법파일 다운로드1
13458정성태11/26/20232312닷넷: 2170. .NET Core/5+ 기반의 COM Server를 tlb 파일을 생성하는 방법(tlbexp)
13457정성태11/25/20232257VS.NET IDE: 187. Visual Studio - 16.9 버전부터 추가된 "Display inline type hints" 옵션
13456정성태11/25/20232556닷넷: 2169. C# - OpenAI를 사용해 PDF 데이터를 대상으로 OpenAI 챗봇 작성 [1]파일 다운로드1
1  2  3  4  5  [6]  7  8  9  10  11  12  13  14  15  ...