【问题标题】:Delphi threading德尔福线程
【发布时间】:2012-09-18 10:43:05
【问题描述】:

我需要在 Delphi 中启动一个线程并为此使用以下代码:

function ThreadFunc(tp: PThreadParams): Integer;
var
    I: Integer;
begin
    OutputDebugString(PChar('ThreadFunc, 1'));
    for I := 0 to 10000 do
    begin
        if (I MOD 100) = 0 then
        begin
                OutputDebugString(PChar('Sample Delphi DLL ' + IntToStr(I)));
        end;

    end;

    Dispose(tp);
end;

procedure RunThread;
var
tp : PThreadParams;
Thread : THandle;
ThreadID : Cardinal;
ExitCode : Cardinal;
begin
    New(tp);

    OutputDebugString(PChar('RunThread, 1'));

    Thread := BeginThread(nil, 0, @ThreadFunc, tp, 0, ThreadID);

    OutputDebugString(PChar('RunThread, 2. ThreadID: ' + IntToStr(ThreadID)));

    WaitForSingleObject(Thread, INFINITE);

    GetExitCodeThread(Thread, ExitCode);

    CloseHandle(Thread);
end;

当我运行 RunThread 时,日志文件包含条目

运行线程,1

RunThread, 2. ThreadID: ...

ThreadFunc 没有日志输出。

我应该如何更改此代码以执行实际的线程函数 (ThreadFunc)?

【问题讨论】:

  • 为什么不使用 TThread?实现线程非常容易。
  • 它是否在“无头”(无 GUI)环境中工作?我需要在 DLL 中启动线程。
  • GolezTrol +1。是的,它确实。如果您想尝试修复您的示例,请尝试对线程函数使用 stdcall 调用约定。
  • 是的,有人想知道为什么你不只使用TThread
  • @GolezTrol 是的,它确实有效,谢谢。请提交此声明作为答案,然后我会将问题标记为已回答。

标签: multithreading delphi delphi-2009


【解决方案1】:

为什么不使用 TThread?使实现线程变得非常容易。 ;-)

【讨论】:

  • TThread 类需要包含一个巨大的 Classes.pas 单元,而后者又需要其他巨大的单元。要创建小型控制台应用程序或服务应用程序,BeginThread 就足够了。这很简单,作者的代码大多是正确的。我怀疑问题不在于线程,而在于 OutputDebugString,这是一个非常晦涩的函数。例如,我不喜欢使用 OutputDebugString。
  • 作者的日志显示“Sample Delphi DLL”。它告诉作者正在编写一个应该很小的 DLL。这就是为什么从 Classes.pas 添加 TThread,从而显着增加 DLL 大小并不是一个好主意。
  • DLL 不需要比可执行文件小,一旦您开始编写实际功能,您也可以使用该单元中的其他类。毕竟,大多数人将 Delphi 用于 RTL&VCL,而不仅仅是用于 Pascal 语法。此外,一个新的 DLL 项目默认包含类,它的发布版本将小于 1MB,即使在最新的 Delphi 版本中也是如此。
  • 感谢您提供的信息。我再次检查了所有内容,包括调用约定,发现作者的代码是正确的,除了未定义的返回值。相反,TThread 会自动处理这些返回值。另一方面,Delphi 编译器在未定义返回时给出警告。所以没有证据表明 TThread 更能抗人为错误 ;-)))
【解决方案2】:

关于输出调试字符串

您的问题可能与 OutputDebugString 以及 Delphi IDE 如何处理它有关。你的线程没有问题。它运行正常。唯一的缺点是 ThreadFunc 必须返回一个值,而在您的情况下它不会。请参阅https://msdn.microsoft.com/en-us/library/windows/desktop/ms686736(v=vs.85).aspx(只返回零 - 最简单的情况)。

要检查 Delphi 是否正确处理 OutputDebugString,请使用替代应用程序来显示调试消息。

退出 Delphi,从https://technet.microsoft.com/en-us/sysinternals/debugview.aspx 的 Windows Sysinternals 获取 DebugView,运行它,并在它运行的同时运行您的应用程序(只要确保 Delphi 没有运行)。

在 DebugView 日志中,您会看到类似的内容:

[8484] RunThread, 1
[8484] RunThread, 2. ThreadID: 8388
[8484] ThreadFunc, 1
[8484] Sample Delphi DLL 0
[8484] Sample Delphi DLL 100
[8484] Sample Delphi DLL 200
<<< 96 lines skipped >>>
[8484] Sample Delphi DLL 9900
[8484] Sample Delphi DLL 10000

OutputDebugString 太晦涩难懂,无法依赖它。只是更简单的方法来确保您的线程正在运行,例如创建文件或发出哔声。因此,想法是,如果 DebugView 没有帮助,只需从您的线程中输出调试字符串并使用其他方法确保线程正在运行。例如,将以下字符串添加到 ThreadFunc 的开头:

MessageBox(0, 'Thread Text', 'Thread Caption', MB_OK);

运行您的应用程序。如果您将看到消息对话框,那么您的线程运行正常。

关于调用约定

调用约定(stdcall vs register)在这里不是问题。从这个意义上说,您的代码是正确的。在 System.pas 中查找 BeginThread 函数的实现(以及 TThreadFunc 的定义,如果存在)。

您没有提及您使用的是什么版本的 Delphi,或者您可能正在使用其他兼容的编译器 - 这可能会影响类型,但这种可能性非常低。 Delphi 10.2 使用 ThreadWrapper 函数将其传递给 Windows API CreateThread()。这个 ThreadWrapper 是按照 Win32 规范的要求使用“stdcall”调用约定定义的,因为它直接传递给运行的 CreateThread()。 ThreadWrapper 又调用您具有注册调用约定的 TThreadFunc。 TThreadFunc 的定义方式如下(简单明了):

TThreadFunc = function(Parameter: Pointer): Integer;

因此,正如上面的代码所示,您的 ThreadFunc 必须与 TThreadFunc 的声明完全匹配。它不必像某些人在 cmets 中所说的那样是“stdcall”。如果您的 Delphi 版本在 System.pas 中没有 TThreadFunc,请查找 BeginThread 的第三个参数所需的类型,以确保一切正确。

如果您将编译器选项“Typed @ operator”打开(默认为关闭),并且如果问题是调用约定不匹配,编译器会给出错误。我建议始终选中“Typed @ operator”选项。或者,在 CreateThread 调用之前添加 {$T+} (或者在文件开头更好)。因此,您将确保没有不匹配。

正如我所解释的,事情表明调用约定在这里不是问题,并且您的代码中的一切都是正确的。如果调用约定不合适,则 ThreadFunc 将一直运行到最后,因为在退出 (Dispose(tp)) 之前不需要任何参数,因此在执行所有 OutputDebugString 调用后会出现访问冲突错误– 在 Dispose(tp) 或从 ThreadFunc 退出时。您会在调试器中看到输出,然后出现访问冲突。由于没有访问冲突 - 调用约定一切正常。

【讨论】:

    猜你喜欢
    • 2014-07-08
    • 1970-01-01
    • 2015-02-08
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-04-19
    • 2022-07-12
    相关资源
    最近更新 更多