4. 동적 적재(DL) 라이브러리

동적 적재 라이브러리는 프로그램이 시작할때가 아닌 다른 시기에 적재되는 라이브러리이다. 이것들은 플러그인이나 모듈을 구현할때 적합하다. 왜냐하면 그것들이 필요해질때까지 적재를 기다릴 수 있기 때문이다. 예를들어, PAM(Pluggable Authentication Modules)시스템은 관리자가 인증을 관리하는 것을 허용하기 위해 DL라이브러리를 사용한다. 이것들은 또한 때때로 코드를 머신 코드로 바꾸고 효율을 위해 멈추지 않고 컴파일된 코드로 만드는 인터프리터를 구현하는데 유용하다. 이것은 실시간의(just-in-time) 컴파일러나 MUD(multi-user dungeon)을 구현하는데에 유용하다.

리눅스에서, DL 라이브러리는 그들의 포멧의 관점에 있어서 특별하지 않다; 그들은 표준의 오브젝트 파일로 만들어지거나, 위에서 언급된 표준의 공유 라이브러리 파일로 만들어진다. 주요 차이점은 프로그램의 링크시나 시작시에 적재되지 않는다는 것이다; 대신, 라이브러리를 열거나, 심볼을 찾거나, 에러를 조정하거나, 라이브러리를 닫는 특별한 API가 있다. C 사용자들은 이런 API를 사용하기위해 dlfcn.h라는 헤더파일을 포함해야 한다.

리눅스에서 사용되는 인터페이스는 솔라리스에서 사용되는 인터페이스와 같다. 이것을 나는 ``dlopen()'' API라고 불를 것이다. 그러나, 이 같은 인터페이스가 모든 플랫폼에서 지원되는 것은 아니다; HP-UX는 shl_load()방법을 사용하고, 윈도우즈는 완전히 다른 인터페이스의 DLLs을 사용한다. 만약 당신이 폭넓은 포팅성이 목표라면, 당신은 이런 다른 플랫폼을 숨기는 라이브러리를 생각해야 할것이다. 하나의 방법은 모듈의 동적 적재를 지원하는 glib 라이브러리이다; 그것은 이 함수들의 포팅가능한 인터페이스를 구현하기위해 플랫폼의 동적 적재 루틴을 사용한다. http://developer.gnome.org/doc/API/glib/glib-dynamic-loading-of-modules.html의 링크에서 glib에 대해 많은 것을 찾아볼 수 있다. glib 인터페이스가 이 문서에 잘 설명되있기 때문에 더 언급하지 않겠다. 다른 방법은 GNU libtool의 일부인 libltdl을 사용하는 것이다. 당신이 이것보다 더 기능적인것을 원한다면, CORBA ORB(Object Request Broker)를 찾아보아라. 당신이 리눅스와 솔라리스에서 직접 지원되는 것을 아직도 원하고 있다면, 계속 읽어나가라.

4.1. dlopen()

dlopen(3)함수는 라이브러리를 열고 그것이 사용되도록 준비시켜준다. C에서 프로토타입은 다음과 같다:
  void * dlopen(const char *filename, int flag);
파일이름이 ``/''(즉, 절대경로)로 시작한다면 dlopen()은 즉시 사용하려 한다(라이브러리를 찾으려 하지 않는다). 그렇지 않으면, dlopen()은 다음과 같은 순서로 라이브러리를 찾는다:

  1. 사용자의 LD_LIBRARY_PATH의 환경변수의 콜론으로 구분지어진 디렉토리

  2. (/etc/ld.so.conf에서 파생된) /etc/ld.so.cache에 명시되어있는 라이브러리의 목록

  3. /lib, /usr/lib. 순서를 주의하라; 이것은 예전의 a.out로더가 사용한 순서의 역이다. 예전의 a.out로더는 프로그램을 로드할때 /usr/lib을 찾고 /lib을 찾았다(ld.so(8)의 man페이지를 참고하라). 보통은 어떤 디렉토리나 다른디렉토리 하나에만 라이브러리가 있기 때문에(둘다 없을수도 있다) 문제가 되지 않는다. 그리고 같은이름을 가진 다른 라이브러리는 잠재적인 위험을 가지고 있다.

dlopen()에서 flag의 값은 ``동적 라이브러리가 실행되면서 코드의 정의되지 않은 심볼을 처리하라''의 의미를 가진 RTLD_LAZY이거나, ``dlopen()이 리턴하기 전에 모든 정의되지 않은 심볼을 처리하고, 그것이 되지 않을 경우 실패하라''의 의미를 지닌 RTLD_NOW가 있다. RTLD_GLOBAL은 flag의 값속에 옵션으로 쓸수 있다. 의미는 라이브러리에 적재되는 외부 심볼들은 그 후에 적재되는 라이브러리에 의해서 가능하다는 것이다. 디버깅시에, RTLD_NOW를 쓰기를 원할것이다; RTLD_LAZY는 정의되지 않은 심볼이 있으면 알아볼수 없는 에러를 내기 때문이다. RTLD_NOW를 쓰면서 라이브러리를 여는 것은 약간의 시간이 더 걸린다(하지만 나중에 찾는 속도는 빨라진다); 만약 이것이 유저 인터페이스 문제를 일으킨다면, 당신은 나중에 RTLD_LAZY로 바꿀 수 있다.

라이브러리들이 서로 의존한다면(예를들어 X가 Y에 의존한다면), 당신은 의존당하는 것을 먼저 로드해야한다(이 예제에서 Y를 로드하고 X를 로드해야 한다).

dlopen()의 리턴 값은 다른 DL라이브러리 루틴이 사용하기에 애매모호하게 느껴지는 ``핸들''이다. dlopen()은 로드가 성공하지 못하거나 당신이 체크해야 할 필요가 있다고 생각하면 NULL을 리턴한다. dlopen()에 의해 같은 라이브러리가 한번이상 로드되면 같은 파일 핸들이 리턴된다.

라이브러리가 _init이라 이름지어진 루틴을 사용한다면(export), 그 코드는 dlopen()이 반환하기 전에 실행된다. 당신은 초기화 루틴을 구현하기위해 당신의 라이브러리에서 이 사실을 사용했을것이다. 더 자세한 내용을 위해 5.2절을 보아라.

4.2. dlerror()

에러는 dlerror()를 호출함으로써 보고될수 있다. dlerror()는 dlerror(), dlsym(), dlclose()중 마지막 부른것의 에러의 스트링을 반환한다. 하나의 이상한점은 dlerror()를 부르고 나서 dlerror()를 부르는 것은 그 사이에 다른 에러가 없으면 NULL을 반환한다는 것이다.

4.3. dlsym()

당신이 사용할 수 없다면 DL라이브러리를 로딩하는 의미가 없다. DL라이브러리를 사용하는 중요한 루틴은 dlsym(3)이다. 이것은 열려진 라이브러리의 심볼의 값을 찾아준다. 이 함수는 다음과 같이 정의된다:
 void * dlsym(void *handle, char *symbol);
핸들은 dlopen에 의해 반환된 값이고, 심볼은 NIL로 끝나는 스트링이다. 당신이 할 수 있다면 있다면, dlsym()의 결과를 void* 포인터로 저장하지 마라. 왜냐하면, 당신은 사용할때마다 캐스트해서 사용해야 하기때문이다(그리고 다른사람들이 프로그램을 유지하는데에 더 적은 정보를 줄것이다).

dlsym()은 심볼이 없으면 NULL을 반환할 것이다. 만약 당신의 심볼에 NULL이나 0이 없다는 것을 알면 좋겠지만, 다음과 같은 잠재적인 문제가 있다: 당신이 받은 NULL이 에러인가, 아니면 심볼의 값이 NULL인가? 표준적인 해결법은 dlerror()를 부르고(전에 있던 에러들을 없앤다), dlsym()을 불러서 심볼의 값을 부르고, dlerror()를 불러서 에러가 났는지 체크한다. 단순한 코드는 다음과 같다:
 dlerror(); /* clear error code */
 s = (actual_type) dlsym(handle, symbol_being_searched_for);
 if ((err = dlerror()) != NULL) {
  /* handle error, the symbol wasn't found */
 } else {
  /* symbol found, its value is in s */
 }

4.4. dlclose()

dlopen()의 반대는 dlclose()로서, DL 라이브러리를 닫아준다. DL 라이브러리가 동적 파일 핸들의 링크 수를 관리하기 때문에, 동적 라이브러리는 dlopen이 성공한 만큼 모두 dlclose를 불러주기 전에 다 할당이 없어지지는 않는다. 따라서, 같은 프로그램이 같은 라이브러리를 여러번 불러주는것은 문제가 되지 않는다. 라이브러리가 할당이 없어진다면, 함수의 _fini가 불린다(존재한다면); 더 자세한 정보를 위해 5.2절를 보아라. 주의하라 : dlclose()는 성공하면 0을 리턴하고, 아니면 0이 아닌 값을 리턴한다; 어떤 리눅스 메뉴얼은 이것을 언급하고 있지 않다.

4.5. DL 라이브러리 예제

여기에 있는 예제는 dlopen(3)의 맨페이지에 나오는 예제이다. 이 예제는 math라이브러리를 로드해서, 코사인 2.0을 출력한다. 또한, 각각의 단계에서 에러 체크를 한다(이렇게 하기를 추천한다).

    #include <stdlib.h>
    #include <stdio.h>
    #include <dlfcn.h>

    int main(int argc, char **argv) {
        void *handle;
        double (*cosine)(double);
        char *error;

        handle = dlopen ("/lib/libm.so.6", RTLD_LAZY);
        if (!handle) {
            fputs (dlerror(), stderr);
            exit(1);
        }

        cosine = dlsym(handle, "cos");
        if ((error = dlerror()) != NULL)  {
            fputs(error, stderr);
            exit(1);
        }

        printf ("%f\n", (*cosine)(2.0));
        dlclose(handle);
    }

만약 이 프로그램의 파일이름이 "foo.c"라면, 다음과 같은 명령으로 프로그램을 만들 수 있을 것이다:
    gcc -o foo foo.c -ldl