以前在写个别程序的时候老是喜欢使用纯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内存泄漏。
http://www.cnblogs.com/wxy8/archive/2011/06/21/2085661.html
|
请发表评论