Windows: DLL에서 함수/심볼 Export하기
Windows의 DLL에서는 내보낼 함수에 대해 명시적으로 export 지시를 해주어야 합니다. 기본적으로 DLL에 정의된 함수들은 외부에 자동 공개되지 않으며, 외부에 제공하려는 함수들은 export 대상임을 표시해야 합니다. 이를 위해 Microsoft 컴파일러에서는 __declspec(dllexport) 키워드를 사용합니다. 반대로, DLL에 들어있는 함수를 외부 애플리케이션에서 사용할 때는 __declspec(dllimport)로 가져오는(import) 함수임을 표시합니다. 예를 들어, DLL을 만들 때 헤더에서 함수를 다음과 같이 선언합니다:
// mylib.h (DLL을 빌드할 때)
__declspec(dllexport) int add(int a, int b);
위와 같이 선언하고 DLL을 빌드하면 add 함수가 DLL의 외부 Export 심볼 테이블에 등록되어 다른 프로그램에서 접근할 수 있게 됩니다. DLL을 사용하는 애플리케이션 쪽에서는 해당 함수를 선언할 때 import 지시어를 붙여주는 것이 일반적입니다:
// 사용 측 애플리케이션 코드
__declspec(dllimport) int add(int a, int b); // DLL에서 가져올 함수 선언
...
int result = add(2, 3); // DLL의 함수를 호출
dllimport로 표시하면 컴파일러가 DLL의 외부 함수임을 인지하여 적절한 호출 코드를 생성합니다. 참고로, 함수 사용 측에서 dllimport를 생략해도 동작은 가능하지만, 명시하는 것이 올바른 사용법이며 컴파일러 최적화에 유리합니다.
헤더 파일 구성: DLL을 만들 때와 사용할 때 서로 다른 선언(dllexport vs dllimport)이 필요하지만, 이를 위해 헤더 파일을 두 벌로 나누지 않고 보통 매크로를 이용해 하나의 헤더로 관리합니다. 예를 들어 DLL을 빌드할 때 특정 매크로(MYLIB_EXPORTS 등)를 정의해 두면, 헤더에서 해당 매크로가 정의된 경우 dllexport를, 아닌 경우 dllimport를 붙이도록 구현합니다. 코드 예시는 다음과 같습니다:
// mylib_export.h - Windows DLL용 매크로 정의
#ifdef MYLIB_EXPORTS // DLL 빌드 시에는 이 매크로를 정의
#define MYLIB_API __declspec(dllexport)
#else // DLL을 사용하는 경우
#define MYLIB_API __declspec(dllimport)
#endif
// mylib.h - 함수 선언부
#include "mylib_export.h"
MYLIB_API int add(int a, int b);
위와 같이 하면 DLL을 빌드할 때는 MYLIB_EXPORTS를 정의하여 컴파일하고, DLL 사용자 측에서는 해당 매크로를 정의하지 않으면 자동으로 dllimport가 적용된 선언을 사용하게 됩니다. 이러한 방식으로 하나의 헤더 파일로 DLL 내보내기/가져오기를 관리할 수 있습니다.
컴파일 및 링크 옵션 (Windows):
- 컴파일 시 정의: DLL 빌드 시 MYLIB_EXPORTS와 같은 매크로를 정의(/D MYLIB_EXPORTS 또는 -DMYLIB_EXPORTS)해야 합니다. Visual C++의 DLL 프로젝트를 생성하면 보통 프로젝트 이름에 _EXPORTS 매크로가 자동 정의되어 위와 같은 조건을 만족하도록 해줍니다.
- DLL 생성 플래그: MSVC에서는 /LD 옵션을 사용하여 DLL을 생성합니다. 예를 들어 명령행에서 컴파일하면:위 명령은 mylib.dll DLL을 생성하고, 함께 mylib.lib 라는 import 라이브러리도 생성합니다. .lib import 라이브러리는 DLL의 함수들을 링커가 연결할 수 있도록 하는 정보가 담긴 파일로, 응용 프로그램을 빌드할 때 필요합니다. 런타임에는 .dll 파일만 있으면 되지만, 컴파일 단계에서 링커는 .lib를 통해 외부 함수 참조를 해결합니다
- cl /LD /D MYLIB_EXPORTS mylib.c /Fe:mylib.dll
- MinGW(gcc) 사용 시: GCC 컴파일러를 사용해 Windows DLL을 만드는 경우에도 소스 코드에서는 동일하게 __declspec(dllexport)를 사용하며, -shared 옵션으로 DLL을 생성합니다. 예를 들어:(GCC로 DLL을 만들 때도 dlltool 등을 사용하여 import 라이브러리(.a)를 생성할 수 있습니다. 하지만 보통 MSVC 환경에서 .lib를 이용해 링크하거나, 필요 시 GCC의 -Wl,--out-implib 옵션으로 import 라이브러리를 얻을 수도 있습니다.)
- gcc -shared -o mylib.dll mylib.c -DMYLIB_EXPORTS
Linux: 공유 객체(.so)에서 함수/심볼 Export하기
Linux의 공유 라이브러리(.so)는 Windows와 달리 별도의 export 지시어 없이도 전역 심볼은 기본적으로 외부에 공개됩니다. GCC를 사용해 -shared 옵션으로 컴파일하면, static이 아닌 전역 함수들은 자동으로 .so의 심볼 테이블에 노출되어 다른 프로그램이 사용할 수 있습니다. 예를 들어 간단한 C 소스를 공유 라이브러리로 빌드하면, 특별한 지시어 없이도 그 함수들은 외부에서 extern 선언으로 사용 가능합니다.
gcc -fPIC -shared -o libmylib.so mylib.c # libmylib.so 생성 (기본적으로 모든 전역함수 export)
위 명령처럼 -fPIC (Position Independent Code) 옵션을 주어 오브젝트 파일을 생성하고, -shared 옵션으로 .so 파일을 링크합니다. 이렇게 하면 mylib.c에 정의된 전역 함수들이 libmylib.so의 공개 API로서 노출됩니다.
심볼 가시성 제어 (선택 사항): 기본 동작은 모든 전역 심볼 공개이지만, 원하지 않는 내부 함수까지 노출되지 않도록 제어하는 것이 권장됩니다. GCC에서는 -fvisibility 옵션으로 기본 심볼 가시성을 조절할 수 있습니다. 기본값 -fvisibility=default에서는 모든 심볼이 공개되지만, 이를 **-fvisibility=hidden**으로 변경하면 기본적으로 모든 심볼을 숨기고, 특정 함수에 한해 __attribute__((visibility("default"))) 속성을 부여하여 선택적으로 공개할 수 있습니다.
즉, 라이브러리를 컴파일할 때 -fvisibility=hidden 플래그를 주고, 헤더 파일에서 외부 API로 공개할 함수 선언에만 아래와 같은 속성(attribute)을 붙입니다:
// mylib.h (Linux에서 가시성 제어 예시)
#ifdef __GNUC__
#define MYLIB_API __attribute__((visibility("default")))
#else
#define MYLIB_API
#endif
MYLIB_API int add(int a, int b); // 외부에 공개할 함수
이렇게 하면 add 함수만 공유 라이브러리의 외부 심볼로 노출되고, 그 외의 전역 함수들은 숨겨지게 됩니다. 이 기법을 사용하면 필요한 API만 노출할 수 있을 뿐 아니라, 바이너리 크기 감소, 링크 충돌 회피, 로드 시간 단축 등의 이점이 있습니다. 숨겨진 심볼은 외부에서 참조할 수 없으므로, 다른 라이브러리와의 이름 충돌을 방지하고 불필요한 심볼로 인한 오버헤드를 줄일 수 있습니다
헤더 파일 매크로 처리: 크로스 플랫폼을 고려하여, Windows와 Linux에서 모두 사용할 수 있는 매크로를 정의할 수 있습니다. 예를 들어 하나의 MYLIB_API 매크로를 만들어 Windows에서는 __declspec(dllexport/dllimport)을, Linux에서는 __attribute__((visibility("default")))로 매핑하면 동일한 헤더 코드로 양쪽을 처리할 수 있습니다. Windows에서는 빌드 시 export 매크로를 활성화하고, Linux에서는 컴파일러 특성만 부여하는 형태입니다.
크로스플랫폼 예제 코드 및 빌드
아래에는 Windows와 Linux에서 모두 사용 가능한 형태의 공유 라이브러리 예제를 제공합니다. 간단한 add 함수를 라이브러리 API로 공개하는 코드이며, 매크로로 플랫폼별 export 규칙을 처리합니다:
/* mylib.h - 공용 헤더 (Windows & Linux) */
#if defined(_WIN32) || defined(_WIN64)
#ifdef MYLIB_EXPORTS
#define MYLIB_API __declspec(dllexport)
#else
#define MYLIB_API __declspec(dllimport)
#endif
#elif defined(__GNUC__) && __GNUC__ >= 4
#define MYLIB_API __attribute__((visibility("default")))
#else
#define MYLIB_API
#endif
// 외부에 공개할 함수 선언
MYLIB_API int add(int a, int b);
#ifdef __cplusplus
extern "C" {
#endif
// ... (다른 공개 함수들 선언 가능)
#ifdef __cplusplus
}
#endif
/* mylib.c - 구현 파일 */
#include "mylib.h"
MYLIB_API int add(int a, int b) {
return a + b;
}
// (내부 전용 함수는 static으로 정의하거나, Linux의 경우 visibility("hidden") 등을 활용)
- MYLIB_EXPORTS 매크로는 라이브러리를 빌드할 때 정의합니다. 이렇게 하면 Windows에서는 MYLIB_API가 __declspec(dllexport)로 설정되어 함수 add가 DLL의 export 심볼로 추가되고, Linux에서는 MYLIB_API가 visibility("default")로 설정되어 (혹은 -fvisibility=hidden 빌드 시) 해당 함수가 공개 심볼이 됩니다. 라이브러리를 사용하는 코드에서는 MYLIB_EXPORTS를 정의하지 않고 헤더를 포함하므로 Windows의 경우 dllimport로 선언되며, Linux의 경우 일반 선언과 다르지 않게 동작합니다.
빌드 방법:
- Windows (MSVC): 위 코드로 DLL을 만들려면, 컴파일 단계에서 MYLIB_EXPORTS를 정의하고 /LD 옵션으로 DLL을 생성합니다. 예를 들어 명령어:이 명령을 실행하면 mylib.dll과 import 라이브러리 mylib.lib가 생성됩니다. 생성된 DLL을 다른 프로젝트에서 사용할 때는 mylib.h를 포함하고 mylib.lib를 링크하면, add 함수를 호출할 수 있습니다.
- cl /LD /D MYLIB_EXPORTS mylib.c /Fe:mylib.dll
- Linux (GCC): 공유 객체(.so)를 만들 때는 포지션 독립 코드를 위해 -fPIC 옵션을 주고, 공유 라이브러리로 빌드하기 위해 -shared 옵션을 사용합니다. 예를 들어:이렇게 하면 libmylib.so가 생성되며, 기본 설정에서는 add 함수 등 전역 함수들이 모두 공개 심볼로 포함됩니다. 만약 내부 함수는 숨기고 add만 export하려면 컴파일시에 -fvisibility=hidden 플래그를 추가하고, 위 헤더에서 정의한 대로 MYLIB_API 속성을 붙인 함수만 공개됩니다. 생성된 libmylib.so는 표준 라이브러리 디렉토리에 두거나, 컴파일 시에 -L 옵션으로 경로를 지정하고 -lmylib로 링크하여 사용할 수 있습니다.
- gcc -fPIC -shared -o libmylib.so mylib.c