본문 바로가기

C/C++

C 동적 라이브러리를 사용하는 프로그램 만들기

@markdown

# C 동적 라이브러리를 사용하는 프로그램 만들기


C로 프로그램을 작성하다 보면, **플러그인** 형식으로 런타임에 기능을 추가/제거 할 수 있어야 할 때가 있다.

런타임에 추가/제거 가능한 플러그인은 프로그램을 작성하고, **동적 라이브러리로 컴파일** 하는 것으로 해결할 수 있다.

_

이번 포스트를 통해 플러그인 형식으로 런타임에 기능을 추가/제거 할 수 있는 일종의 프레임워크에 가까운 프로그램을 어떻게 만드는지 소개한다.


## 시연 영상

시연 영상에서는 메인 프로그램을 띄워 놓고, 실시간으로 플러그인을 제작해 메인 프로그램에 붙이고, 사용하고, 떼네는 모습을 촬영했다.

<iframe width="560" height="315" src="https://www.youtube.com/embed/oHijPZh8fWU" frameborder="0" allowfullscreen></iframe>

## main


사용자가 실제로 사용할 프로그램인 `main`에는 다음과 같은 요구사항이 발생했다고 가정한다.


- 런타임에 플러그인을 추가할 수 있어야 함

- 런타임에 플러그인을 제거할 수 있어야 함

- 런타임에 플러그인이 제공하는 기능의 목록을 출력할 수 있어야 함

- 런타임에 플러그인이 제공하는 모든 기능을 사용할 수 있어야 함

- 사용자가 끄기 전까지는 프로그램이 계속 돌아야 함

- 플러그인과 결합하다 문제가 발생해도 프로그램이 계속 동작해야 함


### types.h


types.h에는 메인 프로그램과 플러그인 프로그램이 사용할 공용 자료구조를 선언한다.

런타임에 서로 잘 맞물리기 위해 사용할 자료구조에 대해 일종의 **약속**을 하는 것이다.


```

typedef struct {

    char* name;


    int (* func)(char*);

} Function;

```


### main.c


이번 포스트의 핵심 내용을 거의 다 갖고 있는 모듈이다.

메인은 다음과 같이 동작한다.


1. `getline()`을 사용해 플러그인의 경로를 입력받는다.

2. `dlopen()`을 사용해 1에서 입력받은 플러그인과 결합한다

3. `dlsym()`을 사용해 결합된 플러그인의 함수 목록 배열과 해당 배열의 길이를 불러온다

4. 플러그인의 함수 목록을 출력한다

5. 플러그인이 제공한 모든 함수를 호출하고 리턴값을 출력한다

6. `dlclose()`를 사용해 플러그인을 떼넨 뒤, 관련 자원을 해제한다

7. 플러그인의 경로를 입력받지 않을 때까지 1~6을 반복한다


만약 플러그인 경로를 잘 못 입력받는다던가, 플러그인에 우리가 찾는 심볼(변수나 함수)이 없을 경우 `dlerror()`를 사용해 에러 메시지를 출력한다.


```

#include <stdio.h>

#include <dlfcn.h>

#include <stdlib.h>

#include <memory.h>

#include <stdbool.h>


#include "types.h"


int main() {

    char* error_message;

    bool run = true;


    while(run) {

        char* plugin_path;

        size_t _ = 0;


        printf("Enter plugin path(leave empty to quit): ");

        ssize_t plugin_path_len = getline(&plugin_path, &_, stdin);


        if(plugin_path_len > 1) {

            plugin_path[plugin_path_len - 1] = '\0';


            // Load plugin

            void* plugin = dlopen(plugin_path, RTLD_LAZY);

            error_message = dlerror();

            if(!plugin || error_message) {

                printf("%s\n", error_message);

                continue;

            }


            // Load plugin function

            Function* functions = dlsym(plugin, "functions");

            int* functions_count = dlsym(plugin, "functions_count");

            error_message = dlerror();

            if(error_message) {

                printf("%s\n", error_message);

                continue;

            }


            // List plugin function

            for(int i = 0; i < *functions_count; ++i)

                printf("plugin '%s' has '%s' function\n", plugin_path, functions[i].name);


            // Call plugin function

            for(int i = 0; i < *functions_count; ++i) {

                int nprint = functions[i].func("Plugin says: ");

                printf("Plugin returns: %d\n", nprint);

            }


            // Resource cleanup

            free(plugin_path), plugin_path = NULL;

            dlclose(plugin), plugin = NULL;

        } else {

            run = false;

        }

    }

    return EXIT_SUCCESS;

}

```


## 플러그인


메인 프로그램과 결합하는 `plugin-hello`와 `plugin-world` 플러그인을 작성하자.

_

메인 프로그램과 결합하기 위해, 플러그인에서는 다음과 같은 약속을 하자.

약속의 세부 내용은 구현하는 사람 마음이나, **약속**자체는 어떤 식으로든지 해두어야 한다.


- 함수이름과 함수포인터는 type.h에 나온 `Function` 구조체를 사용해 기술한다

- 함수 배열은 `functions`라는 변수에 저장한다

- 함수 배열의 길이는 `functions_count`라는 변수에 저장한다


함수의 이름이라던가, `.name`의 값이라던가는 맘대로 해도 상관없다. 중요한건 위의 내용을 지켜야 한다는 것!


### plugin-hello.c

간단히 hello를 출력하는 함수들을 갖고 있는 플러그인.

```

#include <stdio.h>

#include "types.h"


int say1(char* prefix) {

    return printf("%s hello1\n", prefix);

}


int say2(char* prefix) {

    return printf("%s hello2\n", prefix);

}


Function functions[] = {

        {

                .name = "say1",

                .func = say1

        },

        {

                .name = "say2",

                .func = say2

        }

};

int functions_count = sizeof(functions) / sizeof(Function);

```

### plugin-world.c

간단히 world를 출력하는 함수들을 갖고 있는 플러그인.

```

#include <stdio.h>

#include "types.h"


int say1(char* prefix) {

    return printf("%s world1\n", prefix);

}


int say2(char* prefix) {

    return printf("%s world2\n", prefix);

}


Function functions[] = {

        {

                .name = "say1",

                .func = say1

        },

        {

                .name = "say2",

                .func = say2

        }

};

int functions_count = sizeof(functions) / sizeof(Function);

```


## 돌려봅시다


###컴파일


- 메인에는 아주 특별한 처리는 필요없고, `dlopen(), dlclose()`같은 라이브러리 함수를 사용했기 때문에 `-ldl` 플래그를 넣어준다

- 플러그인에는 동적 라이브러리를 위한 전용 플래그 `-shared`와 `-fPIC`를 넣어준다. 각각의 플래그에 대한 설명은 구글링하면 참고할 페이지가 널려있으므로 설명은 생략한다. 동적 라이브러리는 리눅스의 경우 .so 확장자를 갖는 것이 관례이므로 `플러그인명.so`로 컴파일한다


```

gcc -std=gnu99 -o main main.c -ldl

gcc -std=gnu99 -shared -fPIC -o plugin-hello.so plugin-hello.c

gcc -std=gnu99 -shared -fPIC -o plugin-world.so plugin-world.c

```

_

###실행


1. 컴파일이 끝나면 `./main`을 입력해 프로그램을 실행한다.

2. 프로그램이 정상적으로 실행되면 플러그인의 이름을 입력(예를 들어 `plugin-hello.so`)한다.

3. 메인 프로그램이 붙여진 플러그인의 기능을 잘 이용하는 것을 볼 수 있다.


당연한 이야기이지만, 메인 프로그램을 켜둔 와중에

- 새로운 플러그인을 작성하고

- 컴파일한뒤

- 돌고 있던 메인 프로그램과 결합하는 것이 가능하다.

'C/C++' 카테고리의 다른 글

pthread detach  (0) 2017.02.11
C 프로세스 자원 사용량 확인하기  (0) 2017.01.28
C String Utility  (0) 2016.12.22
C printStackTrace  (0) 2016.12.02
Linux C Socket Util  (0) 2016.11.08