標籤:
當你用 DELPHI寫的多線程程式莫名其妙的記憶體錯誤,特別是字串(string)操作;
或者程式無故終止,又沒有任何提示,你需要認真分析可能是你直接使用了CreateThread。
C++的linker可以自己設定運行庫的形式,選擇支援單線程還是多線程模式。
DELPHI是自動判別的,那他是如何自動判別的呢,這就要看看他在System單元提供的函數BeginThread了。 聽說在VC 中也不贊成直接使用沒有保護的
CreateThread ,也要使用加了保護的_BeginThread。
{$IFDEF MSWINDOWS}
function BeginThread(SecurityAttributes: Pointer; StackSize: LongWord;
ThreadFunc: TThreadFunc; Parameter: Pointer; CreationFlags: LongWord;
var ThreadId: LongWord): Integer;
var
P: PThreadRec;
begin
New(P);
P.Func := ThreadFunc;
P.Parameter := Parameter;
IsMultiThread := TRUE;
Result := CreateThread(SecurityAttributes, StackSize, @ThreadWrapper, P,
CreationFlags, ThreadID);
end;
看見了“ IsMultiThread := TRUE;”這句了嗎?
找到他的定義,在全域變數裡:
IsMultiThread: Boolean; { True if more than one thread }
再看看ThreadWrapper:
{$IFDEF MSWINDOWS}
function ThreadWrapper(Parameter: Pointer): Integer; stdcall;
{$ELSE}
function ThreadWrapper(Parameter: Pointer): Pointer; cdecl;
{$ENDIF}
asm
{$IFDEF PC_MAPPED_EXCEPTIONS}
{ Mark the top of the stack with a signature }
PUSH UNWINDFI_TOPOFSTACK
{$ENDIF}
CALL _FpuInit
PUSH EBP
{$IFNDEF PC_MAPPED_EXCEPTIONS}
XOR ECX,ECX
PUSH offset _ExceptionHandler
MOV EDX,FS:[ECX]
PUSH EDX
MOV FS:[ECX],ESP
{$ENDIF}
MOV EAX,Parameter
MOV ECX,[EAX].TThreadRec.Parameter
MOV EDX,[EAX].TThreadRec.Func
PUSH ECX
PUSH EDX
CALL _FreeMem
POP EDX
POP EAX
CALL EDX
{$IFNDEF PC_MAPPED_EXCEPTIONS}
XOR EDX,EDX
POP ECX
MOV FS:[EDX],ECX
POP ECX
{$ENDIF}
POP EBP
{$IFDEF PC_MAPPED_EXCEPTIONS}
{ Ditch our TOS marker }
ADD ESP, 4
{$ENDIF}
end;
這裡DELPHI幫你設定了線程的 SEH 處理函數。
在DELPHI裡,我們應該使用BeginThread,丟掉CreateThread吧。
*****************************************************************************
博主:在實際應用中出現了問題
function GetGuiyue(ABuffer: PArrayByte): Boolean; stdcall; external ‘Guiyue.dll‘;
我調用函數GetGuiyue時出現異常,
BeginThread(nil,0,@GetGuiyue,tempBuffer,0,ThreadID);
原因是BeginThread 訪問函數,與stdcall 介面衝突。所以需要在調用時寫一個引用函數
function ParseGuiyue(ABuffer: PArrayByte): Boolean;
begin
GetGuiyue(ABuffer); //調用DLL 裡的函數
EndThread(0); //函數結束關閉線程
end;
這樣,你就可以放心使用 BeginThread了。
BeginThread(nil,0,@ParseGuiyue,tempBuffer,0,ThreadID); //建立子線程處理解析
但是,也有人提出 BeginThread 使用不安全
把ParseGuiyue作為BeginThread的參數有兩個問題
1.P參數無效(ParseGuiyue會從棧頂擷取,而實際上在EAX中傳遞過來)
2.函數無法正確返回(ParseGuiyue把棧頂的返回地址當成P參數了,而取了下一個不確定的元素作為返回地址)
所以在MyThreadFunc中加EndThread只是讓線程在函數返回前結束執行,並不能解決第一個問題——而這可能會帶來嚴重的錯誤,因為ParseGuiyue裡P參數是一個指向程式碼片段記憶體的地址(ThreadWrapper函數的執行體中某位置)。
另外看起來調用EndThread會造成BeginThread中分配的PThreadRec記憶體流失。
[轉]DELPHI中千萬別直接使用CreateThread ,建議使用BeginThread