1. 程式人生 > >用C語言開發檢視HEX位元組碼的工具--看程式如何進化

用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的設計理念是,在程式碼的進化中成長,象昆蟲那樣,該蛻皮時才蛻皮。
  • 使終記住,改進是為了更簡單。更易於後期維護。在一定程度上,封裝是一把雙刃劍,要用,但要慎用。軟體包裹的層數越多,外表上看上去會越簡潔,但不意味著就越簡明