【问题标题】:How do I pass the main window's handle to a new thread using win32 API only?如何仅使用 win32 API 将主窗口的句柄传递给新线程?
【发布时间】:2022-01-02 21:08:10
【问题描述】:

我已经读到我需要将 HWND 放在堆上。对吗?

我需要这个来从用户输入中读取值。

提前谢谢你!

VOID MakeThread(HWND hWnd)
{

    HWND* h = new HWND(hWnd);
    HANDLE hThread = (HANDLE)_beginthreadex(nullptr, 0, WorkerThread, h, 0, nullptr);

    if (hThread != nullptr) {
        WaitForSingleObject(hThread, INFINITE);
        CloseHandle(hThread);
    }
    delete h;
}

unsigned int __stdcall WorkerThread(VOID* h)
{
char num[71] = { 0 };
GetDlgItemTextA((*(HWND*)h), 2001, num, 70);

//Get number from edit box 2001
//Do work with the above-mentioned number

    return 0;
}

【问题讨论】:

  • “我已经读到我需要将 HWND 放在堆上”这意味着什么?你在哪里读到的?
  • 这在整个地方都在尖叫XY Problem。回答字面问题:HWND 是一个可以按原样传递给线程过程的值。这样做没有多大意义,而且你使用它的方式是完全不安全的。从创建它的线程以外的线程访问窗口需要同步(您没有提供)。读起来好像您相信这是您问题的解决方案,因为您还没有理解输入在 Windows 中的工作原理。
  • 这也实现了一个惊人的常见反模式:创建一个线程,然后在线程上进行同步等待。您可以根本不完全创建线程:行为将是相同的。您应该首先认真考虑了解 Windows API。 Petzold 的Programming Windows 是标准学习材料。
  • @IInspectable 同步由 SendMessage 完成,因此至少从同步的角度来看,这段代码是可以的。但是,它确实会死锁,因为新线程正在向原始线程发送消息,而原始线程无法接收到消息,因为它正在等待新线程退出。
  • @ray 我之前的评论是指取消引用h,由于无限同步等待(正如您所注意到的,这会导致死锁),这恰好是安全的。关于同步的说明旨在促进安全代码,即使不完全准确。窗口管理器提供了一些级别的同步。我把它留给你write about the lesser known subtleties

标签: c++ windows winapi


【解决方案1】:

我已经读到我需要将 HWND 放在堆上。对吗?

不是真的。假设HWND 在创建线程期间保持有效(如果它是应用程序的主窗口,那么这是一个合理的假设),并且 MakeThread 函数将不会返回,直到该线程已完成(就像您的代码中的情况一样),那么您可以在对_beginthreadex 的调用中将其HWND hWnd 参数的地址作为arglist 参数提供。

您不必在堆上创建该窗口句柄的副本。这是您的 MakeThread 函数的简化版本:

VOID MakeThread(HWND hWnd)
{
    stringstream stream;
    stream << &hWnd << "\n" << hWnd;
    std::string s = stream.str();
    MessageBoxA(hWnd, s.c_str(), "Caller: hWnd?", 0);
    HANDLE hThread = (HANDLE)_beginthreadex(nullptr, 0, WorkerThread, &hWnd, 0, nullptr);
    if (hThread != nullptr) {
        WaitForSingleObject(hThread, INFINITE);
        CloseHandle(hThread);
    }
}

但是,如果您的MakeThread 函数要返回而不等待创建的线程完成,那么传递的地址将变得无效(在某些时候)。但是,在这种情况下,您必须对代码进行其他更改,以便跟踪创建的线程句柄。

【讨论】:

  • 我在你回答后更改了一些代码。
  • @David_M 我注意到了。但是您的更改并没有真正使我的答案无效。
  • 您有什么理由不只是传递HWND 值吗?它是指针大小的,因此很容易适合线程过程的签名。完成后,您现在可以删除等待。没有人依赖地址&amp;hWnd,因此不需要同步。
  • @IInspectable 嗯。在 OP 的 原始 代码中,线程函数正在取消引用给定的指针:stream &lt;&lt; (HWND)h &lt;&lt; char(13) &lt;&lt; char(10) &lt;&lt; (*(HWND*)h);,因此只需传递 hWnd 对象就会导致(以及 _beginthreadex 调用)失败。
  • 我的回答只是解决了传递给线程函数的地址是否需要在堆上的问题——它不需要。但是你的问题本身是完全有效和相关的。
【解决方案2】:

HWND 已经是指针类型(指向全局系统资源)。您可以按原样传递它,根本不需要用new 包装它:

void MakeThread(HWND hWnd)
{
    HANDLE hThread = (HANDLE)_beginthreadex(nullptr, 0, WorkerThread, hWnd, 0, nullptr);
    if (hThread != nullptr) {
        WaitForSingleObject(hThread, INFINITE);
        CloseHandle(hThread);
    }
}

unsigned int __stdcall WorkerThread(void* h)
{
    char num[71] = { 0 };
    GetDlgItemTextA((HWND)h, 2001, num, 70);

    //Get number from edit box 2001
    //Do work with the above-mentioned number

    return 0;
}

话虽如此,创建一个线程只是为了立即等待其终止完全是浪费线程。您也可以直接调用目标函数,最终结果将完全相同,只是没有额外的开销:

void DontMakeThread(HWND hWnd)
{
    WorkerThread(hWnd);
}

【讨论】:

    【解决方案3】:

    一旦您使用new HWND(hWnd),您的对象将默认分配在堆上,而不是堆栈上。因此,您可以共享它,而不会有因堆栈展开而丢失对象的风险。

    另一点是你启动线程,然后等待完成。作为副作用,您甚至可以使用堆栈分配的数据来传递给线程,因为堆栈一直存在,直到线程退出(确切地说,WaitForSingleObject 行)。

    处理用户输入通常与堆/堆栈分配无关。您能否在该部分重新表述您的问题或创建另一个问题?

    【讨论】:

    • 这没有意义。 HWND 是一个值,就像 42 是一个值一样。当HWND 类型的变量超出范围时,什么都没有发生。窗口的生命周期与存储该窗口句柄的变量的生命周期没有任何关系。
    • 我编辑了我的帖子以使我的问题更清楚。
    • @IInspectable 给定HWND* h = new HWND(hWnd); 语句意味着我们有一个指针h,而不是一个值本身。因此,您分配了一个指针(将数据放在堆上,除非您使用自定义分配器),如果您在不释放指针的情况下超出范围,则会出现内存泄漏。因此,保存您的 42 值的那块堆内存被标记为“正在使用”,并且不能再被应用程序使用。
    • 问题是关于生命周期。泄漏的堆分配具有无限的生命周期,因此不会出现内存安全问题。关键是new HWND(hWnd) 超出了任何理由。从字面上看,永远没有理由这样做。它浪费了周期,浪费了内存,而且不会有任何好处。我的意思是,跟踪指向指针大小对象的指针,以便您可以跨线程传递它?那么为什么不简单地传递指针大小的对象呢?
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2013-10-23
    • 1970-01-01
    • 1970-01-01
    • 2010-09-05
    • 2013-01-11
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多