用C語言開發檢視HEX位元組碼的工具--看程式如何進化
昨日所作的檢視HEX位元組碼程式,雖然不完善,但程式碼量的確很小。其中核心程式碼不過十行上下,其餘還都是例行公事,如開啟檔案,檢查輸入命令並跳轉執行的。
現在,我再增加數行程式碼,使之在介面和功能上接近UltraEdit或Notepad++的顯示,甚至更強!對於4GB的大檔案(如電影、視訊、光碟ISO映像檔案等) 都可秒開,並跳轉到任意位置檢視,且並未多佔記憶體!
可能嗎?當然,不過是命令列程式最簡單的技巧了。昨日所作程式(https://blog.csdn.net/shaoyubin999/article/details/82950806 )就能檢視大檔案,其實,所以謂開啟檔案,不過是指標指向它,並未調入記憶體!當需要顯示哪部分,再將檔案指標偏到那裡,再讀入少量內容即可。
先看看操作和功能
使用手冊
任何軟體都應有一個使用說明手冊或幫助文件。Unix/Linux風格的手冊通常以MarkDown格式和man格式寫成,其實就是TXT文字。我們極力不推薦使用DOC格式!
這個小軟體只需簡單操作說明即可。文字(bin2hexReadme.txt)如下:
bin2hex V1.0
一個二進位制檔案檢視程式
http://www.wtclab.net 2018 Copyleft(L)
功能:
檢視二進位制檔案的內容,十六進位制HEX數和對應的可顯示字元表示。
檢視當前鍵盤輸入鍵的ASCII碼。例如鍵入 ? 顯示 ASCII為63
command $ ? (ASCII 63)
用法:
在命令提示符$下,
(1)向上翻頁: 用上箭頭↑、減號鍵 - 、[ 都可
(2)向下翻頁: 用下箭頭↓、加號(等號)=、] 、空格鍵Space、回車都可。
(3)回到檔案頭部:h鍵
(4)到檔案結束部:e鍵
(5)從任意位置檢視:j鍵,然後輸入檢視位置的十六進位制地址
(6)進入安靜模式:n鍵
(7)退出安靜模式,回到普通命令模式:ESC鍵
(8)退出: q鍵或 Ctrl+c
(9)是否在字串中顯示ASCII值高於127的,如漢字等? 用 p 鍵來回切換。
(10)顯示本幫助:用 ? 鍵
(11)改變顯示介面顏色:k 黑底白字 w 白背景黑字 b 藍底白字 y 黑底黃字 g 黑底綠字
示例 command $ ? (ASCII 63)【這裡顯示上次鍵入的鍵及鍵值】
command $
使用介面
接下來,看看使用介面,為對比,我也以UltraEdit和Notepad++開啟同樣的二進位制檔案(注意,太大的檔案這兩款編輯器都打不開,所以先用小檔案測試,這兩款軟體開啟50M以內的檔案都還行)。
找一個任意檔案,這裡我還是用123.rar作測試檔案。
bin2hex 123.rar
結果:
UltraEdit的開啟結果:
Notepad++的結果:
對比一下三個軟體在處理位元組串ASCII顯示時的不同,由於ASCII碼值在32到127內是可顯示的,127以上通常用作不同編碼模式的字元,如各種編碼模式的下漢字,所以只能顯示成“亂碼”了。
換膚
從對比上看,我的軟體顯示也不錯。我的軟體輕易換膚
命令列視窗顏色是可變換的,在windows下用color命令即可,只是少有人用罷。我在軟體中集成了一些色彩,作為控制示例。如,鍵入k,w,b,y,g等可改變顯示介面顏色:
- k 黑底白字
- w 白背景黑字
- b 藍底白字
- y 黑底黃字
- g 黑底綠字
幫助系統
隨時鍵入 ?即可見幫助。幫助也就是打出文字 bin2hexReadme.txt 。
退出
命令列給出了提示,鍵入q
則退出, 這也是大多數命令列程式通用的做法(如MATLAB 支援Exit和Quit退出)。 為了相容使用者習慣,我還預設用Ctrl+c
也可退出。
導航
我設定了多種導航(上下翻頁)鍵可用,都是常用軟體所預設的,如以輸入法類似的翻頁模式:加減號鍵、左右方括號鍵、又如用上下箭頭翻頁鍵。特別地,以空格鍵和回車都作為了向下翻頁,這也符合通常使用者習慣。
注意,軟體操作設計最要不得的就是標新立異,符合常用預設操作,則軟體的免學習的。
我用了h鍵回到檔案頭,h是home的意思。用e鍵直接到檔案尾,e是end的意思,當然還可以設定其他任何導航方式。這裡設定只是為了示例在程式碼中如何實現。
用j (Jump)則可輸入任意想檢視的檔案位元組地址。如j2345則跳到0x00002345的位置,如圖:
特有功能
如果嫌字串中顯示亂碼漢字不美,也可選擇純ASCII模式。鍵入p (代表 pure)即可,如圖:
p 是開關鍵, 再次鍵入 p 則回到常規顯示。
軟體每次完成任務後,都在下方提示命令輸入,順便也將上次鍵入的字元及其ASCII碼顯示出來。因此,軟體也可當一個簡單的ASCII碼查尋器用。提示命令輸入也可MATLAB、Python、Julia、MySQL、Gnuplot等等軟體通行的做法。
UNIX和LINUX提倡 沉默是金。如果嫌總是發現命令提示,也可進入安靜模式:只要鍵入n即可。用ESC即退回命令模式。進入安靜模式前,還作了提示,如何退回命令模式。
安靜模式下一切命令均可用,如導航,跳轉j,換膚(k,w,b,y,g),顯示開關p, 幫助?、退出q等等。程式中,新加命令無非是加條件切換操作罷了。程式設計不難。
安靜模式的介面極為純淨。
開啟巨大檔案測試
我開啟一個ISO檔案。
可用j
命令瞬間跳到任意位置。
程式碼共182行
以上功能實現的程式碼卻不多。保持簡潔,才是王道。新版的 bin2hex.c
含註釋共182行。尚可再簡。用tcc, gcc或 VC 6.0編譯都通過。TCC編譯指令是:
tcc bin2hex.c -o bin2hex.exe
團隊同學可用JAVA或其他語言重寫之。
【bin2hex.c】
//bin2hex.c
#include<stdio.h>
#include<stdlib.h>
#include<conio.h>
int main(int argc, char *argv[])
{
unsigned char c,ch[17];//記錄單字元,ch記錄一行位元組內容
const int len=16*16;//每次輸出長度16行,每行16位元組
int i,k,j;//記錄長度,i迴圈變數,k用於ch[k]的腳標,j迴圈變數
int commandchar=0;//命令字
long addr;//地址偏移量
int nomessages=0;//為1則啟用安靜模式
int pure=0;//pure=1時位元組串中不顯示亂碼漢字(即高於127或低於32的ASCII碼顯示為句點)
long tmpint;//記錄檔案尾位置備用
FILE *fp;
if(argc<2)
{
printf("Show HEX code of anyfile 2018 WTCLAB.NET CopyLeft (L)\n");
printf("Usage: bin2hex anyfilename");
return 0;
}
system("chcp 936");//windows中文碼頁
fp=fopen(argv[1],"rb");
if(fp==NULL)
{
printf("file %s not found\n",argv[1]);
return 0;
}
system("color f0"); //改變視窗預設為白底黑字
while(1) //列印二進資料內容表
{
if(nomessages==0)//輸出標準模式訊息
{
printf("\n Address | 0 1 2 3 4 5 6 7 8 9 a b c d e f |");
if(pure==1){printf("Dump [pure display] \n");}
else{printf(" Dump \n");}
printf("---------|------------------------------------------------|");
printf("-------------------");
}
for(i=0; i<len; i++)//輸出len個位元組,16個為一行
{
fread(&c,1,1,fp);
if(feof(fp)!=0)
{
tmpint=16-ftell(fp)%16;//求餘對齊
for(j=0; j<3*tmpint; j++)putc(0x20, stdout); //3個空格
ch[16-tmpint]='\0';//字串到【16-tmpint】位置結束
printf("| %s",ch);//輸出”亂碼字串“
break; //讀到檔案尾就結束
}
if(i%16 == 0)
{
k=0;
printf("\n %08x|",ftell(fp)-1); //換行輸出地址
}
printf("%02x ",c);//按16進位制輸出讀入的位元組值
c=(c<32 |c==0xff)? 46: c;//46為ASCII碼句點,將非列印碼處理為句點顯示
c=(pure==1 & c>127)? 46:c; //這樣就不顯示漢字,改顯46
ch[k]=c;
k++;//記錄1行字元以備輸出
if(k==16)//輸出一行對應的ASCII串,if((i+1)%16 == 0)
{
ch[16]='\0';
printf("| %s",ch);//輸出”亂碼字串“
}
}
if(nomessages==0)//輸出標準模式訊息
{
printf("\n[tips]:prev/next:[\xa1\xfc/- \xa1\xfd/=] \
h(home) e(end) j(jump) q(quit) \n");//輸出命令提示,\xa1\xfc為上箭頭
printf(" n(silent mode) w,k,g,b,y(disp. color) ESC(command mode)\
\n p(pure ASCII disp.) ?(for help) ");
if(commandchar<32){
c=32; //處理不能列印的ASCII
}
else{
c=commandchar;
}
printf("\n command $ %c (ASCII %d)",c,commandchar);
//輸出鍵盤訊息字元和對應的ASCII值
printf("\n command $ ");
}//輸出安靜模式訊息
commandchar=getch();//接收控制命令並執行
if(commandchar=='q' |commandchar=='\x3' )break; //退出: q鍵或 Ctrl+c
if(commandchar=='[' | commandchar==72 |commandchar==45)//鍵盤[或上箭頭或減號
{
if(ftell(fp)>(1*16)*16)
{
tmpint=ftell(fp);
tmpint=tmpint-16*(tmpint/16);//求餘對齊
fseek(fp,-(2*16)*16-tmpint,1);//指標回捲32行,到上一屏位置
}
else
{
//如果到達檔案頭則不動作
rewind(fp);//指標迴文件頭
}
}
else if(commandchar==']' | commandchar==80 | commandchar==32\
|commandchar==13 |commandchar==61)
{
//鍵盤]或下箭頭80或空格32或回車13或加號(等號)61
//直接到下一屏位置。即fseek(fp,0,1);但到檔案結束處要處理對齊
tmpint=ftell(fp);
tmpint=tmpint-16*(tmpint/16);//求餘對齊
fseek(fp,-tmpint,1);//對齊
}
else if(commandchar=='h')//鍵盤s:迴文件頭
{
system("cls");
rewind(fp);
}
else if(commandchar=='e')//鍵盤e:至檔案尾
{
fseek(fp,0,2);//移到文尾
tmpint=ftell(fp);//讀尾部指標
tmpint=tmpint-16*(tmpint/16);//求餘,對齊
fseek(fp,-15*16-tmpint,2);//到檔案尾,回溯16行輸出
}
else if(commandchar=='j')//鍵盤j:輸入開始行地址
{
printf("\naddress(HEX)? Example:0x1024 > 0x");
scanf("%x",&addr);
addr=16*(addr/16);//從16的整數倍開始
fseek(fp,addr,0);
}
else if(commandchar=='n')//鍵盤n:靜默模式:不顯示提示
{
nomessages=1;
printf("\n Silence Mode now!\nESC return to normal mode,Any key to go!");
getch();
}
else if(commandchar==27)//鍵盤ESC:回到顯示提示模式
{
nomessages=0;
fseek(fp,-16*16,1); //顯示不動:回16行重打出來
}
else if(commandchar=='k')//顯示背景色彩:黑底白字
{
system("color 0f");fseek(fp,-16*16,1);
}
else if(commandchar=='w')//顯示背景色彩:白底黑字
{
system("color f0");fseek(fp,-16*16,1);
}
else if(commandchar=='y')//顯示背景色彩:黑底黃字
{
system("color 0e");fseek(fp,-16*16,1);
}
else if(commandchar=='b')//顯示背景色彩:藍底白字
{
system("color 1f");fseek(fp,-16*16,1);
}
else if(commandchar=='g')//顯示背景色彩:黑底綠字
{
system("color 0a");fseek(fp,-16*16,1);
}
else if(commandchar=='?')//顯示幫助
{
system("type bin2hexReadme.txt");
fseek(fp,-16*16,1);
system("pause");
}
else if(commandchar=='p')//顯示字串模式切換
{
pure=(pure!=0)? 0:1; //或pure=(pure+1)%2; //
fseek(fp,-16*16,1); //顯示不動:回16行重打出來
}
else
{
fseek(fp,-16*16,1); //其他鍵:顯示不動:回16行重打出來
}
}
fclose(fp);
system("color 07");
return 0;
}
幫助文件
【bin2hexReadme.txt】
bin2hex V1.0
一個二進位制檔案檢視程式
http://www.wtclab.net 2018 Copyleft(L)
功能:
檢視二進位制檔案的內容,十六進位制HEX數和對應的可顯示字元表示。
檢視當前鍵盤輸入鍵的ASCII碼。例如鍵入 ? 顯示 ASCII為63
command $ ? (ASCII 63)
用法:
在命令提示符$下,
(1)向上翻頁: 用上箭頭↑、減號鍵 - 、[ 都可
(2)向下翻頁: 用下箭頭↓、加號(等號)=、] 、空格鍵Space、回車都可。
(3)回到檔案頭部:h鍵
(4)到檔案結束部:e鍵
(5)從任意位置檢視:j鍵,然後輸入檢視位置的十六進位制地址
(6)進入安靜模式:n鍵
(7)退出安靜模式,回到普通命令模式:ESC鍵
(8)退出: q鍵或 Ctrl+c
(9)是否在字串中顯示ASCII值高於127的,如漢字等? 用 p 鍵來回切換。
(10)顯示本幫助:用 ? 鍵
(11)改變顯示介面顏色:k 黑底白字 w 白背景黑字 b 藍底白字 y 黑底黃字 g 黑底綠字
示例 command $ ? (ASCII 63)【這裡顯示上次鍵入的鍵及鍵值】
command $
原始碼及編譯結果下載點:
已知BUG和不足
程式畢竟是人編的,總是存在不足點,需要不斷改進。程式完成後應做完整測試。目前,作為示例,我故意在測試後未進改程式碼。其實,能發現錯誤,才能有改進的思路。
BUG
- j命令後如果輸入不是十六進位制數字字元,將出錯,你不能期望軟體使用者都依照程式設計師的設計來操作。這一點需要對輸入進行合法性檢查或過濾。
- bin2hex.exe與當前工作路徑不一致時,將導致幫助檔案開啟失敗。這是幫助文件寫在外部的不利處。進一步可用exe檔案路徑檢測方法來解決(在
windows.h
中用GetModuleFileName
函式即可)。但這樣一來就不能跨平臺了,所以不是最佳。
不足
暫列為2點:
- 還沒有快進/快退操作。
- 沒有顯示當前位置的進度百分比。
- 隨著軟體功能增加,需改程序序總的結構以適應。
改進的方向
軟體結構上,還有可改進的地方:
- 程式碼中我用了常規的if-else語句進行輸入控制分支,實際上,這裡用case語句更簡單。
- 輸入命令如果多了,程式碼在結構上就顯冗雜。可考慮將輸入操作做成一個函式程式碼,又將輸出顯示作為另一函式程式碼,在主程式中使用。這屬於程式優化的過程。當程式基本功能完成並不斷增加附加功能和完善時,程式碼量會增大,原來求簡的結構會逐漸不適應,這時就要考慮以封裝(也就是包裝為函式和通過函式做資料介面)來增補結構。所以程式是一個不斷進化的過程,不要在一開始就去考慮所謂“ 完善的” 介面和架構!就象一個小公司,成立之初,重為生存,簡則有效,等成長到一定規模,再考慮擴充套件各種機構。你見過街邊小吃店裡有財務部、人事部?如果真有,就是這家小店的主人不懂人事了。始終注意,小而精巧,是C語言區別於JAVA、C++最著名的特徵。但這不意味著用C碰能做大程式,C的設計理念是,在程式碼的進化中成長,象昆蟲那樣,該蛻皮時才蛻皮。
- 使終記住,改進是為了更簡單。更易於後期維護。在一定程度上,封裝是一把雙刃劍,要用,但要慎用。軟體包裹的層數越多,外表上看上去會越簡潔,但不意味著就越簡明。