动态库参数化

本文介绍了如何通过参数化动态库,实现主程序根据不同的输入动态选择链接对应的库,如bmp或jpg显示库。通过实例展示了如何创建并链接bmp和jpg显示库,以及在主程序show_image中调用动态库的详细过程,强调了这种方法的灵活性和可移植性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

当一个主程序在不同的情况下,需要链接功能不同的动态库时,我们一般的做法时,每换一个情景,我们就要让程序重新链接一个新的动态库,这样就显得很麻烦又笨拙。如果我们可以把动态库作为参数来传递,那样我们只需要在主程序中判断是哪种情景,然后由程序帮我们自动选择所需要链接的动态库即可。

下面我们用一个实例来详细讲解:
这个实例的功能是用一个主程序来使lcd液晶屏显示bmp格式或者jpg格式的图片,当接收到的参数是bmp格式的图片时,就链接bmp动态库;当接收到的参数是jpg格式的图片时,就链接jpg动态库。

制作显示bmp、JPG图片的动态库

common.h是常用的一些头文件

//common.h

#ifndef __COMMOD_H
#define __COMMOD_H

#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <strings.h>
#include <string.h>
#include <sys/mman.h>
#include <sys/ioctl.h>

#include <math.h>
#include <fcntl.h>
#include <unistd.h>
#include <dirent.h>
#include <time.h>
#include <errno.h>
#include <linux/input.h>
#include <linux/fb.h>

#include <dlfcn.h>

#endif

bmp.h是显示bmp图片所需要的头文件

//bmp.h

#ifndef __BMP_H_
#define __BMP_H_

#include <stdio.h>
#include <dirent.h>
#include <string.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#include <unistd.h>
#include <linux/input.h>
#include <linux/fb.h>
#include <sys/mman.h>
#include <pthread.h>

#define LCD "/dev/fb0"

#define BACKGROUND "./res/bmp/background.bmp"
#define BAR        "./res/bmp/bar.bmp"
#define LOGO       "./res/bmp/logo.bmp"
#define KEYON      "./res/bmp/key_on.bmp"
#define KEYOFF     "./res/bmp/key_off.bmp"


struct bitmap_header
{
	int16_t type;
	int32_t size; // 图像文件大小
	int16_t reserved1;
	int16_t reserved2;
	int32_t offbits; // bmp图像数据偏移量
}__attribute__((packed));

struct bitmap_info
{
	int32_t size; // 本结构大小	
	int32_t width; // 单位:像素
	int32_t height;// 单位:像素
	int16_t planes; // 总为零

	int16_t bit_count; // 色深:24(1像素=24位=3字节)
	int32_t compression;
	int32_t size_img; // bmp数据大小,必须是4的整数倍
	int32_t X_pel;
	int32_t Y_pel;
	int32_t clrused;
	int32_t clrImportant;
}__attribute__((packed));

struct rgb_quad
{
	int8_t blue;
	int8_t green;
	int8_t red;
	int8_t reserved;
}__attribute__((packed));


struct image_info
{
	int width;
	int height;
	int pixel_size;
};

void draw_piano(char *FB, struct fb_var_screeninfo *vinfo, char *backgound);
char *load_bmp (const char *bmpfile, struct image_info *imginfo);

void freelcd(void);
void display(char *image, int offset_x, int offset_y);

#endif

showbmp.c是封装好的显示bmp图片的功能模块

//showbmp.c

#include "common.h"
#include "bmp.h"

static bool first = true;
static int lcd_w;
static int lcd_h;
static int lcd_bpp;
static int lcd;
static char *lcdmem;

void display(char *bmp, int x, int y) // ./main xxx.bmp
{

	// 0,分析BMP图片的头部信息,提取长宽深等信息
	int fd = open(bmp, O_RDWR);

	struct bitmap_header head;
	struct bitmap_info   info;

	bzero(&head, sizeof(head));
	bzero(&info, sizeof(info));
	read(fd, &head, sizeof(head));
	read(fd, &info, sizeof(info));

	// 1,读取BMP图片的具体图像数据(RGB)
	int rgb_size = head.size-sizeof(head)-sizeof(info);
	char *rgb = calloc(1, rgb_size);
	int total = 0; 
	while(rgb_size > 0)
	{
		int n = read(fd, rgb+total, rgb_size);
		rgb_size -= n;
		total += n;
		
	}

	if(first)
	{
		// 2,打开LCD设备
		lcd = open("/dev/fb0", O_RDWR);


		// 3,获取LCD设备的硬件参数(分辨率、色深……)
		struct fb_var_screeninfo  vsinfo;
		bzero(&vsinfo, sizeof(vsinfo));
		ioctl(lcd, FBIOGET_VSCREENINFO, &vsinfo); // 将LCD设备参数信息放入svinfo中

		printf("屏幕分辨率: %d × %d\n", vsinfo.xres, vsinfo.yres);
		// 4,映射内存
		lcd_w = vsinfo.xres;
		lcd_h = vsinfo.yres;
		lcd_bpp = vsinfo.bits_per_pixel;

		lcdmem = mmap(NULL, lcd_w*lcd_h*lcd_bpp/8, 
				PROT_READ | PROT_WRITE, MAP_SHARED, lcd, 0);
		if(lcdmem == MAP_FAILED)
		{
			perror("映射内存失败");
			exit(0);
		}
		first = false;
	}

	bzero(lcdmem, lcd_w*lcd_h*lcd_bpp/8);
	int w = (lcd_w-x)<info.width ? (lcd_w-x) : info.width;
	int h = (lcd_h-y)<info.height ? (lcd_h-y) : info.height;

	int pad = (4-(info.width*3)%4) % 4;
	int img_line_size = info.width*3 + pad;
	int lcd_line_size = lcd_w * lcd_bpp/8;
	
	// 5,妥善地将BMP中的RGB数据搬到映射内存上
	char *rgb_r = rgb + img_line_size*(info.height-1);
	char *tmp = lcdmem+(y*800+x)*4;

	for(int j=0; j<h; j++)
	{
		for(int i=0; i<w; i++)
		{
			memcpy(tmp+j*lcd_line_size+4*i, (rgb_r-j*img_line_size)+3*i, 3);
		}
	}

	// 6, 释放相应资源
	close(fd);
	free(rgb);
}

void freelcd(void)
{
	if(!first)
	{
		munmap(lcdmem, lcd_w*lcd_h*lcd_bpp/8);
		close(lcd);
	}
	first = true;
}

上面这部分代码就可以制作bmp的动态库libbmp.so了,具体的命令请移步到我的另一篇文章:linux下的动态库静态库解析

接下来就是制作显示jpg图片的动态库了,下面这个showjpg.c是封装好的显示jpg图片的功能模块。当然除了这个.c文件外,我们肯定还需要链接JPEG库,至于如何编译移植JPEG库就请移步到我的另一篇文章了:编译移植使用JPEG库

//showjpg.c

#include "common.h"
#include "jpeglib.h"

static int lcd;
static int lcd_w;
static int lcd_h;
static int lcd_bpp;
static int screen_size;
static struct fb_var_screeninfo vsinfo;
static char *lcdmem;

static bool first = true;

void display(char *jpg, int x, int y)
{
	// 第一次显示图片的时候,就准备好LCD设备
	if(first)
	{
		lcd = open("/dev/fb0", O_RDWR);

		bzero(&vsinfo, sizeof(vsinfo));
		ioctl(lcd, FBIOGET_VSCREENINFO, &vsinfo); // 获取了LCD的硬件参数

		lcd_w = vsinfo.xres;
		lcd_h = vsinfo.yres;
		lcd_bpp =  vsinfo.bits_per_pixel;
		screen_size = lcd_w * lcd_h * lcd_bpp/8;

		lcdmem = mmap(NULL, screen_size, PROT_READ | PROT_WRITE, MAP_SHARED, lcd, 0);
		first = false;
	}

	bzero(lcdmem, screen_size);

	int jpgfd = open(jpg, O_RDONLY);

	int jpg_size = lseek(jpgfd, 0L, SEEK_END);
	lseek(jpgfd, 0L, SEEK_SET);

	char *jpg_buffer = calloc(1, jpg_size);

	// 完整地读完了 a.jpg 文件的内容,妥妥地放到了 jpg_buffer 中
	int total = jpg_size;
	while(total > 0)
	{
		int n = read(jpgfd, jpg_buffer+(jpg_size-total), total);
		total -= n;
	}

	close(jpgfd);


    	// JPG  ==>  RGB

	// 声明解压缩结构体,以及错误管理结构体
	struct jpeg_decompress_struct cinfo;
	struct jpeg_error_mgr jerr;

	// 使用缺省的出错处理来初始化解压缩结构体
	cinfo.err = jpeg_std_error(&jerr);
	jpeg_create_decompress(&cinfo);

	// 配置该cinfo,使其从jpg_buffer中读取jpg_size个字节
	// 这些数据必须是完整的JPEG数据
	jpeg_mem_src(&cinfo, jpg_buffer, jpg_size);


	// 读取JPEG文件的头,并判断其格式是否合法
	int ret = jpeg_read_header(&cinfo, true);
	if(ret != 1)
	{
		fprintf(stderr, "[%d]: jpeg_read_header failed: "
			"%s\n", __LINE__, strerror(errno));
		exit(1);
	}


	// 开始解码
	jpeg_start_decompress(&cinfo);

	// cinfo中保存了图片文件的尺寸信息
	cinfo.output_width; // 宽
	cinfo.output_height; // 高
	cinfo.output_components; // 深:每个像素点包含的字节数

	// 图片的每一行所包含的字节数
	int row_stride = cinfo.output_width * cinfo.output_components;

	// 根据图片的尺寸大小,分配一块相应的内存rgb_buffer
	// 用来存放从jpg_buffer解压出来的图像数据
	unsigned char *rgb_buffer = calloc(1, row_stride*cinfo.output_height);

	// 循环地将图片的每一行读出并解压到rgb_buffer中
	int line = 0;
	while(cinfo.output_scanline < cinfo.output_height)
	{
		unsigned char *buffer_array[1];
		buffer_array[0] = rgb_buffer +
				(cinfo.output_scanline) * row_stride;
		jpeg_read_scanlines(&cinfo, buffer_array, 1);
	}

	// 解压完了,将jpeg相关的资源释放掉
 	jpeg_finish_decompress(&cinfo);
	jpeg_destroy_decompress(&cinfo);
	free(jpg_buffer);


	// 恭喜!现在rgb_buffer中就已经有图片对应的RGB数据了
	int red_offset  = vsinfo.red.offset;
	int green_offset= vsinfo.green.offset;
	int blue_offset = vsinfo.blue.offset;

	char *lcdtmp = lcdmem;
	char *rgbtmp = rgb_buffer;

	lcdtmp +=  (y*lcd_w+ x)*(lcd_bpp/8);

	int w = (lcd_w-x)<cinfo.output_width ? (lcd_w-x) : cinfo.output_width;
	int h = (lcd_h-y)<cinfo.output_height? (lcd_h-y) : cinfo.output_height;

	// 将rgb数据妥善地放入lcdmem
	for(int j=0; j<h; j++)
	{
		for(int i=0; i<w; i++)
		{
			memcpy(lcdtmp + 4*i + red_offset/8,  rgbtmp + 3*i + 0, 1);
			memcpy(lcdtmp + 4*i + green_offset/8, rgbtmp + 3*i + 1, 1);
			memcpy(lcdtmp + 4*i + blue_offset/8,   rgbtmp + 3*i + 2, 1);
		}

		lcdtmp += (lcd_w*lcd_bpp/8); // lcd显存指针向下偏移一行
		rgbtmp += (row_stride); // rgb指针向下偏移一行
	}

	// 释放相应的资源
	free(rgb_buffer);
}	

void freelcd(void)
{
	if(!first)
	{
		munmap(lcdmem, screen_size);
		close(lcd);

		first = true;
	}
}

接下来我们制作jpg库,这个是我们自己写的用来专门显示jpg图片的动态库,和JPEG库是不一样的,JPEG库是用来专门解码jpg格式的图片的,由于我们要显示jpg图片,所以我们需要用JPEG库来解码jpg图片,以获取jpg图片上面的数据。

经过上面的努力,我们会得到两个动态库文件:libbmp.solibjpg.so
接下来,我们就要把这两个动态库作为参数传递到主函数中。
下面是主程序show_image的代码:

#include "common.h"

int main(int argc, char **argv) // ./show_iamge xxx.jpg/xxx.bmp
{
	if(argc != 2)
	{
		printf("用法: %s <图片文件(*.bmp/*.jpg)>\n", argv[0]);
		exit(0);
	}

	void *handle;

	// 0,判断后缀,并打开相应的动态库
	if(strstr(argv[1], ".bmp")) // 图片是BMP格式的,加载BMP库
	{
		handle = dlopen("libbmp.so", RTLD_NOW);
	}
	if(strstr(argv[1], ".jpg")) // 图片是JPG格式的,加载JPG库
	{
		handle = dlopen("libjpg.so", RTLD_NOW);
	}
	

	// 1,查找指定的函数接口
	void (*display)(char *file, int x, int y);
	void (*freelcd)(void);
	display = dlsym(handle, "display");
	freelcd = dlsym(handle, "freelcd");

	if(display == NULL || freelcd == NULL)
	{
		perror("找不到display或者找不到freelcd");
		exit(0);
	}

	// 2,使用相应的库接口来显示指定的图片
	display(argv[1], 50, 20);
	freelcd();

	dlclose(handle);
	return 0;
}

然后我们编译show_image.c

arm-none-linux-gnueabi-gcc show_image.c -o show_image -ldl -L dir/ -ljpeg

其中:

-ldl 是使用了动态链接库接口
-L dir/ 指定第三方库所在的路径(即JPEG库所安装的路径下的lib/目录)
-ljpeg 链接JPEG库

然后我们将可执行文件show_image、libbmp.so、libjpg.so传输到开发板上,修改main的权限为777,将两个动态库所在路径添加到LD_LIBRARY_PATH环境变量中,最后运行即可。

./show_image xxx.bmp //这样就可以显示一张bmp格式的图片
./show_image xxx.jpg //这样就可以显示一张jpg格式的图片,两种格式可以随时切换

总结

  1. 将 showbmp.c 编译成 showbmp.o,没有借助任何第三方库

  2. 将 showjpg.c 编译成 showjpg.o ,由于用到了 jpeg 第三方库的相应的函数,因此需要指定和包含相应的头文件。由于编译成 jpg.o 不进行库代码的链接,因此不需要指定 jpeg 库。

  3. 将 showbmp.o 制作成动态库 libbmp.so

  4. 将 showjpg.o 制作成动态库 libjpg.so,由于将来在执行 libjpg.so 中的代码时需要链接 libjpeg.so ,因此在此编译指令中需要指定 libjpeg.so

  5. 编译show_image.c ,该程序编译时没有指定任何图像编解码库,而是用了动态链接库接口,因此编译指令包含了 -ldl 选项,使用该接口表明将本程序链接动态库的行为从编译时推迟到运行时,以此来获得更大的灵活性。

如果以后还有其他格式的图片需要显示,直接将制作好的动态库传到之前那些格式的动态库的路径下,再将新的动态库作为参数给主程序调用即可,这样大大提高了效率,具有很强的可移植性。

这只是一个用来说明问题的实例,动态库参数化这个技术可以运用在很多复杂情景,剩下的就大家一起探索吧。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值