CMake SHARED

Seongcheol Jeon·2024년 10월 8일
0

CMake

목록 보기
12/17
post-thumbnail

SHARED 란?

SHARED 라이브러리는 동적으로 링크되고 런타임에 로드되는 라이브러리를 의미한다.

SHARED 라이브러리는 import library와 연관되어있다. import library는 구현은 제외한 심볼 정보만 가지고 있는 라이브러리이며, .lib 확장자를 가진다.

SHARED 바이너리 타겟을 만들려면, CMakeLists.txt에 다음과 같은 명령을 입력하면 된다.

add_library(<name> SHARED
	[EXCLUDE_FROM_ALL]
    [<source>...]

<name>은 바이너리 타겟의 이름이며, SHARED는 동적 라이브러리를 만들겠다는 지시어이다. 그리고 [<source>...]는 소스 파일들의 목록이다. 따로 출력 파일의 이름을 지정하지 않는다면, <name>.dll<name>.lib가 만들어진다. libimport library이다.

SHARED 타겟을 만들면, 해당 타겟을 만들 때 자동으로 <name>_EXPORTS라는 전처리기 매크로 (preprocessor macro)를 만들어준다는 것이다. 이것은 추후 __declspec(dllexport/dllimport)를 지정할 때 중요하다.

이렇게 add_library()를 통해 SHARED 타겟을 생성하면, 그것이 산출한 <name>.libexecutable 타겟에 링크해줘야 한다.

target_link_libraries(<target> ... <item>... ...)

소스 트리 구성

test
	foo
    	foo.h
        foo.cpp
    main.cpp
    CMakeLists.txt

동적 라이브러리들은 어떤 심볼들을 익스포트(export)할 지 결정할 필요가 있다. 자체한 사항은 이곳을 살펴보면 좋을 것이다.

// foo.h

#pragma once

#if defined(foo_EXPORTS)
	#define FOO_API __declspec(dllexport)
#else	// NOT defined(foo_EXPORTS)
	#define FOO_API __declspec(dllimport)
#endif

extern "C" FOO_API void foo();
// foo.cpp

#include <iostream>

extern "C" void foo() {
	std::cout << "foo dynamic library!!" << std::endl;
}
// main.cpp

#include <iostream>
#include "foo/foo.h"

int main() {
	std::cout << "main executable!!" << std::endl;

	foo();

	return 0;
}

SHARED 타겟은 자동으로 <name>_EXPORTS라는 전처리기 매크로를 삽입해 준다. 그것을 이용햏서 SHARED 타겟 내에서는 dllexport로, 밖에서는 dllimport로 해석하라고 지정하였다.

extern "C"는 컴파일러에게 익스포트할 때 C++ 네임 맹글링(name magling)을 수행하지 말라고 알려주는 것이다.

C++ 네임 맹글링이란?

C++에는 함수 오버로딩 기능이 있다. 이는 같은 함수명이라고 하더라도 전달받은 타입이 다르면, 서로 다른 함수로 인식되는 기능이다.
int func(int a)라는 함수와 int func(double a)라는 함수를 둘 다 선언하고 정의하더라도 문제가 없게 된다는 것이다. 하지만 이렇게 했을 시, func라는 함수명만으로 해당 함수들을 구별하는 것이 불가능해진다.
따라서 C++ 컴파일러들은 각 컴파일러마다 자신들의 규칙으로 함수 이름을 변경한다. 이것을 네임 맹글링 (Name Mangling)이라고 한다.

test.sln, main.vcxproj, foo.vcxproj 파일들을 만드는 것이 목표이다. foo.vcxproj 파일은 foo.cpp로부터 foo.dllfoo.lib를 만들고, main.vcxprojfoo.libmain.cpp로부터 main.exe를 만든다.
foo.dllmain.exe가 실행될 때 자동으로 로드된다.

CMakeLists.txt 작성

cmake_minimum_required(VERSION 3.8)

project("test")

set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS ON)

add_library(foo SHARED ./foo/foo.cpp)

add_executable(test main.cpp)
target_link_libraries(test PRIVATE foo)

빌드 트리 생성

cmake -S . -B build -G "Visual Studio 17 2022" -A x64

foo_EXPORTS라는 전처리기 매크로가 제대로 들어간 것을 확인할 수 있다.

빌드

cmake --build ./build --config Debug

다음은 build/Debug 디렉토리의 파일 리스트와 main.exe를 실행 한 모습이다.

DLL 임포트 정보 확인

foo.dll 라이브러리가 main.exe에 자동으로 로드되어서 잘 사용중인지 확인해보자.

Visual Studio 개발자 명령 프롬프트를 열어서 main.exe를 다음과 같이 열어보자.

cd <main.exe 디렉토리 경로>
dumpbin /imports main.exe

foo.dll로부터 foo라는 심볼을 임포트했다고 포여주고 있다.

만약, CUI가 불편하다면 Denpendency Walker라는 프로그램을 사용해 GUI로 볼 수 있다.

depends.exe 파일을 실행한 후 main.exe를 드래그&드롭하면, 종속성 정보가 출력된다.

참고로 dependency walker는 어떤 실행 파일이 실행되지 못하고 그냥 종료될 때, 로드되지 않는 dll이 뭐가 있는지 알아내는 용도로 사용한다.


📌 정리

  • add_library()SHARED 인자를 넣으면, SHARED 바이너리 타겟이 생성된다.
  • add_library()SHARED 인자를 지정하면, <name>_EXPORTS 전처리기 매크로가 삽입된다.
  • add_library()SHARED 인자를 지정하면, <name>.lib<name>.dll 파일들이 생성된다.
  • target_link_libraries()를 통해 import library를 다른 타겟에 링크할 수 있다.

0개의 댓글