성태의 닷넷 이야기
홈 주인
모아 놓은 자료
프로그래밍
질문/답변
사용자 관리
사용자
메뉴
아티클
외부 아티클
유용한 코드
온라인 기능
MathJax 입력기
최근 덧글
[정성태] Detecting blocking calls using asyn...
[정성태] 아쉽게도, 커뮤니티는 아니고 개인 블로그입니다. ^^
[정성태] 질문이 잘 이해가 안 됩니다. 우선, 해당 소스코드에서 ILis...
[양승조
] var대신 dinamic으로 선언해서 해결은 했습니다. 맞는 해...
[양승조
] 또 막혔습니다. ㅠㅠ var list = props[i].Ge...
[양승조
] 아. 감사합니다. 어제는 안됐던것 같은데....정신을 차려야겠네...
[정성태] "props[i].GetValue(props[i])" 코드에서 ...
[정성태] 저렇게 조각 코드 말고, 실제로 재현이 되는 예제 프로젝트를 압...
[정성태] Modules 창(Ctrl+Shift+U)을 띄워서, 해당 Op...
[정성태] 만드실 수 있습니다. 단지, Unity 엔진 내의 스크립트와 W...
글쓰기
제목
이름
암호
전자우편
HTML
홈페이지
유형
제니퍼 .NET
닷넷
COM 개체 관련
스크립트
VC++
VS.NET IDE
Windows
Team Foundation Server
디버깅 기술
오류 유형
개발 환경 구성
웹
기타
Linux
Java
DDK
Math
Phone
Graphics
사물인터넷
부모글 보이기/감추기
내용
<div style='display: inline'> <h1 style='font-family: Malgun Gothic, Consolas; font-size: 20pt; color: #006699; text-align: center; font-weight: bold'>DNNE가 출력한 NE DLL을 직접 생성하는 방법</h1> <p> 지난 글에서,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > UnmanagedCallersOnly 특성과 DNNE 사용 ; <a target='tab' href='https://www.sysnet.pe.kr/2/0/12415'>https://www.sysnet.pe.kr/2/0/12415</a> </pre> <br /> DNNE가 사실상 AOT가 아닌, 껍데기 native 함수를 만들어 주는 것에 불과한 것을 설명했는데요, 따라서 DNNE가 생성한 proxy dll을 우리도 직접 빌드해 만드는 것이 가능합니다. 실제로 DNNE는 그런 경우를 위해 자신이 생성한 헤더 파일에 DNNE_COMPILE_AS_SOURCE라는 옵션도 제공합니다.<br /> <br /> 한 번 직접 해볼까요? ^^<br /> <br /> <hr style='width: 50%' /><br /> <br /> 우선, Visual Studio로 C/C++ DLL 프로젝트를 생성 후, <a target='tab' href='https://www.sysnet.pe.kr/2/0/12415'>지난번 예제 프로젝트 중 ClassLibrary2</a>를 DNNE로 빌드한 폴더에서 <a target='tab' href='https://www.sysnet.pe.kr/2/0/12415#dnne_output'>아래의 2개 파일</a>을 복사해 옵니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > ClassLibrary2NE.h dnne.h </pre> <br /> 그런 다음 우리가 만든 C++ 프로젝트의 MyProxy.cpp 파일에 ClassLibrary2NE.h를 포함시킨 후,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > #include "pch.h" #include "framework.h" #include "MyProxy.h" <span style='color: blue; font-weight: bold'>#define DNNE_COMPILE_AS_SOURCE #include "ClassLibrary2NE.h"</span> </pre> <br /> 빌드하면 이런 오류가 발생합니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > Error C1083 Cannot open include file: 'dnne.h': No such file or directory </pre> <br /> <a target='tab' href='https://www.sysnet.pe.kr/2/0/12415#dnne_header'>이전 글에도 설명한 그 에러</a>로, 이 글에서는 간단하게 ClassLibrary2NE.h 파일을 열어 소스 코드를 수정하는 것으로 해결하겠습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > // #include <dnne.h> #include "dnne.h" </pre> <br /> 다시 빌드하면 이번엔 get_callable_managed_function 코드에서,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > 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"); <span style='color: blue; font-weight: bold'>mymethod_netcore_ptr = get_callable_managed_function(t1_name, methodName, delegateType);</span> } mymethod_netcore_ptr(ptrText); } </pre> <br /> 형 변환이 안 된다는 오류가 발생합니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > Error C2440 '=': cannot convert from 'void *' to 'void (__cdecl *)(intptr_t)' </pre> <br /> 이를 위해 MyProxy.cpp에 함수 포인터를 위한 타입을 하나 정의하고,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > #include "pch.h" #include "framework.h" #include "MyProxy.h" #include "dnne.h" #define DNNE_COMPILE_AS_SOURCE <span style='color: blue; font-weight: bold'>typedef void (DNNE_CALLTYPE* mymethod_netcore_proc)(intptr_t ptrText);</span> #include "ClassLibrary2NE.h" </pre> <br /> get_callable_managed_function 함수의 반환값을 강제 형변환 해줍니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > 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 = <span style='color: blue; font-weight: bold'>(mymethod_netcore_proc)</span>get_callable_managed_function(t1_name, methodName, delegateType); } mymethod_netcore_ptr(ptrText); } </pre> <br /> 이제 당연히 get_callable_managed_function이 없다는 오류가 발생할 텐데요, 이것은 DNNE가 설치된 NuGet cache에서 "platform.c" 파일로 찾을 수 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > <a target='tab' href='https://www.sysnet.pe.kr/2/0/12135'>F:\nuget_root</a>\dnne\1.0.16\tools\platform\platform.c </pre> <br /> 따라서 이 파일을 복사해 DLL 프로젝트에 포함시키고 빌드하면... 휴... 또 에러가 발생합니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > fatal error C1010: unexpected end of file while looking for precompiled header. Did you forget to add '#include "pch.h"' to your source? </pre> <br /> 이를 위해 platform.c 파일의 속성창으로 들어가 "Configuration Properties" / "C/C++" / "Precompiled Headers"에서 "Precompiled Header" 옵션을 "Use (/Yu)"에서 "Not Using Precompiled Headers"로 변경할 수 있지만, 여기서는 편의상 pch.h 파일을 platform.c에서 추가하는 걸로 변경하겠습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > #include "pch.h" </pre> <br /> 그리고 또 빌드하면, ^^;<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > Error C1189 #error: Target assembly name must be defined. Set 'DNNE_ASSEMBLY_NAME'. </pre> <br /> 오류 수정을 위해 pch.h 전역 헤더 파일에 DNNE_ASSEMBLY_NAME 전처리 상수를 실제 구현을 담고 있는 .NET 어셈블리의 이름으로 설정합니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > // pch.h #ifndef PCH_H #define PCH_H // add headers that you want to pre-compile here #include "framework.h" <span style='color: blue; font-weight: bold'>#define DNNE_ASSEMBLY_NAME ClassLibrary2</span> #endif //PCH_H </pre> <br /> 또 빌드하면,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > 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) </pre> <br /> 쉽게 갑시다. ^^ 그냥 platform.c를 platform.cpp로 변경합니다. 또 빌드하면,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > Error C1083 Cannot open include file: 'nethost.h': No such file or directory </pre> <br /> platform.cpp에 포함된 include 파일이 없는 것입니다. 이 파일은 각각의 .NET Core SDK에 포함되어 있는데,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > C:\> <span style='color: blue; font-weight: bold'>dir /a/s nethost.h | findstr native</span> 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 </pre> <br /> 이 글에서는 "C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Host.win-x64\5.0.0\runtimes\win-x64\native" 폴더에 있는 nethost.h를 선택해 platform.cpp에 포함하겠습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > // Include the official nethost API and indicate // consumption should be as a static library. <span style='color: blue; font-weight: bold'>#define NETHOST_USE_AS_STATIC</span> // #include <nethost.h> <span style='color: blue; font-weight: bold'>#include "C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Host.win-x64\5.0.0\runtimes\win-x64\native\nethost.h"</span> </pre> <br /> <a name='nethost_h'></a> 혹은, 해당 헤더 파일의 내용이 간단하므로 (윈도우만을 대상으로 한다면) pch.h에 그냥 nethost.h 대신 다음의 내용을 넣어도 됩니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > 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); </pre> <br /> 다시 빌드하면,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > Error LNK2019 unresolved external symbol get_hostfxr_path referenced in function "void __cdecl load_hostfxr(void)" (?load_hostfxr@@YAXXZ) </pre> <br /> 당연히 구현체가 없으니 오류가 날 텐데, 이것 역시 <a target='tab' href='https://www.sysnet.pe.kr/2/0/12419'>nethost.dll에 정의가 있으므로 nethost.lib로 링크할 수도 있지만 DNNE의 경우 정적 링크</a>를 하고 있으므로 이 글에서도 libnethost.lib에 정적 링크를 하겠습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > // 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. <span style='color: blue; font-weight: bold'>#pragma comment(lib, "C:\\Program Files\\dotnet\\packs\\Microsoft.NETCore.App.Host.win-x64\\5.0.0\\runtimes\\win-x64\\native\\libnethost.lib")</span> </pre> <br /> 자, <a target='tab' href='https://www.sysnet.pe.kr/2/0/12419#build_warning'>이제 약간의 경고가 나오지만</a> 빌드는 일단 잘 됩니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > 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 ========== </pre> <br /> 빌드한 MyProxy.dll을 depends로 확인하면 이렇게 export된 것을 확인할 수 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > ?mymethod_netcore@@YAX_J@Z ?preload_runtime@@YAXXZ ?set_failure_callback@@YAXP6AXW4failure_type@@H@Z@Z get_hostfxr_path </pre> <br /> <a target='tab' href='https://www.sysnet.pe.kr/2/0/11132#undname'>"?mymethod_netcore@@YAX_J@Z"는 "void __cdecl mymethod_netcore(__int64)" 의미</a>이므로 우리가 원했던 이름이 아닙니다. 따라서, 다시 "ClassLibrary2NE.h" 헤더 파일의 <a target='tab' href='https://www.sysnet.pe.kr/2/0/12414'>mymethod_netcore 선언에 'extern "C"'로 변경</a>해 주고,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > // Computed from ClassLibrary.Class1.MyMethod <span style='color: blue; font-weight: bold'>extern "C" { </span> DNNE_API void DNNE_CALLTYPE mymethod_netcore(intptr_t ptrText); <span style='color: blue; font-weight: bold'>}</span> </pre> <br /> 다시 빌드하면, 마침내 우리가 원하는 양식으로 export가 되었습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > ?preload_runtime@@YAXXZ ?set_failure_callback@@YAXP6AXW4failure_type@@H@Z@Z get_hostfxr_path mymethod_netcore </pre> <br /> <hr style='width: 50%' /><br /> <br /> 자, 그럼 DNNE가 하는 작업을 대신해 우리가 직접 생성한 프록시 DLL을 사용해 보겠습니다.<br /> <br /> 빌드한 디렉터리에 지난번 실습의 ClassLibrary2 어셈블리 파일과 runtimeconfig.json을 복사해 와, <br /> <br /> <ul> <li>ClassLibrary2.dll</li> <li>ClassLibrary2.runtimeconfig.json</li> </ul> <br /> 다음과 같은 C++ 콘솔 프로그램에서 실행하면,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > #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); } </pre> <br /> 정상적으로 실행하는 것을 확인할 수 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > [NETCORE] 2020-11-15 오전 11:02:39 Hello World </pre> <br /> (<a target='tab' href='https://www.sysnet.pe.kr/bbs/DownloadAttachment.aspx?fid=1664&boardid=331301885'>첨부 파일은 이 글의 예제 코드를 포함</a>합니다.)<br /> </p><br /> <br /><hr /><span style='color: Maroon'>[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]</span> </div>
첨부파일
스팸 방지용 인증 번호
1312
(왼쪽의 숫자를 입력해야 합니다.)