我在我的 qt cmake 项目中使用 KDNSSD,在 Windows 11 中使用 C++,由 bonjour 支持,它会在调试模式下发现并注册设备,但是当我在发布模式下编译项目时,它不再工作,我在不同的版本中看到的日志模式是,
调试模式
Both Browse and Register:
qt.core.qobject.connect: QObject::connect: Connecting from COMPAT signal (QSocketNotifier::activated(int))
发布模式
For Service Browse:
QObject::setParent: Cannot set parent, new parent is in a different thread
QSocketNotifier: Can only be used with threads started with QThread
qt.core.qobject.connect: QObject::connect: Connecting from COMPAT signal (QSocketNotifier::activated(int))
QObject::startTimer: Timers can only be used with threads started with QThread
For Register:
QSocketNotifier: Can only be used with threads started with QThread
qt.core.qobject.connect: QObject::connect: Connecting from COMPAT signal (QSocketNotifier::activated(int))
此日志显示在所有构建中
Exception thrown at 0x00007FFFFA004B2C (KernelBase.dll) in clipbird.exe: 0x8001010D: An outgoing call cannot be made since the application is dispatching an input-synchronous call.
onecore\com\combase\dcomrem\giptbl.cxx(1796)\combase.dll!00007FFFFC2054EC: (caller: 00007FFFFC21DE1D) ReturnHr(1) tid(3dec) 8000FFFF Catastrophic failure
onecore\com\combase\objact\actvator.cxx(1097)\combase.dll!00007FFFFC21DEB0: (caller: 00007FFFFC280364) ReturnHr(2) tid(3dec) 8000FFFF Catastrophic failure
首先,我没有直接使用线程,当我在 KDNSSD 源代码中看到 line 时,它使用旧的 SIGNAL 和 SLOT,所以
Connecting from COMPAT signal
似乎是合适的,但我不知道其他日志,尤其是在释放模式下。我的整个项目位于 github 特别是 服务寄存器 和 服务浏览器 谢谢:)
服务登记
/**
* @brief Construct a new Discovery Register object
*
* @param parent Parent object
*/
Register::Register(QObject* parent) : QObject(parent) {
this->service = new KDNSSD::PublicService();
this->service->setParent(this);
// connect the signals to the slots
const auto signal_r = &KDNSSD::PublicService::published;
const auto slot_r = &Register::OnServiceRegistered;
connect(this->service, signal_r, this, slot_r);
}
/**
* @brief Register the service
*
* @param callback Callback function to be called
* when service Registered
*/
void Register::registerServiceAsync() {
// Set the service name & other details
this->service->setServiceName(constants::getMDnsServiceName().c_str());
this->service->setType(constants::getMDnsServiceType().c_str());
this->service->setPort(this->getPort());
// publish the service Asynchronously
this->service->publishAsync();
}
/**
* @brief Stop the server
*/
void Register::unregisterService() {
this->service->stop();
}
服务浏览器
/**
* @brief Construct a new Discovery Discover object
*
* @param parent Parent object
*/
Discover::Discover(QObject* parent) : QObject(parent) {
this->m_browser = new KDNSSD::ServiceBrowser(constants::getMDnsServiceType().c_str());
this->m_browser->setParent(this);
}
/// @brief On Service Found
void Discover::OnServiceFound(KDNSSD::RemoteService::Ptr service) {
// get the name of the device & lambda callback
const auto myDevice = QString::fromStdString(constants::getMDnsServiceName());
const auto callback = [&, service](const QHostInfo& info) {
if (info.error() != QHostInfo::NoError || info.addresses().isEmpty()) {
emit this->OnErrorOccurred(LOG("Unable to resolve service"));
return;
}
auto host = info.addresses().first();
auto port = quint16(service->port());
this->onServerAdded({host, port});
};
// resolve the service
if (!service->resolve()) {
emit this->OnErrorOccurred(LOG("Unable to resolve service"));
return;
}
// check if the service is mine
if (service->serviceName() == myDevice) {
return;
}
// lookup the host
QHostInfo::lookupHost(service->hostName(), callback);
}
/// @brief On Service Removed
void Discover::OnServiceRemoved(KDNSSD::RemoteService::Ptr service) {
// get the name of the device & lambda callback
const auto myDevice = QString::fromStdString(constants::getMDnsServiceName());
const auto callback = [&, service](const QHostInfo& info) {
if (info.error() != QHostInfo::NoError || info.addresses().isEmpty()) {
emit this->OnErrorOccurred(LOG("Unable to resolve service"));
return;
}
auto host = info.addresses().first();
auto port = quint16(service->port());
this->onServerRemoved({host, port});
};
// resolve the service
if (!service->resolve()) {
emit this->OnErrorOccurred(LOG("Unable to resolve service"));
return;
}
// check if the service is mine
if (service->serviceName() == myDevice) {
return;
}
// lookup the host
QHostInfo::lookupHost(service->hostName(), callback);
}
/**
* @brief Starts the discovery client by sending the
* broadcast message
*
* @param interval Interval between each broadcast
*/
void Discover::startDiscovery() {
// connect the browser signal to the slot of this class
const auto signal_r = &KDNSSD::ServiceBrowser::serviceRemoved;
const auto slot_r = &Discover::OnServiceRemoved;
connect(this->m_browser, signal_r, this, slot_r);
// connect the browser signal to the slot of this class
const auto signal_a = &KDNSSD::ServiceBrowser::serviceAdded;
const auto slot_a = &Discover::OnServiceFound;
connect(this->m_browser, signal_a, this, slot_a);
// start the browser
this->m_browser->startBrowse();
}
您遇到的错误消息似乎与线程问题有关。由于编译器优化和/或不同的运行时环境,调试模式下的行为可能与发布模式不同。调试模式可能更宽松,这可能会导致工作正常,但在发布模式下,会应用更严格的规则,从而导致这些错误。
关键问题似乎是:
“QObject::setParent:无法设置父级,新父级位于不同的线程中” “QSocketNotifier:只能与以 QThread 启动的线程一起使用” “计时器只能与以 QThread 启动的线程一起使用” “由于应用程序正在调度输入同步调用,因此无法进行传出呼叫。” 让我们逐一讨论:
QObject::setParent:无法设置父级,新父级位于不同线程中: 当您尝试使用 QObject::setParent() 方法在线程之间移动对象,并且新的父对象与子对象位于不同的线程中时,会发生此错误。使用 Qt 时,每个对象通常应与其父对象属于同一线程。
您应该检查设置对象父对象的代码,并确保父对象和子对象属于同一线程。
QSocketNotifier:只能与以 QThread 启动的线程一起使用: QSocketNotifier 设计为与 QThread 一起使用,并且在您的发布版本中,线程行为可能会发生变化,从而导致此错误。同样,您应该检查使用 QSocketNotifier 的代码,并确保它是在 QThread 的上下文中使用的。
定时器只能与以 QThread 启动的线程一起使用: 该错误与定时器的使用有关。 Qt 中的定时器应该在 QThread 的上下文中使用。如果您的代码中运行任何计时器,请确保它们相对于线程正确使用。
“由于应用程序正在调度输入同步调用,因此无法进行传出呼叫。”: 这个错误有点棘手,它也可能与线程有关。这可能是由于尝试从主线程执行长时间运行或阻塞操作而导致的,这可能会导致无响应或死锁。检查主线程中是否正在执行任何冗长或阻塞操作,并考虑将它们移至单独的线程。
一般来说,确保所有 Qt 对象在同一线程上下文中创建、使用和销毁至关重要。混合来自不同线程的对象可能会导致未定义的行为,Qt 提供了各种机制(例如 QThread、QThreadPool 和 QTimer)来有效管理并发。
要调试线程问题,您可以尝试使用 Qt 的内置调试工具,例如 qDebug()、Q_ASSERT 和 QThread::currentThread() 来打印线程信息并识别线程使用中的任何不一致之处。
此外,查看发生错误的 KDNSSD 源代码可能会让您深入了解它如何管理线程,并可能提供有关如何解决问题的提示。
请仔细检查您的代码并确保正确处理线程和 Qt 对象以解决这些错误。