Microsoft MVP성태의 닷넷 이야기
글쓴 사람
정성태 (techsharer at outlook.com)
홈페이지
첨부 파일
(연관된 글이 1개 있습니다.)

C/C++ - Windows 10 Version 1607부터 지원하는 /DEPENDENTLOADFLAG 옵션

아래의 글에,

How can I try to escape the disease-ridden hot-tubs known as the TEMP and Downloads directories?
; https://devblogs.microsoft.com/oldnewthing/20230328-00/?p=107978

새로운 옵션이 소개됐군요. ^^

/DEPENDENTLOADFLAG (Set default dependent load flags)
; https://learn.microsoft.com/en-us/cpp/build/reference/dependentloadflag

이 옵션은 LoadLibraryEx의 dwFlags 인자와 동일한 값을 취하고, 그 역할도 같습니다. 단지 차이점이라면, LoadLibraryEx는 사용자가 직접 호출하는 코드에 flags를 지정하게 되는 반면, /DEPENDENTLOADFLAG는 정적으로 링크된 DLL들에 대해 자동으로 적용된다는 점입니다.

일반적으로는 DLL은 다음의 문서에 따라,

Dynamic-link library search order
; https://learn.microsoft.com/en-us/windows/win32/dlls/dynamic-link-library-search-order

제법 복잡한 규칙으로 찾게 됩니다. 자세한 것은 저 문서를 읽어보시고 여기서는 ^^ 저 옵션을 간단한 예제로 테스트해 보겠습니다.




Visual Studio로 C/C++ Console Application과 Dynamic-Link Library 유형의 프로젝트를 각각 생성한 다음, DLL 프로젝트에는 다음과 같이 테스트용 함수를 하나 추가하고,

// Dll1.h

#pragma once

#ifdef DLL1LIB_EXPORTS
#define DLL1LIBRARY_API __declspec(dllexport)
#else
#define DLL1LIBRARY_API __declspec(dllimport)
#endif

extern "C" DLL1LIBRARY_API void test_func();

// pch.cpp

#include "pch.h"
#include "Dll1.h"
#include <stdio.h>

void DLL1LIBRARY_API test_func()
{
    printf("test_func");
}

Console Application에서는 위의 함수를 사용하는 코드를 다음과 같이 넣어둡니다.

#include <iostream>

#include "..\\Dll1\\Dll1.h"
#pragma comment(lib, "..\\x64\\debug\\Dll1.lib")

int main()
{
    std::cout << "Hello World!\n";

    test_func();
}

빌드 하면 ./x64/Debug/ 디렉터리에 각각 다음과 같은 파일들이 생성되고,

ConsoleApplication1.exe
Dll1.dll
...[필요 없는 파일 생략]...

당연히 저 디렉터리에서 실행하면 화면에는 Hello World와 test_func가 출력됩니다. 자, 그럼 여기서 Dll1.dll 파일을 그 부모 디렉터리로 이동한 다음, 그 x64 디렉터리에서 ConsoleApplication1.exe를 실행해 보면 어떻게 될까요?

C:\ConsoleApplication1\x64> dir /b /s
C:\ConsoleApplication1\x64\Dll1.dll
C:\ConsoleApplication1\x64\Debug\ConsoleApplication1.exe
...[필요 없는 파일 생략]...

C:\ConsoleApplication1\x64> .\Debug\ConsoleApplication1.exe

ConsoleApplication1.exe가 위치한 디렉터리에 Dll1.dll 파일이 없는데도 잘 실행이 될 것입니다. 왜냐하면, "Dynamic-link library search order" 문서에 나오듯이 "Current Directory"에서도, 즉 해당 응용 프로그램을 실행한 경로에서도 dll을 찾기 때문입니다.

이제 약간씩 변형을 줘볼까요? ^^ 우선, DEPENDENTLOADFLAG 옵션으로 0xa00 값을 줄 텐데요,

// Linker / Command Line의 "Additional Options"에 명시

/DEPENDENTLOADFLAG:0xa00

문서에 0xa00은 다음의 2개 플래그를 OR 연산한 것입니다.

LOAD_LIBRARY_SEARCH_APPLICATION_DIR 0x00000200
LOAD_LIBRARY_SEARCH_SYSTEM32 0x00000800

그러니까, DLL을 c:\windows\system32 디렉터리와 exe 파일이 있는 디렉터리에서만 찾으라는 의미입니다. 이렇게 빌드하고 다시 위와 같은 상황으로 실행하면 이번에는 다음과 같은 오류가 발생합니다.

ConsoleApplication1.exe - System Error
The code execution cannot proceed because Dll1.dll was not found. Reinstalling the program may fix this problem. 

또한 이벤트 로그에는 다음과 같은 "정보" 항목이 남습니다.

Log Name:      System
Source:        Application Popup
...[생략]...
Event ID:      26
Task Category: None
Level:         Information
User:          SYSTEM
Description:
Application popup: ConsoleApplication1.exe - System Error : The code execution cannot proceed because Dll1.dll was not found. Reinstalling the program may fix this problem. 

왜냐하면, Dll1.dll 파일이 exe와 같은 디렉터리이거나, system32에만 있어야 하기 때문입니다. 대충 느낌이 오시죠? ^^ 그렇다면, 다음의 옵션(LOAD_LIBRARY_SEARCH_SYSTEM32)으로 주고 빌드하면 어떻게 될까요?

/DEPENDENTLOADFLAG:0x800

그럼 dll 파일을 오직 system32 디렉터리에서만 찾기 때문에 exe와 dll 파일이 같은 디렉터리에 있어도 실행되지 않습니다. 따라서 위의 옵션을 주었다면 의존하는 dll을 모두 system32로 복사해야만 합니다.




해보는 김에, 혹시 LOAD_LIBRARY_SEARCH_APPLICATION_DIR만 주면 어떻게 될까요?

/DEPENDENTLOADFLAG:0x200

현재 예제에서는 Visual C++ Debug 빌드를 했기 때문에 다음의 DLL들에 대한 의존성이 있습니다.

msvcp140d.dll
msvcp140_1d.dll
ucrtbased.dll
vcruntime140d.dll
vcruntime140_1d.dll

Visual Studio를 설치한 경우 위의 DLL들은 모두 c:\windows\system32 디렉터리에 있기 때문에 LOAD_LIBRARY_SEARCH_APPLICATION_DIR 옵션만으로 빌드하게 되면 위의 DLL들을 찾을 수 없어 System Error가 발생합니다. 따라서 해당 DLL들을 exe와 같은 디렉터리에 복사해,

C:\ConsoleApplication1\x64\Debug> dir /b
ConsoleApplication1.exe
Dll1.dll
msvcp140d.dll
msvcp140_1d.dll
ucrtbased.dll
vcruntime140d.dll
vcruntime140_1d.dll
...[필요 없는 파일 생략]...

실행하면 정상적으로 구동됩니다. 여기서 재미있는 것은, windows의 시스템 DLL(예를 들어, combase.dll, gdi32.dll, imm32.dll 등)은 이전처럼 system32 디렉터리에서 로드한다는 것입니다. 다시 말해 LOAD_LIBRARY_SEARCH_APPLICATION_DIR 옵션을 줘도 시스템 DLL들은 예외인 것입니다. (사실, 편의상 예외로 할 수밖에 없었을 것입니다. ^^)




비록 이 옵션의 지원은 Windows 10 Version 1607부터지만 개발자에게 있어 지금도 충분한 의미가 있습니다. 위의 설명을 이해하셨다면 눈치채셨을 텐데요, ^^ 보통, 개발된 응용 프로그램을 다른 PC에서 실행할 때 의존성 문제로 고생하는 경우가 있습니다. 바로 그럴 때, 저 옵션을 주고 실행해 보면 되는 것입니다. 최종적으로 실행이 잘 되면, 그 상태의 출력 디렉터리를 xcopy로 복사해 배포하면 끝입니다.

마지막으로 유의해야 할 점은, 저 옵션은 정적 링크된 DLL에 대해서만 통용된다는 점입니다. 코드상에서 LoadLibrary로 DLL 로딩을 시도하면, 그에 대해서는 "Dynamic-link library search order" 문서의 규칙을 적용받습니다.




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

[연관 글]






[최초 등록일: ]
[최종 수정일: 3/29/2023]

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

비밀번호

댓글 작성자
 



2025-03-15 08시09분
Making sure that a DLL loads only from your application directory
; https://devblogs.microsoft.com/oldnewthing/20250313-00/?p=110963
정성태

1  [2]  3  4  5  6  7  8  9  10  11  12  13  14  15  ...
NoWriterDateCnt.TitleFile(s)
13893정성태2/27/20252225Linux: 115. eBPF (bpf2go) - ARRAY / HASH map 기본 사용법
13892정성태2/24/20252981닷넷: 2325. C# - PowerShell과 연동하는 방법파일 다운로드1
13891정성태2/23/20252500닷넷: 2324. C# - 프로세스의 성능 카운터용 인스턴스 이름을 구하는 방법파일 다운로드1
13890정성태2/21/20252320닷넷: 2323. C# - 프로세스 메모리 중 Private Working Set 크기를 구하는 방법(Win32 API)파일 다운로드1
13889정성태2/20/20253050닷넷: 2322. C# - 프로세스 메모리 중 Private Working Set 크기를 구하는 방법(성능 카운터, WMI) [1]파일 다운로드1
13888정성태2/17/20252498닷넷: 2321. Blazor에서 발생할 수 있는 async void 메서드의 부작용
13887정성태2/17/20253070닷넷: 2320. Blazor의 razor 페이지에서 code-behind 파일로 코드를 분리 및 DI 사용법
13886정성태2/15/20252572VS.NET IDE: 196. Visual Studio - Code-behind처럼 cs 파일을 그룹핑하는 방법
13885정성태2/14/20253234닷넷: 2319. ASP.NET Core Web API / Razor 페이지에서 발생할 수 있는 async void 메서드의 부작용
13884정성태2/13/20253521닷넷: 2318. C# - (async Task가 아닌) async void 사용 시의 부작용파일 다운로드1
13883정성태2/12/20253260닷넷: 2317. C# - Memory Mapped I/O를 이용한 PCI Configuration Space 정보 열람파일 다운로드1
13882정성태2/10/20252577스크립트: 70. 파이썬 - oracledb 패키지 연동 시 Thin / Thick 모드
13881정성태2/7/20252832닷넷: 2316. C# - Port I/O를 이용한 PCI Configuration Space 정보 열람파일 다운로드1
13880정성태2/5/20253167오류 유형: 947. sshd - Failed to start OpenSSH server daemon.
13879정성태2/5/20253406오류 유형: 946. Ubuntu - N: Updating from such a repository can't be done securely, and is therefore disabled by default.
13878정성태2/3/20253197오류 유형: 945. Windows - 최대 절전 모드 시 DRIVER_POWER_STATE_FAILURE 발생 (pacer.sys)
13877정성태1/25/20253249닷넷: 2315. C# - PCI 장치 열거 (레지스트리, SetupAPI)파일 다운로드1
13876정성태1/25/20253706닷넷: 2314. C# - ProcessStartInfo 타입의 Arguments와 ArgumentList파일 다운로드1
13875정성태1/24/20253133스크립트: 69. 파이썬 - multiprocessing 패키지의 spawn 모드로 동작하는 uvicorn의 workers
13874정성태1/24/20253555스크립트: 68. 파이썬 - multiprocessing Pool의 기본 프로세스 시작 모드(spawn, fork)
13873정성태1/23/20252983디버깅 기술: 217. WinDbg - PCI 장치 열거파일 다운로드1
13872정성태1/23/20252883오류 유형: 944. WinDbg - 원격 커널 디버깅이 연결은 되지만 Break (Ctrl + Break) 키를 눌러도 멈추지 않는 현상
13871정성태1/22/20253292Windows: 278. Windows - 윈도우를 다른 모니터 화면으로 이동시키는 단축키 (Window + Shift + 화살표)
13870정성태1/18/20253731개발 환경 구성: 741. WinDbg - 네트워크 커널 디버깅이 가능한 NIC 카드 지원 확대
13869정성태1/18/20253456개발 환경 구성: 740. WinDbg - _NT_SYMBOL_PATH 환경 변수에 설정한 경로로 심벌 파일을 다운로드하지 않는 경우
13868정성태1/17/20253109Windows: 277. Hyper-V - Windows 11 VM의 Enhanced Session 모드로 로그인을 할 수 없는 문제
1  [2]  3  4  5  6  7  8  9  10  11  12  13  14  15  ...