我一直認為Delphi功能與C++相比毫不遜色,提供了豐富的控制項和類、全部API以及嵌入的彙編。最近小弟在把C版的Huffman壓縮改用Delphi寫時,順便“研究”了一下Delphi的位操作和嵌入式彙編,利用嵌入彙編我們可以得到高效的程式碼,完成一些Delphi沒有提供的底層功能。借貴報一方寶地與大家分享我的“研究”。
1.Delphi的位操作
每個學習C的朋友都會被告之C是“中級語言”,其位操作非常方便,而Pascal之流只適用於教學。但是Delphi中提供了一組位操作,可別以過去對Pascal的態度看Delphi。
Delphi中的AND、OR、NOT可不僅僅只對邏輯運算式有作用,它們還可以運算元;
AND:按位與,如:1 AND 2其結果為0
OR:按位或,如:1 OR 2其結果為3
Not:按位取反:如Not 1其結果對於有符號數是-2,對於無符號數是65534
另外,還有按位異或XOR:如:1 XOR 2結果為3
Delphi提供了SHL和SHR進行移位左移和右移:
例如:2 SHR1表示2按位右移一位結果為1。
既然有位的操作就一定涉及到數的類型:是有符號數(頭一位用0和1表示正負)還是無符號數。
Delphi中:Shortint(8位)、Smallint(16位)、Longint(32位)、Integer(32位)、Int64(64位)是有符號數;而Byte(8位)、Word(16位)、Longword(32位)是無符號數。它們之間可以像C一樣強制轉換。例如:Smallint類型的-1轉換成Word類型就是65535。轉換方法是Word(-1)。
怎樣,夠全吧^_^!什麼還不夠……!?Delphi還有一招,接招吧……
2. Delphi的嵌入式彙編
Delphi中提供了幾乎全部常用彙編指令的支援:MOV、JE、JMP、CMP、SHL、SHR、SAL、SAR、POP、PUSH、HLT……自己去查吧。至於INT也能識別,不過非法操作或死機可別找我(在最早的Windows95中用Delphi 3似乎可以正確運行中斷,但Windows 95 OEM、Windows 98就不對了,大概是16位模組的問題,還搞不清楚)。
* 嵌入式彙編的格式
Delphi是使用ASM……END來標誌彙編語句
如:ASM
mov al,1
mov bl,al
END;
* 可操作的寄存器
Delphi可用彙編管理以下寄存器:
32位寄存器 EAX EBX ECX EDX ESP EBP ESI EDI
16位寄存器 AX BX CX DX SP BP SI DI
8位寄存器 AL BL CL DL AH BH CH DH
16位段寄存器CS DS SS ES
以及副處理器寄存器堆棧 ST
* 使用彙編前的工作
教彙編的老師一再強調使用彙編要儲存寄存器現場(儲存使用前的寄存器狀態,使用Push壓棧和Pop從棧中彈出),不過這一切對於Delphi的嵌入式彙編是沒有必要的(除非你自己要使用Push和Pop),因為Delphi已經幫你做了,不必擔心會使資料丟掉。
* Delphi嵌入式彙編的使用方式
1.在一般函數過程中使用彙編
組譯工具段可以嵌套於其它過程中:如:
procedure TForm1.Button1Click(Sender: TObject);
var i:smallint;
begin
i:=1;
asm
mov ax,i
sal ax,1
mov &i,ax
end;
showmessage(inttostr(i));
end;
這個程式段是把16位的變數I進行左移,然後把結果用Mov &I,ax語句放入I變數所在地址傳回值。最後顯示I 的值是2。
2.獨立的組譯工具段
組譯工具段也可以單獨寫成函數或過程。這就涉及到參數的傳遞與結果的返回。 首先Delphi對於函數的返回有一個約定:
即:整型資料:8位的用AL返回,16位的用AX返回,32位的用EAX返回; 實型:用ST(0)返回
指標:用EAX返回
長字串:用EAX返回其所在地址
變數:可用@Result返回
例如:一個用彙編的求和函數
function _Sum(X, Y: Integer): Integer;
asm
MOV EAX,X //把32位的數放入EAX
ADD EAX,Y //進行加法運算
MOV @Result,EAX //返回X+Y
end;
一個把字元轉化為大寫的函數例子
function _UpCase( ch : Char ) : Char;
asm
CMP AL,'a'
JB @@exit
CMP AL,'z'
JA @@exit
SUB AL,'a' -'A'
@@exit:
end;
值得注意的是第二個例子中,沒有象第一個那樣把參數用語句放到寄存器中,這是由於Delphi中預設的把Byte(Char)類型放在AL中,不需要用Mov語句,但是這種函數不能是類的成員,否則結果會出錯。
3.在彙編中調用其它過程
彙編語句中的Call語句,可以用於調用其它過程,既可以是其它組譯工具段也可以是Delphi中的標準流程:
例如:假設建立一個表單並在上面加了一個按鈕,在Click事件中寫入以下代碼
procedure TForm1.Button1Click(Sender: TObject);
begin
showmessage(`ok');
end;
再寫一個過程_X
function TForm1._x(var i:smallint):integer;
asm
call button1click
end;
執行_x的結果就可以顯示訊息框。
* 彙編的調試
編好了程式,沒錯,還好,如果有錯,就得用到調試工具:如變數的跟蹤、斷點、堆棧查看……對於彙編還可以用View菜單的Debug Windows的CPU視窗跟蹤。
附代碼:
program Project1;
{$APPTYPE CONSOLE}
uses
SysUtils;
var
a,b,c,d:Word;
BYTE1:Byte;
word1:Word;
int1:Integer;
(*------------------------------------------------------------------------*)
function add2value(x1:Integer;x2:Integer):Integer ; (*用彙編寫的函數*)
asm
mov eax,x1;
add eax,x2;
mov @result,eax; (* 傳回值為 @result *)
end;
(*------------------------------------------------------------------------*)
begin
try
{ TODO -oUser -cConsole Main : Insert code here }
asm
mov a,'A';
mov b,'B';
mov c,'C';
mov d,'D';
end;
Writeln(chr(a) + chr(b) + chr(c) + chr(d));
BYTE1 := $FF;
Writeln(BYTE1 and $0F);
BYTE1 := $0F;
Writeln(BYTE1 or $F0);
word1 := 1;
asm
mov ax,word1
sal ax,1 (*左移1位*)
mov &word1,ax
end;
Writeln(' i = ' + IntToStr(word1));
int1 := add2value(12,32);
Writeln(int1);
except
on E: Exception do
Writeln(E.ClassName, ': ', E.Message);
end;
readln;
end.