题目:
利用动态链接库技术实现具有热插拔能力的“消息映射表”程序。程序在功能上表现为一个计算器程序,主菜单中提示:
Press A:Adding;
Press S: Subtracting
Press M: Multiplying
Press D: Dividing
用户键入A则启动加法子程序,提示用户输入两个操作数,并输出计算结果。用户键入S则启动减法子程序。以此类推。子程序执行完毕之后再回到主菜单状态。
使该程序有热插拔能力是指可以通过配置文件在不改变主程序的前提下动态增加新的菜单项和新的功能(例如增加乘方、开方、指数、对数运算等。)
设计思路:
首先设计程序所需的动态链接库liba.so 和 libb.so,然后在主程序中读配置文件信息,根据配置文件的信息使用动态库。
设计动态链接库:根据题目要求设计动态链接库liba.so与libb.so,liba.so中包含四个计算程序
“a.h”
include<stdio.h>
void Add();
void Sub();
void Multiply();
void Divide();
“a.c”
void Add(){
int a=0,b=0;
int result=0;
printf("input two numbers:\n");
scanf("%d %d",&a,&b);
result=a+b;
printf("the result of Adding is %d\n",result);
}
void Sub(){
int a=0,b=0;
int result=0;
printf("input two numbers:\n");
scanf("%d %d",&a,&b);
result=a-b;
printf("the result of Subing is %d\n",result);
}
void Multiply(){
int a=0,b=0;
int result=0;
printf("input two numbers:\n");
scanf("%d %d",&a,&b);
result=a*b;
printf("the result of Multiply is %d\n",result);
}
void Divide(){
double a=0.0,b=0.0;
double result=0.0;
printf("input two numbers:\n");
scanf("%lf %lf",&a,&b);
result=a/b;
printf("the result of Divide is %lf\n",result);
}
//"b.h"
#include<stdio.h>
#include<math.h>
void Pow();
//"b.c"
#include"b.h"
void Pow(){
int a=0,b=0;
long int result=0;
printf("input two numbers:\n");
scanf("%d %d",&a,&b);
result=pow(a,b);
printf("the result of Adding is %ld\n",result);
}
将源代码编译成动态链接库
gcc a.c -fPIC -shared -o liba.so
gcc b.c -fPIC -shared -o libb.so
-fPIC是指建立位置无关代码;
-shared标签是告诉编译器生成动态链接库
动态链接库建立后,建议将库放在Liunx默认的链接库文件目录/lib或者/usr/lib中。
主程序设计
/*main.cpp*/
#include<stdio.h>
#include<iostream>
#include<string.h>
#include"readso.hpp"
#define fun_max_number 20
#define line_max_length 100
using namespace std;
int main(int argc,char*argv[])
{
method met[fun_max_number];
FILE *configfile=fopen("config.ini","r");
//读配置文件信息
int funcount=0;
char buff[100];
while(fgets(buff,line_max_length,configfile))
{
met[funcount]=method(buff);
//cout<<met[funcount].msg<<" "<<met[funcount].menu<<" "<<met[funcount].libso<<" "<<met[funcount].fun_entry<<" "<<endl;
funcount++;
bzero(buff,100*sizeof(char));
}
fclose(configfile);
// cout<<"close config"<<endl;
//循环执行部分while(1)
while(1)
{
for(int i=0;i<funcount;i++)
cout<<"Pressing "<<met[i].msg<<" :"<<met[i].menu<<endl;
//printf("Pressing %c : %s \n",met[i].msg,met[i].menu);
char flag;
cin>>flag;
for(int a=0;a<funcount;a++)
{
if(flag==met[a].msg)
{
met[a]._act();
//跳转到对应的函数,链接库
getchar();
break;
}
}
}
return 0;
}
主程序中读并且处理配置文件信息,然后用一个循环来作为主体,在循环中调用链接库。在处理配置文件信息的时候使用了一个method类,
//"readso.hpp"
#include<stdio.h>
#include<iostream>
#include<string.h>
#include<dlfcn.h>
using namespace std;
class method{
public:
method();
method(char *str);
~method()
{
char msg;
char *menu;
char *libso;//链接库的绝对路径
char *fun_entry;//对应借口的符号,根据这个符号从动态链接库中查找相关函数的地址
void *handle;//dlopen句柄
public:
void (*function)();
void _act();
};
//readso.cpp
#include"readso.hpp"
using namespace std;
method::method()
{
/*this->msg='A';
this->menu="Adding";
this->libso="/lib/liba.so";
this->fun_entry="Add";
*/
}
method::method(char *str)
{
char a,b[100],c[100],d[100];
int n=0;
sscanf(str,"%c %s %s %s",&a,b,c,d);
//cout<<"read::::"<<a<<" "<<b<<" "<<c<<" "<<d<<" "<<endl;
this->msg=a;
this->menu=new char[strlen(b)+1];
strcpy(this->menu,b);
this->libso=new char[strlen(c)+1];
strcpy(this->libso,c);
this->fun_entry=new char[strlen(d)+1];;
strcpy(this->fun_entry,d);
//sscanf(str,"%c %s %s %s",&(this->msg),this->menu,this->libso,this->fun_entry);
//cout<<this->msg<<" "<<this->menu<<" "<<this->libso<<" "<<this->fun_entry<<" "<<endl;
}
void method::_act()
{
this->handle=dlopen(this->libso,RTLD_LAZY);
//this->handle=dlopen(c,RTLD_LAZY);
if(!handle)
cout<<"error open "<<this->libso<<endl;
else{
this->function=(void(*)())dlsym(this->handle,this->fun_entry);
//this->function=(void(*)())dlsym(this->handle,d);
char*err=dlerror();
this->function();
//getchar();
dlclose(this->handle);
if(err)
cout<<err<<endl;
}
}
写的时候没有考虑的清楚,把method类写的比较复杂,其实关键得成员变量不需要这么多,method::method(char *str)是类的构造函数,*str是从配置文件读到的信息,每条*str指向的信息包含四个,比如
“A Adding a.so Add”。
在method::_act()中,使用了dlopen()和dlsym()函数,函数声明如下
#include <dlfcn.h>
void *dlopen(const char *filename, int flag);
char *dlerror(void);
void *dlsym(void *handle, const char *symbol);
int dlclose(void *handle);
dlopen以指定模式打开指定的动态连接库文件,并返回一个句柄给调用进程,dlerror返回出现的错误,dlsym通过句柄和连接符名称获取函数名或者变量名,dlclose来卸载打开的库。 dlopen打开模式如下:
RTLD_LAZY 暂缓决定,等有需要时再解出符号
RTLD_NOW 立即决定,返回前解除所有未决定的符号。
dlopen函数将lib.so加载到main程序内存中,dlsym()函数查找内存,查找符号表找到fun_entry对应接口地址,映射到void (*function)()成员,调用void (*function)()即为调用相关的函数,比如
function=(void(*)())dlsym(/lib/liba.so,Add);后
执行function()函数即为执行Add()函数。
至此,所有工作准备就绪。
编译主函数,在调试的时候编译的次数太多,就把编译过程写在了makefile文件中,makefile:
main:main.o readso.o
g++ -rdynamic -o main main.o readso.o -ldl
main.o:main.cpp readso.hpp
g++ -c main.cpp -o main.o
readso.o:readso.hpp readso.cpp
g++ -c readso.cpp -o readso.o
clean:
rm -rf *.o main
配置文件
A Adding /lib/liba.so Add
M Multiplying /lib/liba.so Multiply
D Dividing /lib/liba.so Divide
P Power /lib/libb.so Pow
S Subtracting /lib/liba.so Sub
需要注意的问题
dlopen接口是标准c的接口,c和c++的符号表有所不同,所以a.so和b.so不能用c++编写,否则在dlsym的时候无法在符号表中查找到Add()等接口。
只有在a.so和b.so中存在的函数才能写到配置文件中。
在更改配置文件后,比如,增加、删除操作后,需要通过重启程序的方式来获取到配置文件更改的结果,做到了题目中提到的在不更改主程序的情况下完成菜单的增加和删除操作。