@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 |