본문 바로가기

Linux

Windows 환경에서 Linux 환경으로 Library의 C++ 이식

때로 소프트웨어 프로그램은 프로그램을 작성하거나 개발한 시스템과는 완전히 동떨어진 시스템에서 동작하도록 만들어집니다. 시스템에 맞춰 소프트웨어를 수정하는 과정을 이식이라 부릅니다. 여러 가지 이유 때문에 소프트웨어를 이식할 필요가 생깁니다. 최종 사용자들이 다양한 유닉스(UNIX®) 버전 같은 새로운 환경에서 소프트웨어를 사용하기를 원하거나 조직에서 사용하는 플랫폼에 맞춰 최적화하는 과정에서 개발자가 손수 만든 코드를 해당 소프트웨어에 통합할지도 모릅니다.
윈도우에서 유닉스 환경으로 이식하기

대다수 마이크로소프트 윈도우(Microsoft® Windows®) 기반 제품은 마이크로소프트 비주얼 스튜디오(Microsoft Visual Studio®)를 사용해 만들어진다. 비주얼 스튜디오는 복잡한 통합 개발 환경(IDE)을 제공해 개발자를 위한 전체 빌드 과정을 거의 대부분 자동화해 준다. 한 술 더 떠, 윈도우 개발자들은 윈도우 플랫폼에 밀접한 응용 프로그램 인터페이스(API), 헤더, 언어 확장을 사용한다. SunOS, OpenBSD, IRX와 같은 대다수 유닉스(UNIX®) 시스템은 IDE 또는 윈도우에 밀접한 헤더나 확장을 제공하지 않으므로 종종 이식은 시간 소모적인 작업으로 변한다. 설상가상으로, 기존에 사용하던 윈도우 기반 코드는 16비트나 32비트 x86 하드웨어에서 동작한다. 유닉스 기반 환경은 종종 64비트이며 대다수 유닉스 회사는 x86 명령어 집합을 지원하지 않는다. 2회 연재 중 첫 번째인 이 기사에서는 윈도우 운영체제에 기반을 둔 전형적인 비주얼 C++ 프로젝트를 SunOS에 기반한 g++ 환경으로 이식하는 베일을 벗기는데, 앞서 말한 몇 가지 쟁점에 대해서는 세부 내용까지 설명하겠다.

비주얼 스튜디오에서 지원하는 C/C++ 프로젝트 유형

비주얼 C++를 사용하면 (단일 스레드나 다중 스레드를 지원하는) 세 가지 프로젝트 종류 중 하나를 생성할 수 있다.

동적 링크 라이브러리(DLL 또는 .dll)
정적 라이브러리(LIB 또는 .lib)
실행 파일(.exe)
좀 더 복잡한 프로젝트 종류가 필요하다면, 비주얼 스튜디오 .NET을 사용한다. 이 프로그램은 다중 프로젝트를 생성해 관리하도록 지원한다. 다음 이어지는 절에서는 동적 라이브러리와 정적 라이브러리 프로젝트를 윈도우 환경에서 유닉스 환경으로 이식하는 데 초점을 맞춘다.

DLL을 유닉스 환경으로 이식하기

윈도우에서 .dll 파일은 유닉스에서 .so(shared object) 파일이다. 하지만 .so 파일 생성 과정은 .dll 파일 생성 과정과 조금 다르다. Listing 1에 나온 예제는 main.cpp 파일에 있는 main에서 호출하는 간단한 함수인 printHello를 정의한다.

Listing 1. printHello 루틴 선언을 포함하는 파일인 hello.h

#ifdef BUILDING_DLL
#define PRINT_API __declspec(dllexport)
#else
#define PRINT_API __declspec(dllimport)
#endif

extern "C" PRINT_API void printHello();

Listing 2는 hello.cpp 원시 코드다.

Listing 2. hello.cpp

#include
#include "hello.h"

void printHello
{
std::cout << "hello Windows/UNIX users\n";
}

extern "C" PRINT_API void printHello();

x86 플랫폼을 위한 마이크로소프트 32비트 C/C++ 표준 컴파일러(cl.exe)를 사용한다면 hello.dll 파일을 만들기 위해 다음과 같은 명령을 내린다.

cl /LD  hello.cpp /DBUILDING_DLL

/LD 옵션은 .dll 파일을 생성하라고 지시한다(.exe나 .obj와 같은 다른 형식을 생성하도록 옵션을 바꿀 수 있다). /DBUILDING_DLL은 이 DLL에서 printHello 심볼을 공개하도록 빌드 과정에서 PRINT_API 매크로를 정의한다.

Listing 3은 main.cpp 원시 파일을 담고 있는데, 여기서 printHello 루틴을 호출한다. 이 글에서는 hello.h, hello.cpp, main.cpp가 모두 같은 폴더에 있다고 가정한다.

Listing 3. printHello 루틴을 호출하는 main 원시 코드

#include "hello.h"

int main ( )
{
printHello();
return 0;
}

main 코드를 컴파일해 링크하려면 다음과 같은 명령을 내린다.

cl main.cpp hello.lib

원시 코드와 생성된 결과물을 슬쩍 살펴보면 두 가지 중요한 사실이 드러난다. 첫째로 윈도우에 밀접한 구문인 __declspec(dllexport)가 함수, 변수, 클래스를 DLL에서 공개할 때 필요하다. 비슷하게, 윈도우에 밀접한 구문인 __declspec(dllimport)는 DLL에서 함수, 변수, 클래스를 불러쓸 때 필요하다. 둘째로 컴파일 결과로 printHello.dll과 printHello.lib라는 파일 두 개가 생긴다. printHello.lib는 main에서 링크할 때 사용하며, 공유 목적 파일을 위한 유닉스 헤더는 declspec 구문을 요구하지 않는다. 컴파일을 성공적으로 마치고 나면 .so 파일 하나만 남아 main과 링크할 때 사용한다.

g++를 사용해 유닉스 플랫폼에서 공유 라이브러리를 만들기 위해, fPIC 플래그를 g++에 붙이는 방법으로 모든 원시 파일을 재배치 가능한 공유 목적 파일로 컴파일한다. PIC는 위치 독립 코드(Position Independent Code)의 약어다. 공유 라이브러리는 메모리에 올라올 때마다 새로운 메모리 주소에 사상될 가능성이 있다. 따라서 라이브러리 내부에 존재하는 모든 변수와 함수 주소를 계산하기 쉬운 방식으로 라이브러리를 메모리에 올리는 시작 주소에 상대적으로 배치해야 한다. -fPIC 옵션으로 생성하면 재배치 가능한 코드가 나온다. -o 옵션은 출력 파일 이름을 지정하며, -shared 옵션은 미정의 심볼 참조를 허용하도록 공유 라이브러리를 만든다. hello.so 파일을 생성하려면, Listing 4와 같이 헤더 파일을 변경하자.

Listing 4. 유닉스에 밀접하도록 변경한 수정된 hello.h 헤더

#if defined (__GNUC__) && defined(__unix__)
#define PRINT_API __attribute__ ((__visibility__("default")))
#elif defined (WIN32)
#ifdef BUILDING_DLL
#define PRINT_API __declspec(dllexport)
#else
#define PRINT_API __declspec(dllimport)
#endif

extern "C" PRINT_API void printHello();

그리고 hello.so 공유 라이브러리를 링크하기 위한 g++ 명령은 다음과 같다.

g++ -fPIC -shared hello.cpp -o hello.so

main 실행 파일을 생성하려면 다음과 같이 컴파일한다.

g++ -o main main.cpp hello.so

g++에서 심볼 감추기

윈도우 기반 DLL에서 심볼을 공개하는 방법에는 두 가지가 있다. 첫 번째 방법으로 DLL에서 공개할 필요가 있는 선택된 항목(예를 들어 클래스, 전역 변수, 전역 함수)에만 적용되는 __declspec(dllexport)를 활용한다. 두 번째 방법으로 모듈 정의(.def) 파일을 활용한다. .def 파일은 독자적인 구문을 제공하며 DLL에서 공개할 필요가 있는 심볼을 포함한다.

g++ 링커의 기본 행동 양식은 .so 파일에서 모든 심볼을 공개한다. 이런 행동 양식이 바람직하지 않을지도 모르며, 다중 DLL 링크를 시간 소모적인 작업으로 만들어버린다. 공유 라이브러리에서 선택적으로 심볼을 공개하려면 g++에서 제공하는 속성 메커니즘을 활용한다. 예를 들어, 'void print1();'과 'int print2(char*);' 메서드 두 개가 있을 때 print2만 공개하고 싶은 경우가 있다. Listing 5는 윈도우와 유닉스에서 이렇게 print2만 공개하는 방법을 보여준다.

Listing 5. g++에서 심볼 감추기

#ifdef _MSC_VER // Visual Studio specific macro
#ifdef BUILDING_DLL
#define DLLEXPORT __declspec(dllexport)
#else
#define DLLEXPORT __declspec(dllimport)
#endif
#define DLLLOCAL
#else
#define DLLEXPORT __attribute__ ((visibility("default")))
#define DLLLOCAL   __attribute__ ((visibility("hidden")))
#endif

extern "C" DLLLOCAL void print1();         // print1 hidden
extern "C" DLLEXPORT int print2(char*); // print2 exported

__attribute__ ((visibility("hidden")))을 사용하면 DLL에서 심볼 공개를 방지한다. (4.0.0 이상) 최신 g++ 버전은 또한 -fvisibility 스위치를 제공하는데, 공유 라이브러리에서선택적으로 심볼을 공개하도록 허용한다. g++ 명령을 내릴 때 -fvisibility=hidden을 붙이면 공유 라이브러리에서 __attribute__ ((visibility("default")))로 선언된 심볼을 제외한 모든 심볼 공개를 중단한다. 이게 바로 명시적으로 visibility로 표시하지 않은 선언 전부를 숨김 속성으로 취급하도록 g++에 요청하는 깔끔한 방식이다. dlsym을 사용해 숨겨진 심볼을 추출할 경우 NULL이 반환된다.

[출처] http://www.test104.com/kr/tech/2838.html

'Linux' 카테고리의 다른 글

공유라이브러리-2  (0) 2008.09.05
공유 라이브러리-1  (0) 2008.09.05
library 만들기  (0) 2008.09.05
extern "C" gcc, g++ 라이브러리 컴파일  (0) 2008.09.05
리눅스 gcc 컴파일러(c/c++)와 make 강좌  (0) 2008.09.04