當前位置:
首頁 > 科技 > C模塊化編程

C模塊化編程

模塊化編程是指程序核心部分定義好功能的介面,而具體的實現留給各個模塊去做。舉個現實世界的例子:我們可以在電腦的PCI插槽上安裝顯卡、音效卡或者網卡,原因就是這些硬體都按照PCI介面的規範來製造的。

模塊化編程也一樣,程序核心部分定義好介面,各個模塊按照介面的定義去實現功能,然後把各個模塊掛載到程序上即可,這個有點像Java的面向介面編程。如下圖:

C模塊化編程

(圖一)

模塊化編程的好處就是最大化靈活性,程序的核心部分不用關心功能的具體實現,只需要調用模塊提供的介面即可得到相應的結果。因為各個模塊的具體實現各不相同,所以得到的結果也是多樣化的。


使用C進行模塊化編程

用過C語言編程的人都知道C語言是沒有介面的,所以怎麼使用C語言進行模塊化編程呢?使用C語言的結構體和函數指針可以模擬出Java介面的特性,我們只需定義一個由多個函數指針構成的結構體,然後功能模塊實現這個結構體里的函數即可。

例如我們定義一個名為Car的結構體,而這個結構體有兩個函數指針,分別是run()和stop():

car.h

#ifndef __CAR_H
#define __CAR_H

struct Car
{
void (*run)();
void (*stop)();
};

#endif

從上面定義可以知道,實現了run()和stop()方法的模塊都可以被稱為Car(汽車)。現在我們來編寫兩個模塊,分別是Truck和Van。

Truck模塊如下(truck.c):

#include <stdlib.h>
#include <stdio.h>
#include "car.h"

static void run()
{
printf("I am Truck, running...
");
}

static void stop()
{
printf("I am Truck, stopped...
");
}

struct Car truck = {
.run = &run,
.stop = &stop,
};

Van模塊如下(van.c):

#include <stdlib.h>
#include <stdio.h>
#include "car.h"

static void run()
{
printf("I am Van, running...
");
}

static void stop()
{
printf("I am Van, stopped...
");
}

struct Car van = {
.run = &run,
.stop = &stop,
};

這樣我們編寫了兩個模塊,一個是卡車模塊(Truck),一個是客車模塊(Van)。為了簡單起見,我們只是簡單的列印一段文字。現在可以說我們的車是變形金剛了,因為可以隨時變成卡車或者客車(嘻嘻)。

我們把模塊都寫好了,但是怎麼把模塊應用到程序的核心部分呢?這時候我們需要一個註冊機制。因為核心部分不知道我們到底編寫了什麼模塊,所以就需要這個註冊機制來告訴核心部分。註冊機制很簡單,只需要一個函數即可(main.c):

#include "car.h"

extern struct Car van;
extern struct Car truck;

struct Car *car;

void register_module(struct Car *module)
{
car = module;
}

int main(int argc, char *argv[])
{
register_module(&truck);

car->run();
car->stop();

return 0;
}

編譯運行後我們會得到以下的結果:

C模塊化編程

如果把 register_module(&truck); 改為 register_module(&van); 會得到以下結果:

C模塊化編程

從上面的結果可以看到,我們可以註冊不同的模塊來提供不同的服務,模塊化編程就這樣實現了。

Are you kidding me?

C的模塊化編程的確是這麼簡單,但是我們可以實現更強大的功能:使用動態鏈接庫來實現模塊化。


使用動態鏈接庫進行模塊化編程

Linux提供一種叫動態鏈接庫的技術(Windows也有類似的功能),可以通過系統API動態載入.so文件中的函數或者變數。動態鏈接庫的好處是把程序劃分成多個獨立的部分編譯,每個部分的編譯互補影響。例如我們有動態鏈接庫A、B、C,如果發現A有bug,我們只需要修改和重新編譯A即可,而不用對B和C進行任何的改動。

下面我們使用動態鏈接庫技術來重寫上面的程序。

其實要使用動態鏈接庫技術,只需要把模塊編譯成.so文件,然後核心部分使用操作系統提供的dlopen()dlsym()介面來載入模塊即可。

1. 把模塊編譯成.so文件

首先我們修改van.c文件,主要是增加一個讓核心部分獲取模塊介面的方法get_module():

#include <stdlib.h>
#include <stdio.h>
#include "car.h"

static void run()
{
printf("I am Van, running...
");
}

static void stop()
{
printf("I am Van, stopped...
");
}

struct Car module = {
.run = &run,
.stop = &stop,
};

sturct Car *get_module()
{
return &module;
}

然後我們需要把庫的源文件編譯成無約束位代碼。無約束位代碼是存儲在主內存中的機器碼,執行的時候與絕對地址無關。

$gcc -c -Wall -Werror -fpic van.c

現在讓我們將對象文件變成共享庫。我們將其命名為van.so:

$ gcc -shared -o van.so van.o

這樣我們就把van.c編譯成動態鏈接庫了。我們使用相同的方法把truck.c編譯成truck.so。

2. 在核心部分載入動態鏈接庫

使用動態鏈接庫介面來修改核心部分代碼,如下:

#include "Car.h"
#include <dlfcn.h>
#include <stdlib.h>

struct Car *car;

struct Car *register_module(char *module_name)
{
struct Car *(*get_module)();
void *handle;

handle = dlopen(module_name, RTLD_LAZY);
if (!handle) {
return NULL;
}

get_module = dlsym(handle, "get_module");
if (dlerror() != NULL) {
dlclose(handle);
return NULL;
}

dlclose(handle);

return get_module();
}

int main(int argc, char *argv[])
{
struct Car *car;

if ((car = register_module("./van.so")) == NULL) {
return -1;
}

car->run();
car->stop();

return 0;
}

使用以下命令編譯代碼:

$ gcc -rdynamic -o car main.c -ldl

運行程序後得到結果:

C模塊化編程

修改 register_module("./van.so") 為 register_module("./ truck .so") 得到結果:

C模塊化編程

可以看到我們成功使用動態鏈接庫改寫了程序。

總結

由於模塊化編程的靈活性和可擴展性非常好,所以很多流行的軟體也提供模塊化特性,如:Nginx、PHP和Python等。而這些軟體使用的方法與本文介紹的大致相同,有興趣可以查看這些軟體的實現。

喜歡這篇文章嗎?立刻分享出去讓更多人知道吧!

本站內容充實豐富,博大精深,小編精選每日熱門資訊,隨時更新,點擊「搶先收到最新資訊」瀏覽吧!

TAG: |