[转]DELPHI中千万别直接使用CreateThread ,建议使用BeginThread

来源:转载

以前在写个别程序的时候老是喜欢使用纯API编程。
在C++中无大问题,可是到了DELPHI中情况就不一样了。
当你用 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内存泄漏。


 


分享给朋友:
您可能感兴趣的文章:
随机阅读: