【问题标题】:Is it possible to create a message loop without creating a window in C++是否可以在不使用 C++ 创建窗口的情况下创建消息循环
【发布时间】:2019-01-27 07:17:48
【问题描述】:

我正在使用一些蓝牙库,并且对于一些蓝牙回调,有必要使用 Windows 消息循环。但根据我的要求,我需要创建一个没有任何 GUI 的普通 C++ 程序。是否可以创建没有窗口的消息循环?

main(){
    Discovery disc;
    disc.startDiscovery();
}


Discovery::startDiscovery(){
  __hook(&CallBackFromLibrary::OnDiscoveryStarted, &obj, &Discovery::OnDiscoveryStarted);
  __hook(&CallBackFromLibrary::OnDiscoveryComplete, &obj, &Discovery::OnDiscoveryComplete);  
}


apiObject.discoverBluetoothDevices();

在这个示例代码中,我应该在调用 apiObject.discoverBluetoothDevices() 后收到回调作为 OnDiscoveryStarted 和 OnDiscoveryComplete。

由于他们使用消息循环进行回调,我只在 GUI 应用程序上得到了回调。如何使用消息循环接收回调,因为库文档说消息循环是必需的。

【问题讨论】:

  • 我认为这是可能的。您创建消息循环的代码是什么(例如,minimal reproducible example)?
  • 消息循环不依赖于窗口,但窗口依赖于消息循环。但是库使用什么样的消息传递不涉及向窗口发送消息?我能想到的只有 2 种可能性 - 线程消息,或单元线程 ActiveX/COM 对象..
  • 蓝牙库很可能会创建一个隐藏窗口。很常见。因此,您必须自己抽出一个消息循环以使其继续运行,这与您自己创建一个窗口时所做的没有什么不同。
  • 那或者它被设计成可以在有或没有窗口的情况下工作,并且更喜欢两个用例的通用代码(这似乎是合理的)。在此之前,我还编写了一些库(而且,公平地说,还提供)一个消息循环,而无需任何线程:它使组件之间的交互变得简单。

标签: c++ visual-c++ windows-messages message-loop


【解决方案1】:

是的,这是可能的——当/如果线程试图使用一个消息队列时,Windows 会将一个消息队列与一个线程相关联。但是,这样做时会有一点竞争条件。要发布到线程的消息队列,请使用PostThreadMessage。但是,线程在调用函数尝试从消息队列中读取之前不会有消息队列(即,Windows 不会为线程创建消息队列,直到线程尝试使用它)。

为防止出现竞争条件,您通常希望按此顺序执行一些操作:

  1. cal CreateEvent 创建一个事件(无信号)
  2. 调用CreateThread,将要传递给新线程的事件句柄传递给它
  3. 让父母等待事件
  4. 让孩子调用PeekMessage(不期待任何结果,因为尚未创建队列——但这会强制创建它)。
  5. 让孩子发出信号
  6. 现在父级将恢复,并且可以使用PostThreadMessage,确保子级具有消息队列的“知识”,因此这将起作用。

另一种可能性是让孩子创建一个窗口,但将其隐藏。这里的明显优势是与期望使用SendMessagePostMessageSendMessageTimeout 等而不是特殊的PostThreadMessage 向普通消息队列发布/发送消息的代码兼容。另一个明显的优点是它避免了上面概述的线程-消息-队列舞。

当您深入了解它时,Windows“窗口”的主要特征不是显示器上的东西——它是一个消息队列,显示器上显示的只是响应某些特定的绘图而完成的消息。隐藏的窗口不仅仅是一个消息队列。

【讨论】:

  • "为了防止竞争条件,您通常希望在这个订单上做一些事情" - 通过CreateEvent() 的事件对象,或通过InitializeConditionVariable() 的条件变量或std::condition_variable,比互斥锁更适合使用。您不能向互斥体发出信号。
  • 如何隐藏窗口?
  • @kittu: ShowWindow(yourWindow, SW_HIDE) 将隐藏一个窗口。
【解决方案2】:

非 GUI 线程中的消息循环:

#include "stdafx.h"
#include <Windows.h>
#include <thread>
#include <iostream>
using namespace std;

void ThreadFunction()
{
    MSG msg;
    BOOL result;

    for (;;)
    {
        result = GetMessage(&msg, nullptr, 0, 0);

        if (result <= 0)
        {
            break;
        }

        cout << msg.message << " " << msg.wParam << " " << msg.lParam << endl;

        //TranslateMessage(&msg);
        //DispatchMessage(&msg);
    }
}

int main()
{
    thread t(ThreadFunction);
    HANDLE h = t.native_handle();
    DWORD dw = GetThreadId(h);

    PostThreadMessage(dw, WM_APP + 1, 1, 2);
    PostThreadMessage(dw, WM_APP + 2, 10, 20);
    PostThreadMessage(dw, WM_QUIT, 10, 20);

    t.join();
    return 0;
}

更新

根据注释,这段代码没有在gcc中编译。试图在 VC++ 中重现这一点,我发现该程序在 x64 中不起作用。这个更新的解决方案有望解决这两个问题:

#include "stdafx.h"
#include <Windows.h>
#include <thread>
#include <iostream>
using namespace std;

DWORD threadID{};


void ThreadFunction(HANDLE event_handle)
{
    MSG msg;
    BOOL result;

    threadID = GetCurrentThreadId();
    SetEvent(event_handle);

    for (;;)
    {
        result = GetMessage(&msg, nullptr, 0, 0);

        if (result <= 0)
        {
            break;
        }

        cout << msg.message << " " << msg.wParam << " " << msg.lParam << endl;
    }
}

int main()
{
    HANDLE e = CreateEvent(nullptr, FALSE, FALSE, nullptr);

    thread t(ThreadFunction, e);

    WaitForSingleObject(e, INFINITE);
    CloseHandle(e);


    PostThreadMessage(threadID, WM_APP + 1, 1, 2);
    PostThreadMessage(threadID, WM_APP + 2, 10, 20);
    PostThreadMessage(threadID, WM_QUIT, 10, 20);

    t.join();
    return 0;
}

【讨论】:

  • 我收到错误:error: invalid conversion from 'std::thread::native_handle_type' {aka 'unsigned int'} to 'HANDLE' {aka 'void*'} [-fpermissive] HANDLE h = t.native_handle();
  • 无法在 VC++ 编译器中重现这一点。但我看到这段代码在 x64 中不起作用。我稍后会发布更新的解决方案。
  • 谢谢我去看看
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2019-03-11
  • 2015-03-04
  • 2014-06-17
  • 2019-04-24
  • 1970-01-01
  • 2012-05-01
  • 2013-12-03
相关资源
最近更新 更多