LinuxSir.cn,穿越时空的Linuxsir!

 找回密码
 注册
搜索
热搜: shell linux mysql
查看: 1280|回复: 6

所謂資源文件

[复制链接]
发表于 2010-6-4 16:56:21 | 显示全部楼层 |阅读模式
Windows 程序開發會用到資源文件,開發時得準備原始文件,資源腳本,還有資源編譯器,然後開發時得把生成的 .res 文件和中間文件鏈接起來。
    Linux下程序開發時,要在生成的程序中直接包含一個文件。若是用 C/C++ 的話,大多人的做法是用工具轉換成 .h 文件,作為頭文件包含。這是個不錯的方法,缺點是轉換出的 .h 文件往往比原來的文件要大很多,真的是非常的多 。而且這個 .h “資源文件” 不能在其他語言上使用。

    我覺得,更加好的方法是 Windows 程序開發的方法。那麼,資源編譯器,資源腳本語法如何選擇呢?生成的資源文件又如何應用?
    用不着選工具啦。標準工具 GNU binutils 中的 as(GNU assembler as) 就可以。那豈不是說,要用匯編語言來寫資源腳本?是啊,不過只是用到 as 中一些平台無關語法,很簡單的。生成的资源文件在使用上也很简单。

下面給出例子:
    首先是一個資源文件:
echo HelloWorld > t.bin
這樣得到一個 t.bin 文件。不過,這樣產生的文件的末尾是 0xa 換行符,所以
dd bs=1 count=1 conv=notrunc seek=10 if=/dev/zero of=t.bin
把最后的 0xa 改成了 0x0 这样就可以直接在 C 语言中作为字符串来使用。
    然後寫一個資源腳本,也就是匯編語言程序
  1. .section .data
  2. .balign 0x10, 0x0
  3. .global res_tbin
  4. .global res_size_tbin
  5. res_tbin:
  6. .incbin "t.bin"
  7. res_size_tbin:
  8. .long res_size_tbin - res_tbin
复制代码

保存為 res_tbin.s
執行
as res_tbin.s -o res_tbin.o
這樣就得到了資源文件了。
解釋:
.section .data 是為了告訴鏈接器,把以下數據放入 .data 段
.balign 0x10, 0x0 是用來對齊數據的,有兩個參數,第一個參數是對齊的邊界,單位為字節。這個值越小,浪費的空間就越小,但它必須大於對應平台的最小地址單位,比如,如果這個資源文件用在 ARM 平台上,這個值必須是4的倍數,如果在 i386 上,則隨便了。如果為了追求速度,則必須是這個平台上“字”的大小的倍數。在 ARM 上,還是4.在 i386 上,是4,在 amd64 上,則是8。第二個值為填充所用的數據,既然是數據,則用0填充沒錯的。這兩個參數在一般情況下都不需要改動。
跳過一空行,後面的6行么,一個資源就需要這麼6行。
頭兩行是使得兩個標號為鏈接器 ld 可見,相當與聲明了兩個外部可見的全局變量啦,和 C 語言中的聲明沒什麼區別。
接下來, res_tbin: 則是這個變量的定義開始了。
.incbin "t.bin" 則是這個變量的值了,告訴 as 包含原始的文件。這裡,原始文件就是 t.bin
然後緊接res_size_tbin: 又是一個變量,這個變量為這個文件的大小
.long res_size_tbin - res_tbin res_size_tbin 的地址減去這個資源開頭的地址,也就是這個文件的大小了, as 會計算它的,不用我們擔心。開頭的 .long 告訴 as 這裡寫入一個數據。 .long 是個很有用的指示, as 會根據平台自動選擇它的大小。在 i386 上,會產生出一個 4 字節的數據,amd64 上,則是 8 字節。

所以,如果還要一個圖像資源的,比如一個 dog.bmp 文件,就在 t.s 後面再加上
  1. .global res_dogbmp
  2. .global res_size_dogbmp
  3. res_dogbmp:
  4. .incbin "dog.bmp"
  5. res_size_dogbmp:
  6. .long res_size_dogbmp - res_dogbmp
复制代码

接下來是使用這個文件,首先是個 C 語言的例子:
  1. /*author: Kandu(張道遠)*/
  2. #include<stdio.h>
  3. extern char res_tbin[];
  4. extern int res_size_tbin;
  5. int main(void)
  6. {
  7.   printf("the size of res_tbin: %d\n", res_size_tbin);
  8.   printf("the data of it: %s\n", res_tbin);
  9.   return 0;
  10. }
复制代码


extern char res_tbin[];
extern int res_size_tbin;

聲明了兩個外部變量,然後就可以直接使用它了。
這個 C 語言文件編譯後運行:

[~]$ gcc res_tbin.o t.c -o t
[~]$ ./t
the size of res_tbin: 11
the data of it: HelloWorld



另一個最重要的編譯器是 FPC ,雖然只支持 Pascal 語言和不同平台的匯編語言,但它實在是很好用,例子:
  1. {author: Kandu(張道遠)}
  2. program t;
  3. {$link res_tbin.o}
  4. var
  5.   res_tbin:char;external;
  6.   res_size_tbin:integer;external;
  7. begin
  8.   writeln('the size of res_tbin: ',res_size_tbin);
  9.   writeln('the date of it: ',pchar(@res_tbin));
  10. end.
复制代码

{$link res_tbin.o} 編譯指示告訴 FPC 編譯器鏈接資源文件
然後是

  res_tbin:char;external;
  res_size_tbin:integer;external;

和 C 語言的聲明很像吧。

編譯及運行:

[~]$ fpc t.pas
Free Pascal Compiler version 2.4.0 [2010/01/01] for i386
Copyright (c) 1993-2009 by Florian Klaempfl
Target OS: Linux for i386
Compiling t.pas
Linking t
/usr/bin/ld: warning: link.res contains output sections; did you forget -T?
9 lines compiled, 0.2 sec
[~]$ ./t
the size of res_tbin: 11
the data of it: HelloWorld


兩個屏顯結果一模一樣。



--------------------------------------------------------------------------------------------

以上只是演示了字符資源的使用,同理可包含其他的聲音影響圖片文件。

我在為一個文件系統寫作 mkfs 的時候,就把這個文件系統的 BootBlock 用這樣的方式包含入 mkfs 裡面。在一個小系統裡面,也是通過這樣的方法,把所有資源,字庫,圖片直接包含入內核裡面。



--------------------------------------------------------------------------------------------
tips:


  • 如果是 GCC 的話寫好 res_tbin.s 和 t.c 後,直接
    gcc res_tbin.s t.c -o t
    即可生成可執行文件了,不需要
    as res_tbin.s -o res_tbin.o

  • 如果你知道原始文件的確切大小的話,而且這個原始文件不會改動,那麼可以省略
    res_size_資源名的定義和使用;同理,你也可以增加其他的屬性,比如
    res_type_資源名:
    .long 類型值

    來說明這個資源的類型:圖片,聲音,影像等等。
    但所有增加的值必須跟在 res_size_資源名 後面,不然 res_size_資源名 的值會不準確(變大)。

  • 在 C 語言中,聲明是

    extern char res_tbin[];
    extern int res_size_tbin;

    而在 Pascal 語言中,聲明是

    res_tbin:char;external;
    res_size_tbin:integer;external;

    在這裡
    int res_size_tbin;                /* C */

    res_size_tbin:integer;        {Pascal}
    一樣,直接就是聲明了整數。

    char res_tbin[];                        /* C */

    res_tbin:char;                        {Pascal}
    則不同了,在 C 中,它可以被聲明成數組而正常使用, Pascal中被仍然被聲明為整數。這是 C 語言設計上的一個缺陷,C語言內部幾個不統一設計之一。要優美地寫作的話,可以把程序改成
    1. /*author: Kandu(張道遠)*/
    2. #include<stdio.h>
    3. extern char res_tbin;
    4. extern int res_size_tbin;
    5. int main(void)
    6. {
    7.   printf("the size of res_tbin: %d\n", res_size_tbin);
    8.   printf("the data of it: %s\n", &res_tbin);
    9.   return 0;
    10. }
    复制代码
    char res_tbin[] 改成 char res_bin 。然後在 printf 處傳地址。
发表于 2010-6-4 17:28:27 | 显示全部楼层
非常好,可以让开发更高效。
另外echo加-n选项可以让输出内容不具有换行符号。
回复 支持 反对

使用道具 举报

 楼主| 发表于 2010-6-5 09:11:44 | 显示全部楼层
thx
在為 i386 和 amd64 寫程序,我還是比較喜歡用 nasm ,而不是 as 。 as 的好處是跨平台。各個 CPU/MCU 上都好用。 nasm 的好處么,簡潔,強大,優美。

這是剛寫的兩個 nasm 宏,方便資源文件應用:
  1. ;res.inc
  2. ;author: Kandu(張道遠)
  3. %macro InitRes 1
  4.   section .data
  5.   %define WordSize %1
  6.   %if %1=16
  7.     %define d dw
  8.   %elif %1=32
  9.     %define d dd
  10.   %elif %1=64
  11.     %define d dq
  12.   %endif
  13.   bits WordSize
  14.   align WordSize>>3, db 0
  15. %endmacro
  16. %macro res 2
  17.   global res_%2
  18.   global res_size_%2
  19.   res_%2:
  20.   incbin %1
  21.   res_size_%2:
  22.   d res_size_%2 - res_%2
  23. %endmacro
复制代码
建議保存為 res.inc

不多解釋了,和 as 的用法沒啥區別的。

如果有兩個資源文件,一個 dog.bmp 一個 t.bin 應用方法
  1. %include 'res.inc'
  2. InitRes 32
  3. res 't.bin', tbin
  4. res 'dog.bmp', dogbmp
复制代码
保存為 res.asm

開頭的 %include 'res.inc' 是必需的,除非把 res.inc 中的內容直接複製過來。
每個“資源腳本” 的開頭,必須 InitRes  帶參數,參數為 “字” 的大小的倍數 ARM 是 32, i386 是 32, amd64 是 64 等等。

然後有幾個資源就用幾個 res 宏。它 帶兩參數,第一個參數是文件名,第二個參數是定義用的變量名。
res宏會把它倆擴展成 res_變量名res_size_變量名 變量。

若採用 elf 格式的話,編譯方法:
nasm -f elf -o res.o res.asm
然後用已有的示例代碼就可以使用 res.o 中的數據。

-------------------------------------------------------------------------------------------

as 用宏
  1. #res.inc
  2. #author: Kandu(張道遠)
  3. .macro InitRes
  4.   .section .data
  5.   .balign 0x10, 0x0
  6. .endm
  7. .macro res filename label
  8.   .global res_\label
  9.   .global res_size_\label
  10.   res_\label:
  11.   .incbin "\filename"
  12.   res_size_\label:
  13.   .long res_size_\label - res_\label
  14. .endm
复制代码

用法:
在 res.s 中
  1. .include "res.inc"
  2. InitRes
  3. res t.bin tbin
  4. res dog.bmp dogbmp
复制代码
as t.s -o res.o
即可產生同樣資源文件
-------------------------------------------------------------------------------------------

因為 as 用 .long 自動根據平台產生相應大小的變量,所以不需要向 nasm 那樣,InitRes 宏不需要額外的參數。
在 nasm 宏 InitRes 中,也對 接受的值進行檢查,如果不合法,會給出提示。
回复 支持 反对

使用道具 举报

发表于 2010-6-5 10:24:22 | 显示全部楼层
用nasm可能不太好整合进autotools。
回复 支持 反对

使用道具 举报

 楼主| 发表于 2010-6-5 10:31:09 | 显示全部楼层
後來在帖子中又加了 as 宏
回复 支持 反对

使用道具 举报

发表于 2010-6-5 11:47:05 | 显示全部楼层
Post by kandu;2094488
Linux下程序開發時,要在生成的程序中直接包含一個文件。若是用 C/C++ 的話,大多人的做法是用工具轉換成 .h 文件,作為頭文件包含。這是個不錯的方法,缺點是轉換出的 .h 文件往往比原來的文件要大很多,真的是非常的多 。而且這個 .h “資源文件” 不能在其他語言上使用。

这种说法有些搞笑。
.h 文件大,是因为要用文本格式保存数据,编译后,数据该多大还多大。
放 .c .cpp 中编译成 .o,想怎么用怎么用。

两种风格差异本就是 闭源 开源 态度差异。
开源世界本就没必要把图像声音等数据写死在程序中,不信您掰指头数数看有几个常用软件包这么做的。
闭源世界那么做,至少让对资源动心的家伙要绕绕弯才能得到资源。
回复 支持 反对

使用道具 举报

发表于 2010-6-5 17:30:14 | 显示全部楼层
人家给了一种方法,仅仅通过一个简单的.s文件就可以节省一个.h文件,并且保留了原来的portable的bmp文件。这个是人家文章的主题思想。
你非要拐到什么开源,闭源上去,请问你是怎么样思考的?
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 注册

本版积分规则

快速回复 返回顶部 返回列表