Hệ thống plugin
Sau bài này bạn sẽ làm được
- Thiết kế giao diện đăng ký cho plugin
- Kiểm tra phiên bản ABI trước khi tin một plugin
- Quét thư mục và nạp mọi thư viện tìm được
- Giải phóng đúng cách khi thoát
Người dùng thả một tệp vào thư mục plugins và chương trình có thêm một lệnh mới, không cần dịch lại gì. Đây là ứng dụng lớn nhất của việc nạp lúc chạy, và nó chỉ cần khoảng tám mươi dòng.
#Giao diện đăng ký
#ifndef PLUGIN_H
#define PLUGIN_H
#ifndef PLUGIN_ABI
#define PLUGIN_ABI 1
#endif
typedef struct {
int abi;
const char *ten;
const char *mo_ta;
int (*chay)(const char *doi_so);
} Plugin;
/* Moi plugin phai xuat ra dung mot ham nay. */
typedef const Plugin *(*HamDangKy)(void);
#endif#Hai plugin
#include <stdio.h>
#include <ctype.h>
#include "plugin.h"
static int chay(const char *s) {
for (; *s; ++s) putchar(toupper((unsigned char)*s));
putchar('\n');
return 0;
}
static const Plugin P = { PLUGIN_ABI, "hoa", "Doi chuoi thanh chu hoa", chay };
__declspec(dllexport) const Plugin *dang_ky(void) { return &P; }#include <stdio.h>
#include <string.h>
#include "plugin.h"
static int chay(const char *s) {
printf("do dai = %d\n", (int)strlen(s));
return 0;
}
static const Plugin P = { PLUGIN_ABI, "dem", "Dem so ky tu", chay };
__declspec(dllexport) const Plugin *dang_ky(void) { return &P; }# Cach xuat ra ham dang_ky tren tung nen tang
#if defined(_WIN32)
#define PLUGIN_XUAT __declspec(dllexport)
#else
#define PLUGIN_XUAT __attribute__((visibility("default")))
#endif
PLUGIN_XUAT const Plugin *dang_ky(void) { return &P; }
# Dich:
gcc -shared pl_hoa.c -o plugins/pl_hoa.dll # Windows
gcc -fPIC -shared -fvisibility=hidden pl_hoa.c -o plugins/pl_hoa.so # Linux#Chương trình chính
#include <stdio.h>
#include <string.h>
#include <windows.h>
#include "plugin.h"
enum { TOI_DA = 16 };
static const Plugin *ds[TOI_DA];
static HMODULE mo[TOI_DA];
static int so;
static int nap_mot(const char *duong_dan) {
HMODULE h = LoadLibraryA(duong_dan);
if (h == NULL) {
fprintf(stderr, "khong nap duoc %s (loi %lu)\n",
duong_dan, (unsigned long)GetLastError());
return -1;
}
HamDangKy f = (HamDangKy)(void *)GetProcAddress(h, "dang_ky");
if (f == NULL) {
fprintf(stderr, "%s: khong co ham dang_ky\n", duong_dan);
FreeLibrary(h);
return -1;
}
const Plugin *p = f();
if (p == NULL || p->abi != PLUGIN_ABI) {
fprintf(stderr, "%s: ABI %d, can %d\n",
duong_dan, p ? p->abi : -1, PLUGIN_ABI);
FreeLibrary(h);
return -1;
}
ds[so] = p;
mo[so] = h;
++so;
printf("da nap: %-6s %s\n", p->ten, p->mo_ta);
return 0;
}static void nap_thu_muc(const char *thu_muc) {
char mau[512];
snprintf(mau, sizeof mau, "%s\\*.dll", thu_muc);
WIN32_FIND_DATAA fd;
HANDLE h = FindFirstFileA(mau, &fd);
if (h == INVALID_HANDLE_VALUE) return;
do {
char duong[512];
snprintf(duong, sizeof duong, "%s\\%s", thu_muc, fd.cFileName);
nap_mot(duong);
} while (so < TOI_DA && FindNextFileA(h, &fd));
FindClose(h);
}
int main(int argc, char **argv) {
nap_thu_muc("plugins");
printf("\nco %d plugin\n\n", so);
if (argc >= 3) {
for (int i = 0; i < so; ++i)
if (strcmp(ds[i]->ten, argv[1]) == 0) {
ds[i]->chay(argv[2]);
break;
}
}
for (int i = 0; i < so; ++i) FreeLibrary(mo[i]);
return 0;
}#include <dirent.h>
#include <string.h>
static void nap_thu_muc(const char *thu_muc) {
DIR *d = opendir(thu_muc);
if (d == NULL) return;
struct dirent *e;
while (so < TOI_DA && (e = readdir(d)) != NULL) {
const char *duoi = strrchr(e->d_name, '.');
if (duoi == NULL || strcmp(duoi, ".so") != 0) continue;
char duong[512];
snprintf(duong, sizeof duong, "%s/%s", thu_muc, e->d_name);
nap_mot(duong);
}
closedir(d);
}
/* Chu y: duong dan PHAI co dau gach cheo, neu khong dlopen se tim
trong LD_LIBRARY_PATH thay vi trong thu muc cua ban. Bai 39.10. */#Chạy thử
da nap: dem Dem so ky tu da nap: hoa Doi chuoi thanh chu hoa co 2 plugin XIN CHAO THE GIOI
da nap: dem Dem so ky tu da nap: hoa Doi chuoi thanh chu hoa co 2 plugin do dai = 17
#Bốn điều cần thêm cho bản thật
Hàm khởi tạo và dọn dẹp cho plugin
Plugin có thể cần mở tệp, cấp phát, đăng ký với hệ thống. Thêm hai con trỏ hàm
khoi_taovàdon_depvào struct, và gọidon_deptrước khi gỡ thư viện.Bảng hàm để plugin gọi ngược lại
Plugin cần ghi nhật ký, đọc cấu hình, cấp phát bằng bộ cấp phát của chương trình chính. Truyền một struct chứa các con trỏ hàm đó vào
khoi_tao.Kiểm tra tên trùng
Hai plugin cùng đăng ký tên
hoathì cái nào thắng? Từ chối cái thứ hai và báo rõ, đừng để im lặng.Cô lập lỗi
Một plugin sập là cả chương trình sập. Nếu điều đó không chấp nhận được thì phải chạy plugin trong tiến trình riêng và giao tiếp qua đường ống, như cách trình duyệt làm.
#define PLUGIN_ABI 2
/* Bang ham chuong trinh chinh cung cap cho plugin */
typedef struct {
void (*ghi_log)(int muc, const char *tin);
void *(*cap_phat)(size_t n);
void (*giai_phong)(void *p);
const char *(*cau_hinh)(const char *khoa);
} DichVu;
typedef struct {
int abi;
const char *ten;
const char *mo_ta;
const char *phien_ban;
int (*khoi_tao)(const DichVu *dv); /* tra ve 0 neu thanh cong */
void (*don_dep)(void);
int (*chay)(const char *doi_so);
} Plugin;/* Nap */
const Plugin *p = f();
if (p == NULL || p->abi != PLUGIN_ABI) { FreeLibrary(h); return -1; }
/* Kiem tra trung ten */
for (int i = 0; i < so; ++i)
if (strcmp(ds[i]->ten, p->ten) == 0) {
fprintf(stderr, "%s: ten '%s' da co, bo qua\n", duong_dan, p->ten);
FreeLibrary(h);
return -1;
}
/* Khoi tao */
if (p->khoi_tao != NULL && p->khoi_tao(&g_dich_vu) != 0) {
fprintf(stderr, "%s: khoi tao that bai\n", duong_dan);
FreeLibrary(h);
return -1;
}
ds[so] = p; mo[so] = h; ++so;
/* ... chuong trinh chay ... */
/* Go, theo thu tu NGUOC lai thu tu nap */
for (int i = so; i-- > 0; ) {
if (ds[i]->don_dep != NULL) ds[i]->don_dep();
FreeLibrary(mo[i]); /* don_dep TRUOC, go SAU */
}Tự làm thử
- Dựng đủ hệ thống plugin trong bài và chạy được cả hai lệnh.
- Viết plugin thứ ba và xác nhận chương trình chính không cần dịch lại.
- Dịch một plugin với
-DPLUGIN_ABI=2và xác nhận nó bị từ chối. - Bỏ
FreeLibraryở một nhánh lỗi và tìm rò rỉ bằng công cụ. - Thêm
khoi_tao,don_depvà bảng dịch vụ. - Gọi
don_depsauFreeLibraryvà quan sát chương trình sập.
Trình chấm điểm tự động sẽ được bổ sung ở giai đoạn sau. Hiện tại bạn tự chạy thử trên máy.
Tóm tắt
- Một hàm xuất ra duy nhất trả về một struct mô tả, và nạp plugin không chạy mã nghiệp vụ nào.
- Trường
abiphải ở độ dời 0 để đọc được an toàn kể cả khi phần còn lại của struct có bố cục khác. - Mọi thứ trong plugin đều
statictrừ hàm đăng ký, và nạp vớiRTLD_LOCAL. - Mỗi nhánh lỗi phải gọi
FreeLibrary, nếu không mỗi lần quét thư mục lại rò rỉ một tay cầm. - Khởi tạo xuôi, dọn dẹp ngược, và luôn gọi
don_deptrước khi gỡ thư viện.