什么是资源呢?
用过VC的人都知道VC的图片资源可以导进exe可执行文件中,也就是说发布程序的时候可以不用带着那些可恶的图片了,这也可以减少误删图片造成程序出问题的几率。但是在Linux下的GCC中则没有这个功能,因此在这里给出了几种可行的方案。
1.方案1:基于NASM的资源实现
NASM是x86汇编器,我们用它来定义数据段。
由于代码的是可模板化,图片多了就是重复性工作了,其次是图片的内容一般很大,如果手动定义图片的数据是不现实的,(太累了^-^)。下面给出python脚本来自动生成源代码,脚本名字script.py。
import struct def read_config(): f = open('configure') config = [] for line in f: c = line.split() config.append (c) return config def write_source (f): b = f.read(1) while(b): byte = struct.unpack("B", b) print "0x0%x," %(byte), b = f.read(1) print "0x0" def make_source(config): for c in config: name = "source"+c[0] print "global " + name+":" print("[section .data]") for c in config: name = "source"+c[0] print name+":" image = open(c[1]) print "db ", write_source(image) print if __name__ == '__main__': config = read_config() make_source(config)
上述脚本的功能是根据配置文件configure中的内容来生成NASM汇编代码,首先看一下configure的写法:
0 root.png
1 collection.png
configure的写法很简单,ID-imagename的格式,先写ID号(注意一定要从0开始连续的编号),然后是要作为资源的图片名字,同时将这些图片放在相同的目录下。然后就可以执行下面的命令:
python script.py>source.asm
执行完该命令后,可以得到一个source.asm的NASM的汇编源代码,我们分析一下生成的文件内容:
global source0: global source1: [section .data] source0: db 0x089, 0x050, 0x04e, 0x047, 0x0d, 0x0a, 0x01a, 0x0a, 0x00, 0x00, 0x00, 0x0d, 0x049 db 0x048, 0x044, 0x052, 0x00, 0x00, 0x00, 0x014, 0x00, 0x00, 0x00, 0x010, 0x08, 0x02 db 0x00, 0x00, 0x00, 0x099, 0x07a, 0x0c8, 0x04c, 0x00, 0x00, 0x00, 0x03, 0x073, 0x042 db 0x049, 0x054, 0x08, 0x08, 0x08, 0x0db, 0x0e1, 0x04f, 0x0e0, 0x00, 0x00, 0x01, 0x0da db 0x049, 0x044, 0x041, 0x054, 0x028, 0x053, 0x09d, 0x092, 0x0bf, 0x04b, 0x09b, 0x041 db 0x018, 0x0c7, 0x0ef, 0x0b5, 0x011, 0x0c4, 0x086, 0x04, 0x024, 0x0b6, 0x0a6, 0x06, 0x05a db 0x0ab, 0x014, 0x042, 0x087, 0x08a, 0x0d0, 0x045, 0x084, 0x066, 0x068, 0x017, 0x0b7 db 0x02c, 0x0ba, 0x0b5, 0x05b, 0x0e7, 0x04e, 0x015, 0x0a5, 0x0ff, 0x08a, 0x0e, 0x082, 0x076 db 0x070, 0x0cb, 0x02a, 0x0e, 0x01d, 0x0da, 0x082, 0x09b, 0x0a5, 0x083, 0x082, 0x0d6, 0x049 db 0x074, 0x089, 0x015, 0x062, 0x07c, 0x02f, 0x0ef, 0x0dd, 0x0f3, 0x05c, 0x0bf, 0x0cf, 0x0bd db 0x097, 0x0e4, 0x035, 0x05d, 0x0c4, 0x0cb, 0x0bd, 0x097, 0x0fb, 0x0f5, 0x0b9, 0x0ef, 0x0f7 db 0x09e, 0x07b, 0x022, 0x0e7, 0x09c, 0x0ba, 0x06f, 0x089, 0x00, 0x082, 0x037, 0x0ba, 0x0fd db 0x06d, 0x0f3, 0x0cb, 0x0e9, 0x0c1, 0x0ae, 0x063, 0x0f6, 0x033, 0x08a, 0x031, 0x0eb, 0x064 db 0x029, 0x0ed, 0x0e7, 0x046, 0x01e, 0x0ce, 0x0be, 0x07d, 0x03f, 0x05f, 0x0ff, 0x094, 0x015 db 0x08a, 0x0b0, 0x0a8, 0x0af, 0x0af, 0x0b6, 0x0d7, 0x06a, 0x0a3, 0x0f9, 0x062, 0x0a9, 0x03c db 0x0a5, 0x0a2, 0x048, 0x079, 0x086, 0x085, 0x04f, 0x0ab, 0x023, 0x056, 0x0c6, 0x09a, 0x0e6 db 0x0d9, 0x09f, 0x0ea, 0x042, 0x0fd, 0x0cd, 0x0f2, 0x04a, 0x08f, 0x017, 0x078, 0x06b, 0x0b5 db 0x036, 0x0fc, 0x020, 0x02a, 0x03d, 0x099, 0x0be, 0x038, 0x0fa, 0x069, 0x03a, 0x06d, 0x026 db 0x045, 0x04e, 0x011, 0x03b, 0x0cb, 0x0ca, 0x0e2, 0x014, 0x021, 0x05d, 0x0e1, 0x0f1, 0x0f3 db 0x042, 0x0a9, 0x072, 0x079, 0x07e, 0x0f2, 0x0e8, 0x069, 0x075, 0x0e9, 0x0f3, 0x086, 0x052 db 0x062, 0x039, 0x087, 0x02f, 0x06e, 0x035, 0x05f, 0x0bf, 0x05b, 0x0dc, 0x0df, 0x06b, 0x0c4 db 0x0ad, 0x04b, 0x021, 0x03d, 0x063, 0x0d9, 0x011, 0x029, 0x0b4, 0x06, 0x02d, 0x0b9, 0x0ab db 0x0e3, 0x0df, 0x063, 0x03a, 0x099, 0x079, 0x0f1, 0x0ea, 0x0d7, 0x08f, 0x06, 0x0ac, 0x079 db 0x0d6, 0x0c3, 0x0b8, 0x0d5, 0x078, 0x065, 0x0a2, 0x0fe, 0x0e1, 0x023, 0x06b, 0x0ed, 0x0d8 db 0x032, 0x0f6, 0x012, 0x011, 0x05a, 0x06b, 0x0d8, 0x05a, 0x036, 0x069, 0x087, 0x0d8, 0x0d8 db 0x0a1, 0x062, 0x0f9, 0x0e0, 0x07b, 0x03f, 0x0c0, 0x01, 0x086, 0x050, 0x0fc, 0x0f7, 0x090 db 0x093, 0x01b, 0x0b0, 0x06c, 0x058, 0x078, 0x043, 0x060, 0x041, 0x0fa, 0x08a, 0x08e, 0x09c db 0x092, 0x01f, 0x0ad, 0x020, 0x010, 0x05d, 0x061, 0x0af, 0x08c, 0x0c0, 0x012, 0x0ae, 0x068 db 0x088, 0x034, 0x01, 0x093, 0x09a, 0x040, 0x099, 0x0d1, 0x0a, 0x0e9, 0x087, 0x038, 0x014 db 0x05e, 0x0c4, 0x045, 0x0e6, 0x069, 0x083, 0x0b2, 0x06c, 0x0ea, 0x058, 0x0ea, 0x0e0, 0x078 db 0x04b, 0x041, 0x04a, 0x07c, 0x092, 0x0c1, 0x0a1, 0x029, 0x0e6, 0x0ed, 0x080, 0x0cf, 0x0a4 db 0x045, 0x050, 0x0c6, 0x02, 0x069, 0x063, 0x0b4, 0x019, 0x0f0, 0x029, 0x030, 0x0c1, ... .... ... .... ... ....
我们发现生成的文件内容很简单,首先是导出变量,以使C语言可是调用,这些变量是各个资源在内存中的指针。然后是数据段的定义,这里是将整个文件都复制进数据段,所以前面说这个文件要是人写的话,肯定会疯掉的,太多了,不过幸好我们可以用脚本自动生成该文件。
接下来我们又提供了两个脚本用来生成source.h source.c 文件,这样的话在C语言中引用该文件就能使用资源了,首先看一下这两个脚本:
script_source_head.py
def head(config):
print "#ifndef SOURCE_H"
print "#define SOURCE_H"
for c in config:
print "#define SOURCE_IMAGE_" + (c[1]).split(".")[0].upper(),c[0]
print "void *get_source(int sourceID);"
print "int *get_source_length(int sourceID);"
print "#endif"
def read_config():
f = open('configure')
config = []
for line in f:
c = line.split()
config.append (c)
return config
if __name__ == '__main__':
config = read_config()
head(config)
和script_source.py
import os
def source(config):
print "#include \"source.h\""
for c in config:
print "extern void *source"+c[0]+";"
i = 0
print "static void *Image[] = {"
for c in config:
print "\t&source"+c[0],
i = i + 1
if (i != len(config)):
print ","
else:
print
print "};"
i = 0
print "static void *ImageLength[] = {"
for c in config:
statinfo = os.stat(c[1])
print "\t%s" %(statinfo.st_size),
i = i + 1
if (i != len(config)):
print ","
else:
print
print "};"
print '''void *
get_source(int sourceID)
{
return Image[sourceID];
}
int *
get_source_length(int sourceID)
{
return ImageLength[sourceID];
}'''
def read_config():
f = open('configure')
config = []
for line in f:
c = line.split()
config.append (c)
return config
if __name__ == '__main__':
config = read_config()
source(config)
执行下面的命令:
python script_source_head.py>source.h
python script_source.py >source.c
接下来我们看一下生成的文件:
source.h
#ifndef SOURCE_H #define SOURCE_H #define SOURCE_IMAGE_ROOT 0 #define SOURCE_IMAGE_COLLECTION 1 void *get_source(int sourceID); int *get_source_length(int sourceID); #endif
source.c
#include "source.h" extern void *source0; extern void *source1; static void *Image[] = { &source0 , &source1 }; static void *ImageLength[] = { 546 , 470 }; void * get_source(int sourceID) { return Image[sourceID]; } int * get_source_length(int sourceID) { return ImageLength[sourceID]; }
好了到此我们已经实现了资源,用函数get_source可以获得资源的首地址,用函数get_source_length可以获得资源的大小。
下面我们研究一下怎么使用,请看下面的一个测试程序:
#include "stdio.h" #include <gtk/gtk.h> #define WINDOW_WIDTH 400 #define WINDOW_HEIGHT 300 #include "source.h" static void init_tree_store ( GtkTreeModel *model); GtkWidget *create_index_tree (gpointer data); GdkPixbuf * load_icon(int ID) { g_print ("%d\n",ID); GdkPixbuf *icon = NULL; GMemoryInputStream *stream; stream = g_memory_input_stream_new_from_data(get_source(ID), get_source_length(ID), NULL); icon = gdk_pixbuf_new_from_stream (stream, NULL, NULL); return icon; } int main (int argc, char **argv) { gtk_init (&argc, &argv); GtkWidget *window; window = gtk_window_new (GTK_WINDOW_TOPLEVEL); gtk_window_set_title (GTK_WINDOW(window), "资源例子"); gtk_window_set_position (GTK_WINDOW(window), GTK_WIN_POS_CENTER_ALWAYS); gtk_widget_set_size_request (GTK_WIDGET(window), WINDOW_WIDTH, WINDOW_HEIGHT); gtk_container_add (GTK_CONTAINER(window), create_index_tree(0)); /*信号*/ g_signal_connect (G_OBJECT(window), "delete_event", G_CALLBACK(gtk_main_quit), NULL); if (!gtk_widget_get_visible (window)) gtk_widget_show_all (window); else gtk_widget_destroy (window); gtk_main (); return 0; } GtkWidget * create_index_tree (gpointer data) { GtkTreeViewColumn *col; GtkCellRenderer *renderer; GtkWidget *view; GtkTreeModel *model; GtkTreeStore *treestore; view = gtk_tree_view_new (); /*去掉标题栏*/ gtk_tree_view_set_headers_visible (view, FALSE); gtk_tree_view_set_enable_tree_lines (view, TRUE); gtk_tree_view_set_enable_search (view, FALSE); col = gtk_tree_view_column_new (); gtk_tree_view_append_column (GTK_TREE_VIEW(view), col); renderer = gtk_cell_renderer_pixbuf_new (); gtk_tree_view_column_pack_start (col, renderer, FALSE); gtk_tree_view_column_add_attribute (col, renderer, "pixbuf", 0); renderer = gtk_cell_renderer_text_new (); gtk_tree_view_column_pack_start (col, renderer, FALSE); gtk_tree_view_column_add_attribute (col, renderer, "text", 1); treestore = gtk_tree_store_new (3, GDK_TYPE_PIXBUF, G_TYPE_STRING, G_TYPE_POINTER); init_tree_store (treestore); model = GTK_TREE_MODEL (treestore); gtk_tree_view_set_model (GTK_TREE_VIEW(view), model); g_object_unref (model); gtk_tree_view_expand_all (view); return view; } static void init_tree_store ( GtkTreeModel *model) { GtkTreeStore *treestore = GTK_TREE_STORE (model); GdkPixbuf *icon = NULL; GtkTreeIter *parentiter; GtkTreeIter child; g_print ("test\n"); icon = load_icon (SOURCE_IMAGE_ROOT); gtk_tree_store_append (treestore, &child, NULL); gtk_tree_store_set (treestore, &child, 0, icon, 1,"测试1", -1); icon = load_icon (SOURCE_IMAGE_COLLECTION); gtk_tree_store_append (treestore, &child, NULL); gtk_tree_store_set (treestore, &child, 0, icon, 1,"测试2", -1); }
Makefile为:
all: nasm -f elf -o source_asm.o source.asm gcc -c -o source.o source.c gcc -c -o main.o main.c `pkg-config --cflags --libs gtk+-3.0` gcc -o main main.o source.o source_asm.o `pkg-config --cflags --libs gtk+-3.0`
程序运行的截图如下:
2.方案2:基于GCC内嵌汇编的方法
方案1的优点是可以自己选择自己喜好的汇编器,但是缺点是如果用autotools来组织工程的话很难将汇编器加到项目中。使用GCC内嵌汇编的方法可以解决这个问题,但是GCC支持的汇编是AT&T格式的汇编,不是Intel的汇编,对于初学者来说着很难理解其复杂的格式。方案的具体实施可以自己实验一下,这里不具体研究了。
3.方案3:基于C语言的方法
上述两个方法之所以用汇编来定义数据,不用c语言定义数据,这是因为C语言中没有相关的数据类型来定义该数据段,但也不是不能定义,可以定一个全局的字符数组,并对其初始化,这样就和汇编中定义的数据段是一样的。但是理解起来有点别扭,因为字符数组中存的其实是二进制数据。