FIX(client, server): Load native mDNS/DNS-SD symbols at runtime

This change:
- Fixes binaries failing to start on versions of Windows where dnsapi.dll doesn't export the symbols we need.
- Allows us to use the native API in x86 builds.
See https://developercommunity.visualstudio.com/content/problem/1191345/some-dns-api-functions-cause-lnk2019-errors-in-32.html for more info.
This commit is contained in:
Davide Beatrici 2020-12-23 05:13:20 +01:00
parent 72228f6e03
commit 5e088269ae
4 changed files with 82 additions and 73 deletions

View File

@ -5,39 +5,36 @@
#include "Zeroconf.h"
#define SYMBOL_EXISTS(symbol) (GetProcAddress(handle, symbol))
#define GET_SYMBOL(symbol) (symbol = reinterpret_cast< decltype(symbol) >(GetProcAddress(handle, #symbol)))
Zeroconf::Zeroconf() : m_ok(false) {
qRegisterMetaType< uint16_t >("uint16_t");
qRegisterMetaType< BonjourRecord >("BonjourRecord");
qRegisterMetaType< QList< BonjourRecord > >("QList<BonjourRecord>");
#ifdef Q_OS_WIN64
static bool winDnsUnavailable = false;
if (!winDnsUnavailable) {
auto handle = GetModuleHandle(L"dnsapi.dll");
if (handle) {
if (SYMBOL_EXISTS("DnsServiceBrowse") && SYMBOL_EXISTS("DnsServiceBrowseCancel")
&& SYMBOL_EXISTS("DnsServiceResolve") && SYMBOL_EXISTS("DnsServiceResolveCancel")
&& SYMBOL_EXISTS("DnsServiceFreeInstance")) {
m_ok = true;
return;
}
}
#ifdef Q_OS_WIN
auto handle = GetModuleHandle(L"dnsapi.dll");
if (handle) {
GET_SYMBOL(DnsServiceBrowse);
GET_SYMBOL(DnsServiceBrowseCancel);
GET_SYMBOL(DnsServiceResolve);
GET_SYMBOL(DnsServiceResolveCancel);
GET_SYMBOL(DnsServiceFreeInstance);
winDnsUnavailable = true;
qWarning("Zeroconf: Native mDNS/DNS-SD API not available, falling back to third-party API");
}
static bool dnssdLoadFailed = false;
if (!dnssdLoadFailed) {
auto handle = LoadLibrary(L"dnssd.DLL");
if (!handle) {
dnssdLoadFailed = true;
qWarning("Zeroconf: Failed to load dnssd.dll, assuming third-party API is not available");
if (DnsServiceBrowse && DnsServiceBrowseCancel && DnsServiceResolve && DnsServiceResolveCancel
&& DnsServiceFreeInstance) {
m_ok = true;
return;
}
FreeLibrary(handle);
}
qWarning("Zeroconf: Native mDNS/DNS-SD API not available, falling back to third-party API");
handle = LoadLibrary(L"dnssd.dll");
if (!handle) {
qWarning("Zeroconf: Failed to load dnssd.dll, assuming third-party API is not available");
return;
}
FreeLibrary(handle);
#endif
resetHelperBrowser();
resetHelperResolver();
@ -80,7 +77,7 @@ bool Zeroconf::startBrowser(const QString &serviceType) {
m_helperBrowser->browseForServiceType(serviceType);
return true;
}
#ifdef Q_OS_WIN64
#ifdef Q_OS_WIN
const QString queryName = serviceType + QLatin1String(".local");
DNS_SERVICE_BROWSE_REQUEST request{};
@ -110,7 +107,7 @@ bool Zeroconf::stopBrowser() {
resetHelperBrowser();
return true;
}
#ifdef Q_OS_WIN64
#ifdef Q_OS_WIN
if (m_cancelBrowser) {
const auto ret = DnsServiceBrowseCancel(m_cancelBrowser.get());
if (ret == ERROR_SUCCESS || ret == ERROR_CANCELLED) {
@ -134,7 +131,7 @@ bool Zeroconf::startResolver(const BonjourRecord &record) {
m_helperResolver->resolveBonjourRecord(record);
return true;
}
#ifdef Q_OS_WIN64
#ifdef Q_OS_WIN
stopResolver(record);
auto qualifiedHostname = record.serviceName.toStdWString() + L"." + record.registeredType.toStdWString()
@ -160,7 +157,7 @@ bool Zeroconf::startResolver(const BonjourRecord &record) {
#endif
return false;
}
#ifdef Q_OS_WIN64
#ifdef Q_OS_WIN
bool Zeroconf::stopResolver(const BonjourRecord &record) {
if (!m_ok) {
return false;
@ -200,7 +197,7 @@ bool Zeroconf::cleanupResolvers() {
}
auto result = true;
#ifdef Q_OS_WIN64
#ifdef Q_OS_WIN
for (auto i = 0; i < m_resolvers.size(); ++i) {
if (!stopResolver(m_resolvers[i])) {
result = false;
@ -228,7 +225,7 @@ void Zeroconf::helperResolverError(const BonjourRecord record, const DNSServiceE
qWarning("Zeroconf: Third-party resolver API reports error %d", error);
emit resolveError(record);
}
#ifdef Q_OS_WIN64
#ifdef Q_OS_WIN
void WINAPI Zeroconf::callbackBrowseComplete(const DWORD status, void *context, DNS_RECORD *records) {
auto zeroconf = static_cast< Zeroconf * >(context);
zeroconf->m_cancelBrowser.reset();
@ -283,9 +280,12 @@ void WINAPI Zeroconf::callbackBrowseComplete(const DWORD status, void *context,
}
void WINAPI Zeroconf::callbackResolveComplete(const DWORD status, void *context, DNS_SERVICE_INSTANCE *instance) {
auto resolver = static_cast< Resolver * >(context);
auto zeroconf = resolver->m_zeroconf;
if (status != ERROR_SUCCESS) {
if (instance) {
DnsServiceFreeInstance(instance);
zeroconf->DnsServiceFreeInstance(instance);
}
if (status != ERROR_CANCELLED) {
@ -299,10 +299,8 @@ void WINAPI Zeroconf::callbackResolveComplete(const DWORD status, void *context,
return;
}
auto resolverContext = static_cast< Resolver * >(context);
emit resolverContext->m_zeroconf->recordResolved(resolverContext->m_record,
QString::fromWCharArray(instance->pszHostName), instance->wPort);
emit zeroconf->recordResolved(resolver->m_record, QString::fromWCharArray(instance->pszHostName), instance->wPort);
DnsServiceFreeInstance(instance);
zeroconf->DnsServiceFreeInstance(instance);
}
#endif

View File

@ -11,7 +11,7 @@
#include <memory>
#ifdef Q_OS_WIN64
#ifdef Q_OS_WIN
# include <windns.h>
#endif
@ -20,7 +20,7 @@ private:
Q_OBJECT
Q_DISABLE_COPY(Zeroconf)
protected:
#ifdef Q_OS_WIN64
#ifdef Q_OS_WIN
struct Resolver {
Zeroconf *m_zeroconf;
BonjourRecord m_record;
@ -35,7 +35,7 @@ protected:
QList< BonjourRecord > m_records;
std::unique_ptr< BonjourServiceBrowser > m_helperBrowser;
std::unique_ptr< BonjourServiceResolver > m_helperResolver;
#ifdef Q_OS_WIN64
#ifdef Q_OS_WIN
QList< Resolver > m_resolvers;
std::unique_ptr< DNS_SERVICE_CANCEL > m_cancelBrowser;
@ -51,7 +51,13 @@ protected:
void helperResolverRecordResolved(const BonjourRecord record, const QString hostname, const int port);
void helperBrowserError(const DNSServiceErrorType error) const;
void helperResolverError(const BonjourRecord record, const DNSServiceErrorType error);
#ifdef Q_OS_WIN
DNS_STATUS(WINAPI *DnsServiceBrowse)(PDNS_SERVICE_BROWSE_REQUEST pRequest, PDNS_SERVICE_CANCEL pCancel);
DNS_STATUS(WINAPI *DnsServiceBrowseCancel)(PDNS_SERVICE_CANCEL pCancelHandle);
DNS_STATUS(WINAPI *DnsServiceResolve)(PDNS_SERVICE_RESOLVE_REQUEST pRequest, PDNS_SERVICE_CANCEL pCancel);
DNS_STATUS(WINAPI *DnsServiceResolveCancel)(PDNS_SERVICE_CANCEL pCancelHandle);
VOID(WINAPI *DnsServiceFreeInstance)(PDNS_SERVICE_INSTANCE pInstance);
#endif
public:
inline bool isOk() const { return m_ok; }
inline QList< BonjourRecord > currentRecords() const {
@ -62,7 +68,7 @@ public:
bool stopBrowser();
bool startResolver(const BonjourRecord &record);
#ifdef Q_OS_WIN64
#ifdef Q_OS_WIN
bool stopResolver(const BonjourRecord &record);
#endif
bool cleanupResolvers();

View File

@ -5,37 +5,33 @@
#include "Zeroconf.h"
#define SYMBOL_EXISTS(symbol) (GetProcAddress(handle, symbol))
#define GET_SYMBOL(symbol) (symbol = reinterpret_cast< decltype(symbol) >(GetProcAddress(handle, #symbol)))
Zeroconf::Zeroconf() : m_ok(false) {
#ifdef Q_OS_WIN64
static bool winDnsUnavailable = false;
if (!winDnsUnavailable) {
auto handle = GetModuleHandle(L"dnsapi.dll");
if (handle) {
if (SYMBOL_EXISTS("DnsServiceConstructInstance") && SYMBOL_EXISTS("DnsServiceFreeInstance")
&& SYMBOL_EXISTS("DnsServiceRegister") && SYMBOL_EXISTS("DnsServiceDeRegister")
&& SYMBOL_EXISTS("DnsServiceRegisterCancel")) {
m_ok = true;
return;
}
}
#ifdef Q_OS_WIN
auto handle = GetModuleHandle(L"dnsapi.dll");
if (handle) {
GET_SYMBOL(DnsServiceConstructInstance);
GET_SYMBOL(DnsServiceFreeInstance);
GET_SYMBOL(DnsServiceRegister);
GET_SYMBOL(DnsServiceDeRegister);
GET_SYMBOL(DnsServiceRegisterCancel);
winDnsUnavailable = true;
qWarning("Zeroconf: Native mDNS/DNS-SD API not available, falling back to third-party API");
}
static bool dnssdLoadFailed = false;
if (!dnssdLoadFailed) {
auto handle = LoadLibrary(L"dnssd.DLL");
if (!handle) {
dnssdLoadFailed = true;
qWarning("Zeroconf: Failed to load dnssd.dll, assuming third-party API is not available");
if (DnsServiceConstructInstance && DnsServiceFreeInstance && DnsServiceRegister && DnsServiceDeRegister
&& DnsServiceRegisterCancel) {
m_ok = true;
return;
}
FreeLibrary(handle);
}
qWarning("Zeroconf: Native mDNS/DNS-SD API not available, falling back to third-party API");
handle = LoadLibrary(L"dnssd.dll");
if (!handle) {
qWarning("Zeroconf: Failed to load dnssd.dll, assuming third-party API is not available");
return;
}
FreeLibrary(handle);
#endif
resetHelper();
@ -64,7 +60,7 @@ bool Zeroconf::registerService(const BonjourRecord &record, const uint16_t port)
m_helper->registerService(record, port);
return true;
}
#ifdef Q_OS_WIN64
#ifdef Q_OS_WIN
DWORD size = 0;
GetComputerNameEx(ComputerNameDnsHostname, nullptr, &size);
std::vector< wchar_t > hostname(size);
@ -118,7 +114,7 @@ bool Zeroconf::unregisterService() {
resetHelper();
return true;
}
#ifdef Q_OS_WIN64
#ifdef Q_OS_WIN
if (m_cancel) {
const auto ret = DnsServiceRegisterCancel(m_cancel.get());
if (ret == ERROR_SUCCESS || ret == ERROR_CANCELLED) {
@ -142,17 +138,18 @@ bool Zeroconf::unregisterService() {
void Zeroconf::helperError(const DNSServiceErrorType error) {
qWarning("Zeroconf: Third-party API reports error %d, service registration probably failed", error);
}
#ifdef Q_OS_WIN64
#ifdef Q_OS_WIN
void WINAPI Zeroconf::callbackRegisterComplete(const DWORD status, void *context, DNS_SERVICE_INSTANCE *instance) {
auto zeroconf = static_cast< Zeroconf * >(context);
if (instance) {
DnsServiceFreeInstance(instance);
zeroconf->DnsServiceFreeInstance(instance);
}
if (status == ERROR_CANCELLED) {
return;
}
auto zeroconf = static_cast< Zeroconf * >(context);
zeroconf->m_cancel.reset();
if (status != ERROR_SUCCESS) {

View File

@ -10,7 +10,7 @@
#include <memory>
#ifdef Q_OS_WIN64
#ifdef Q_OS_WIN
# include <windns.h>
#endif
@ -21,14 +21,22 @@ private:
protected:
bool m_ok;
std::unique_ptr< BonjourServiceRegister > m_helper;
#ifdef Q_OS_WIN64
#ifdef Q_OS_WIN
std::unique_ptr< DNS_SERVICE_CANCEL > m_cancel;
std::unique_ptr< DNS_SERVICE_REGISTER_REQUEST > m_request;
static void WINAPI callbackRegisterComplete(const DWORD status, void *context, DNS_SERVICE_INSTANCE *instance);
#endif
void helperError(const DNSServiceErrorType error);
#ifdef Q_OS_WIN
PDNS_SERVICE_INSTANCE(WINAPI *DnsServiceConstructInstance)
(PCWSTR pServiceName, PCWSTR pHostName, PIP4_ADDRESS pIp4, PIP6_ADDRESS pIp6, WORD wPort, WORD wPriority,
WORD wWeight, DWORD dwPropertiesCount, PCWSTR *keys, PCWSTR *values);
VOID(WINAPI *DnsServiceFreeInstance)(PDNS_SERVICE_INSTANCE pInstance);
DWORD(WINAPI *DnsServiceRegister)(PDNS_SERVICE_REGISTER_REQUEST pRequest, PDNS_SERVICE_CANCEL pCancel);
DWORD(WINAPI *DnsServiceDeRegister)(PDNS_SERVICE_REGISTER_REQUEST pRequest, PDNS_SERVICE_CANCEL pCancel);
DWORD(WINAPI *DnsServiceRegisterCancel)(PDNS_SERVICE_CANCEL pCancelHandle);
#endif
public:
inline bool isOk() const { return m_ok; }