Microsoft MVP성태의 닷넷 이야기
글쓴 사람
정성태 (techsharer at outlook.com)
홈페이지
첨부 파일
 

(시리즈 글이 3개 있습니다.)
개발 환경 구성: 707. 빌드한 Unity3D 프로그램을 C++ Windows Application에 통합하는 방법
; https://www.sysnet.pe.kr/2/0/13581

개발 환경 구성: 708. Unity3D - C# Windows Forms / WPF Application에 통합하는 방법
; https://www.sysnet.pe.kr/2/0/13584

닷넷: 2232. C# - Unity + 닷넷 App(WinForms/WPF) 간의 Named Pipe 통신
; https://www.sysnet.pe.kr/2/0/13588




빌드한 Unity3D 프로그램을 C++ Windows Application에 통합하는 방법

Unity에서 (간단한 프로젝트를 만들어) Windows 대상으로 빌드하면 다음과 같은 내용을 가진 출력물이 나옵니다.

C:\temp\unity> tree /F
...[생략]...
│   My project.exe
│   UnityCrashHandler64.exe
│   UnityPlayer.dll
├───MonoBleedingEdge
│   ├───EmbedRuntime
│   └───etc
│       └───mono
│           ├───2.0
│           │   └───Browsers
│           ├───4.0
│           │   └───Browsers
│           ├───4.5
│           │   └───Browsers
│           └───mconfig
└───My project_Data
    ├───Managed
    └───Resources

루트에 보면 "My project.exe"와 "UnityPlayer.dll"이 있는데, 그중에서 dll의 export 함수를 조사해 보면,

c:\temp\unity> dumpbin /EXPORTS UnityPlayer.dll
Microsoft (R) COFF/PE Dumper Version 14.35.32217.1
Copyright (C) Microsoft Corporation.  All rights reserved.


Dump of file UnityPlayer.dll

File Type: DLL

  Section contains the following exports for UnityPlayer.dll

    00000000 characteristics
    FFFFFFFF time date stamp
        0.00 version
           1 ordinal base
           1 number of functions
           1 number of names

    ordinal hint RVA      name

          1    0 007FBE20 UnityMain

  Summary

      126000 .data
      10E000 .pdata
      390000 .rdata
       1E000 .reloc
        1000 .rodata
        1000 .rsrc
     1832000 .text
       11000 _RDATA

단 하나의 UnityMain 함수가 보입니다. 검색해 보면, 다음과 같이 Unity 측에서 공식 사용법을 제공하고 있습니다. ^^

Using Unity as a Library in other applications
; https://docs.unity3d.com/2021.3/Documentation/Manual/UnityasaLibrary.html

Integrating Unity into Windows applications
; https://docs.unity3d.com/2021.3/Documentation/Manual/UnityasaLibrary-Windows.html

방법은 2가지인데, 1) 유니티를 자식 프로세스로 실행(위의 예제의 경우 "My Project.exe"를 실행)하면서 명령행에 현재 프로세스가 소유한 윈도우 핸들을 "-parentHWND" 인자로 넘겨줘 유니티로 하여금 (마치 Internet Explorer의 LCIE처럼) 그 윈도우에 출력을 하도록 만드는 방법과, 2) "UnityPlayer.dll"을 직접 로드해 UnityMain 함수를 호출하면서,

extern "C" UNITY_API int UnityMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPWSTR lpCmdLine, int nShowCmd);

마찬가지로 lpCmdLine 인자에 활성화시킬 부모 윈도우의 핸들 값을 넘기는 것입니다. 오호~~~ 생각보다 간단하군요. ^^

자, 그렇다면 직접 해볼까요?

우선, C++ Desktop Window Application을 간단하게 하나 만들고, WM_KEYDOWN 이벤트에서 F2 키를 누르면 UnityPlayer.dll을 로드해 UnityMain 함수를 실행하는 코드를 다음과 같이 넣어보겠습니다.

...[생략]...

    case WM_KEYDOWN:
    {
        if (wParam == VK_F2)
        {
            HMODULE hModule = ::LoadLibrary(L"C:\\temp\\unity\\UnityPlayer.dll");
            UnityMainFuncPtr unityMainFunc = (UnityMainFuncPtr)::GetProcAddress(hModule, "UnityMain");
            if (unityMainFunc == nullptr)
            {
                return DefWindowProc(hWnd, message, wParam, lParam);
            }

            wchar_t buffer[1024] = { 0 };
            wsprintf(buffer, L"-parentHWND %I64u", (__int64)hWnd);

            unityMainFunc(hInst, nullptr, buffer, SW_SHOWDEFAULT);
        }
    }

...[생략]...

실행해 보면, 이런 오류 메시지 창이 뜨는데요,

Data folder not found

Application folder:
C:/unity/ConsoleApplication1/x64/Debug
There should be 'WindowsProject1_Data'
folder next to the executable

2가지 문제가 있음을 알 수 있습니다.

  1. UnityPlayer.dll이 아닌, C++ 프로젝트의 EXE 빌드 위치를 기준으로 실행
  2. Unity 데이터를 찾는 디렉터리 이름이 "[EXE 실행 파일 이름]_Data"로 고정

따라서, 위의 실습을 제대로 하려면 C++ 프로젝트의 빌드 결과를 Unity 빌드 디렉터리(이 글의 예제에서는 "C:\temp\unity")로 맞춰야 하고, 실행 파일명도 "WindowsProject1"이 아닌 "My Project.exe"로 해야 합니다. 이를 위해 프로젝트 속성 창을 이용하거나, vcxproj의 내용을 편집해 아래와 같이 맞춰주시면 됩니다.

...[생략]...
  <PropertyGroup>
    <OutDir>C:\temp\unity</OutDir>
    <TargetName>My Project</TargetName>
  </PropertyGroup>
...[생략]...

자, 이제 다시 실행해서 F2 키를 누르면 ^^ 다음과 같이 우리가 만든 Window 프레임의 자식으로 Unity가 활성화됩니다.

unity_embed_cpp_1.png




일단 UnityMain 함수를 실행하면, 이후 해당 스레드는 블록킹됩니다. 즉, 아래와 같이 코딩한 경우,

unityMainFunc(hInst, nullptr, buffer, SW_SHOWDEFAULT); // 이 함수에서 제어를 반환하지 않음!
::OutputDebugString(L"UnityMain called\n");

디버깅 콘솔에 "UnityMain called" 메시지는 볼 수 없습니다. 그렇기 때문에 현실적인 상황에서는 별도의 스레드를 생성해 호출하게 될 것입니다.

...[생략]...
case WM_KEYDOWN:
{
    if (wParam == VK_F2)
    {
        std::thread t([&]() 
            {
                //...[생략]...

                wchar_t buffer[1024] = { 0 };
                wsprintf(buffer, L"-parentHWND %I64u", (__int64)hWnd);

                int result = unityMainFunc(hInst, nullptr, buffer, SW_SHOWDEFAULT);
            });

        t.detach();
    }
}
...[생략]...

그리고 이유는 알 수 없지만, Visual Studio 디버깅 중에는 위와 같이 Unity를 호스팅하는 경우 해당 프로세스를 종료하면 unityMainFunc를 벗어나자마자 AV(Access violation) 예외가 발생합니다.

unityMainFunc(hInst, nullptr, buffer, SW_SHOWDEFAULT);
::OutputDebugString(L"UnityMainFunc called\n");

Exception thrown at 0x0000000000000000 in My Project.exe: 0xC0000005: Access violation executing location 0x0000000000000000.

디버깅에만 나오는 것이므로 무시할 수는 있지만, ... 개발 중에는 상당히 신경이 쓰이는군요. ^^; 이때의 호출 스택을 보면,

    0000000000000000()  Unknown No symbols loaded.
    UnityPlayer.dll!00007ffcfefd6cbe()  Unknown No symbols loaded.
    UnityPlayer.dll!00007ffcff085490()  Unknown No symbols loaded.
    UnityPlayer.dll!00007ffcff661e60()  Unknown No symbols loaded.
    UnityPlayer.dll!00007ffcff6605be()  Unknown No symbols loaded.
    UnityPlayer.dll!00007ffcfeba7917()  Unknown No symbols loaded.
    UnityPlayer.dll!00007ffcfee17ef8()  Unknown No symbols loaded.
    UnityPlayer.dll!00007ffcff05bbea()  Unknown No symbols loaded.
    UnityPlayer.dll!00007ffcff05be2b()  Unknown No symbols loaded.
>    My Project.exe!WndProc::__l11::<lambda_1>::operator()() Line 180  C++ Symbols loaded.
    My Project.exe!std::invoke<`WndProc'::`11'::<lambda_1>>(WndProc::__l11::<lambda_1> && _Obj) Line 1753 C++ Non-user code. Symbols loaded.
    My Project.exe!std::thread::_Invoke<std::tuple<`WndProc'::`11'::<lambda_1>>,0>(void * _RawVals) Line 56   C++ Non-user code. Symbols loaded.
    ucrtbased.dll!thread_start<unsigned int (__cdecl*)(void *),1>(void * const parameter) Line 97 C++ Non-user code. Symbols loaded.
    kernel32.dll!BaseThreadInitThunk()  Unknown Non-user code. Symbols loaded without source information.
    ntdll.dll!RtlUserThreadStart()  Unknown Non-user code. Symbols loaded without source information.

UnityPlayer.dll 내부에서 발생하는 것이므로 (x64라서 상관없겠지만) 호출 규약이 틀어져 발생하는 것도 아니라 더 해볼 것이 없습니다.




마지막으로, 정확한 재현 규칙은 알 수 없지만 UnityMain을 부를 때 이런 예외가 발생할 때가 있습니다.

Fatal error
Failed to create window

위의 오류는 "-parentHWND"로 전달한 인자가 잘못된 값이거나 형식일 때 발생하는데요, 간혹 정확한 값을 전달했는데도 간헐적으로 발생하는 경우가 있습니다.




[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]







[최초 등록일: ]
[최종 수정일: 3/18/2024]

Creative Commons License
이 저작물은 크리에이티브 커먼즈 코리아 저작자표시-비영리-변경금지 2.0 대한민국 라이센스에 따라 이용하실 수 있습니다.
by SeongTae Jeong, mailto:techsharer at outlook.com

비밀번호

댓글 작성자
 




... 31  32  33  34  35  36  37  38  39  40  41  42  [43]  44  45  ...
NoWriterDateCnt.TitleFile(s)
12577정성태3/26/202111407개발 환경 구성: 559. Docker Desktop for Windows 기반의 Kubernetes 구성 - WSL 2 인스턴스에 kind 도구로 k8s 클러스터 구성
12576정성태3/25/20219237개발 환경 구성: 558. Docker Desktop for Windows에서 DockerDesktopVM 기반의 Kubernetes 구성 (2) - k8s 서비스 위치
12575정성태3/24/20218252개발 환경 구성: 557. Docker Desktop for Windows에서 DockerDesktopVM 기반의 Kubernetes 구성
12574정성태3/23/202112390.NET Framework: 1030. C# Socket의 Close/Shutdown 동작 (동기 모드)
12573정성태3/22/202110170개발 환경 구성: 556. WSL 인스턴스 초기 설정 명령어 [1]
12572정성태3/22/20219692.NET Framework: 1029. C# - GC 호출로 인한 메모리 압축(Compaction)을 확인하는 방법파일 다운로드1
12571정성태3/21/20218852오류 유형: 706. WSL 2 기반으로 "Enable Kubernetes" 활성화 시 초기화 실패 [1]
12570정성태3/19/202113158개발 환경 구성: 555. openssl - CA로부터 인증받은 새로운 인증서를 생성하는 방법
12569정성태3/18/202112131개발 환경 구성: 554. WSL 인스턴스 export/import 방법 및 단축 아이콘 설정 방법
12568정성태3/18/20217621오류 유형: 705. C# 빌드 - Couldn't process file ... due to its being in the Internet or Restricted zone or having the mark of the web on the file.
12567정성태3/17/20219045개발 환경 구성: 553. Docker Desktop for Windows를 위한 k8s 대시보드 활성화 [1]
12566정성태3/17/20219410개발 환경 구성: 552. Kubernetes - kube-apiserver와 REST API 통신하는 방법 (Docker Desktop for Windows 환경)
12565정성태3/17/20216833오류 유형: 704. curl.exe 실행 시 dll not found 오류
12564정성태3/16/20217314VS.NET IDE: 160. 새 프로젝트 창에 C++/CLI 프로젝트 템플릿이 없는 경우
12563정성태3/16/20219264개발 환경 구성: 551. C# - JIRA REST API 사용 정리 (3) jira-oauth-cli 도구를 이용한 키 관리
12562정성태3/15/202110465개발 환경 구성: 550. C# - JIRA REST API 사용 정리 (2) JIRA OAuth 토큰으로 API 사용하는 방법파일 다운로드1
12561정성태3/12/20219077VS.NET IDE: 159. Visual Studio에서 개행(\n, \r) 등의 제어 문자를 치환하는 방법 - 정규 표현식 사용
12560정성태3/11/202110322개발 환경 구성: 549. ssh-keygen으로 생성한 개인키/공개키 파일을 각각 PKCS8/PEM 형식으로 변환하는 방법
12559정성태3/11/20219824.NET Framework: 1028. 닷넷 5 환경의 Web API에 OpenAPI 적용을 위한 NSwag 또는 Swashbuckle 패키지 사용 [2]파일 다운로드1
12558정성태3/10/20219278Windows: 192. Power Automate Desktop (Preview) 소개 - Bitvise SSH Client 제어 [1]
12557정성태3/10/20217989Windows: 191. 탐색기의 보안 탭에 있는 "Object name" 경로에 LEFT-TO-RIGHT EMBEDDING 제어 문자가 포함되는 문제
12556정성태3/9/20217194오류 유형: 703. PowerShell ISE의 Debug / Toggle Breakpoint 메뉴가 비활성 상태인 경우
12555정성태3/8/20219295Windows: 190. C# - 레지스트리에 등록된 DigitalProductId로부터 라이선스 키(Product Key)를 알아내는 방법파일 다운로드2
12554정성태3/8/20219066.NET Framework: 1027. 닷넷 응용 프로그램을 위한 PDB 옵션 - full, pdbonly, portable, embedded
12553정성태3/5/20219574개발 환경 구성: 548. 기존 .NET Framework 프로젝트를 .NET Core/5+ 용으로 변환해 주는 upgrade-assistant, try-convert 도구 소개 [4]
12552정성태3/5/20218757개발 환경 구성: 547. github workflow/actions에서 Visual Studio Marketplace 패키지 등록하는 방법
... 31  32  33  34  35  36  37  38  39  40  41  42  [43]  44  45  ...