QEventLoop屏蔽了底层消息循环实现细节,向上提供了与平台无关的消息/事件循环。
本文拟对Windows系统下QEventLoop的实现原理予以分析。
注1:限于研究水平,分析难免不当,欢迎批评指正。
注2:文章内容会不定期更新。
一、研究素材:Win32应用程序框架
在Win32应用程序中,wWinMain是整个程序的入口点,整个代码段主要包括窗口类注册、创建窗口、消息循环等。
int APIENTRY wWinMain(_In_ HINSTANCE hInstance,
_In_opt_ HINSTANCE hPrevInstance,
_In_ LPWSTR lpCmdLine,
_In_ int nCmdShow)
{
UNREFERENCED_PARAMETER(hPrevInstance);
UNREFERENCED_PARAMETER(lpCmdLine);
// TODO: Place code here.
// Initialize global strings
LoadStringW(hInstance, IDS_APP_TITLE, szTitle, MAX_LOADSTRING);
LoadStringW(hInstance, IDC_WINDOWSPROJECT1, szWindowClass, MAX_LOADSTRING);
MyRegisterClass(hInstance);
// Perform application initialization:
if (!InitInstance (hInstance, nCmdShow))
{
return FALSE;
}
HACCEL hAccelTable = LoadAccelerators(hInstance, MAKEINTRESOURCE(IDC_WINDOWSPROJECT1));
MSG msg;
// Main message loop:
while (GetMessage(&msg, nullptr, 0, 0))
{
if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
return (int) msg.wParam;
}
MyRegisterClass用于注册创库类,可以指定窗口样式、窗口过程函数等。
//
// FUNCTION: MyRegisterClass()
//
// PURPOSE: Registers the window class.
//
ATOM MyRegisterClass(HINSTANCE hInstance)
{
WNDCLASSEXW wcex;
wcex.cbSize = sizeof(WNDCLASSEX);
wcex.style = CS_HREDRAW | CS_VREDRAW;
wcex.lpfnWndProc = WndProc;
wcex.cbClsExtra = 0;
wcex.cbWndExtra = 0;
wcex.hInstance = hInstance;
wcex.hIcon = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_WINDOWSPROJECT1));
wcex.hCursor = LoadCursor(nullptr, IDC_ARROW);
wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);
wcex.lpszMenuName = MAKEINTRESOURCEW(IDC_WINDOWSPROJECT1);
wcex.lpszClassName = szWindowClass;
wcex.hIconSm = LoadIcon(wcex.hInstance, MAKEINTRESOURCE(IDI_SMALL));
return RegisterClassExW(&wcex);
}
完成窗口类注册之后,可以依据窗口类创建窗口实例,
//
// FUNCTION: InitInstance(HINSTANCE, int)
//
// PURPOSE: Saves instance handle and creates main window
//
// COMMENTS:
//
// In this function, we save the instance handle in a global variable and
// create and display the main program window.
//
BOOL InitInstance(HINSTANCE hInstance, int nCmdShow)
{
hInst = hInstance; // Store instance handle in our global variable
HWND hWnd = CreateWindowW(szWindowClass, szTitle, WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, nullptr, nullptr, hInstance, nullptr);
if (!hWnd)
{
return FALSE;
}
ShowWindow(hWnd, nCmdShow);
UpdateWindow(hWnd);
return TRUE;
}
当完成窗口类注册之后,便可以依据窗口类名称来创建窗口。在Windows系统下,消息队列用于管理线程内窗口相关的消息,同一线程内的窗口对象共享同一各消息队列。
For each thread that creates a window, the operating system creates a queue for window messages. This queue holds messages for all the windows that are created on that thread.
MSG msg;
// Main message loop:
while (GetMessage(&msg, nullptr, 0, 0))
{
if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
当消息被投递到窗口时,便会调用对应的窗口过程函数,
//
// FUNCTION: WndProc(HWND, UINT, WPARAM, LPARAM)
//
// PURPOSE: Processes messages for the main window.
//
// WM_COMMAND - process the application menu
// WM_PAINT - Paint the main window
// WM_DESTROY - post a quit message and return
//
//
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
switch (message)
{
case WM_COMMAND:
{
int wmId = LOWORD(wParam);
// Parse the menu selections:
switch (wmId)
{
case IDM_ABOUT:
DialogBox(hInst, MAKEINTRESOURCE(IDD_ABOUTBOX), hWnd, About);
break;
case IDM_EXIT:
DestroyWindow(hWnd);
break;
default:
return DefWindowProc(hWnd, message, wParam, lParam);
}
}
break;
case WM_PAINT:
{
PAINTSTRUCT ps;
HDC hdc = BeginPaint(hWnd, &ps);
// TODO: Add any drawing code that uses hdc here...
EndPaint(hWnd, &ps);
}
break;
case WM_DESTROY:
PostQuitMessage(0);
break;
default:
return DefWindowProc(hWnd, message, wParam, lParam);
}
return 0;
}
二、QEventLoop实现原理
QEventLoop实际上是通过QAbstractEventDispatcher子类来屏蔽了底层窗口系统的消息循环。
bool QEventLoop::processEvents(ProcessEventsFlags flags)
{
Q_D(QEventLoop);
if (!d->threadData->hasEventDispatcher())
return false;
return d->threadData->eventDispatcher.loadRelaxed()->processEvents(flags);
}
从中可以看出,QAbstractEventDispatcher是线程级别的存在,每个线程有唯一的QAbstractEventDispatcher实现,线程内的所有QEventLoop共享同一个QAbstractEventDispatcher实现。
Ref. from QAbstractEventDispatcher
An event dispatcher receives events from the window system and other sources. It then sends them to the QCoreApplication or QApplication instance for processing and delivery. QAbstractEventDispatcher provides fine-grained control over event delivery.
2.1 QEventDispatcherWin32的创建
实际上,在Windows系统下,当创建QCoreApplication时,便会创建主线程相关的QEventDispatcherWin32,而QEventDispatcherWin32正是QAbstractEventDispatcher的Windows系统实现。
void QCoreApplicationPrivate::init()
{
// ...
#ifndef QT_NO_QOBJECT
// use the event dispatcher created by the app programmer (if any)
Q_ASSERT(!eventDispatcher);
eventDispatcher = threadData->eventDispatcher.loadRelaxed();
// otherwise we create one
if (!eventDispatcher)
createEventDispatcher();
Q_ASSERT(eventDispatcher);
if (!eventDispatcher->parent()) {
eventDispatcher->moveToThread(threadData->thread.loadAcquire());
eventDispatcher->setParent(q);
}
threadData->eventDispatcher = eventDispatcher;
eventDispatcherReady();
#endif
// ...
}
void QCoreApplicationPrivate::createEventDispatcher()
{
Q_Q(QCoreApplication);
QThreadData *data = QThreadData::current();
Q_ASSERT(!data->hasEventDispatcher());
eventDispatcher = data->createEventDispatcher();
eventDispatcher->setParent(q);
}
QAbstractEventDispatcher *QThreadData::createEventDispatcher()
{
QAbstractEventDispatcher *ed = QThreadPrivate::createEventDispatcher(this);
eventDispatcher.storeRelease(ed);
ed->startingUp();
return ed;
}
QAbstractEventDispatcher *QThreadPrivate::createEventDispatcher(QThreadData *data)
{
Q_UNUSED(data);
#ifndef Q_OS_WINRT
return new QEventDispatcherWin32;
#else
return new QEventDispatcherWinRT;
#endif
}
2.2 QEventDispatcherWin32的实现
由前面的分析可知,在Windows系统下,QEventLoop实际上是使用QEventDispatcherWin32来完成消息循环。那QEventDispatcherWin32又是如何实现对Windows消息循环的封装呢?
在QEventDispatcherWin32中,注册了一个"QEventDispatcherWin32_Internal_Widget"窗口类,而对应的窗口过程函数主要是提供了对定时器、套接字等消息处理。
QWindowsMessageWindowClassContext::QWindowsMessageWindowClassContext()
: atom(0), className(0)
{
// make sure that multiple Qt's can coexist in the same process
const QString qClassName = QStringLiteral("QEventDispatcherWin32_Internal_Widget")
+ QString::number(quintptr(qt_internal_proc));
className = new wchar_t[qClassName.size() + 1];
qClassName.toWCharArray(className);
className[qClassName.size()] = 0;
WNDCLASS wc;
wc.style = 0;
wc.lpfnWndProc = qt_internal_proc;
wc.cbClsExtra = 0;
wc.cbWndExtra = 0;
wc.hInstance = GetModuleHandle(0);
wc.hIcon = 0;
wc.hCursor = 0;
wc.hbrBackground = 0;
wc.lpszMenuName = NULL;
wc.lpszClassName = className;
atom = RegisterClass(&wc);
if (!atom) {
qErrnoWarning("%ls RegisterClass() failed", qUtf16Printable(qClassName));
delete [] className;
className = 0;
}
}
同时,依据该窗口类,创建了一个内部窗口,
void QEventDispatcherWin32::createInternalHwnd()
{
Q_D(QEventDispatcherWin32);
if (d->internalHwnd)
return;
d->internalHwnd = qt_create_internal_window(this);
// setup GetMessage hook needed to drive our posted events
d->getMessageHook = SetWindowsHookEx(WH_GETMESSAGE, (HOOKPROC) qt_GetMessageHook, NULL, GetCurrentThreadId());
if (Q_UNLIKELY(!d->getMessageHook)) {
int errorCode = GetLastError();
qFatal("Qt: INTERNAL ERROR: failed to install GetMessage hook: %d, %ls",
errorCode, qUtf16Printable(qt_error_string(errorCode)));
}
// start all normal timers
for (int i = 0; i < d->timerVec.count(); ++i)
d->registerTimer(d->timerVec.at(i));
}
static HWND qt_create_internal_window(const QEventDispatcherWin32 *eventDispatcher)
{
QWindowsMessageWindowClassContext *ctx = qWindowsMessageWindowClassContext();
if (!ctx->atom)
return 0;
HWND wnd = CreateWindow(ctx->className, // classname
ctx->className, // window name
0, // style
0, 0, 0, 0, // geometry
HWND_MESSAGE, // parent
0, // menu handle
GetModuleHandle(0), // application
0); // windows creation data.
if (!wnd) {
qErrnoWarning("CreateWindow() for QEventDispatcherWin32 internal window failed");
return 0;
}
#ifdef GWLP_USERDATA
SetWindowLongPtr(wnd, GWLP_USERDATA, reinterpret_cast<LONG_PTR>(eventDispatcher));
#else
SetWindowLong(wnd, GWL_USERDATA, reinterpret_cast<LONG>(eventDispatcher));
#endif
return wnd;
}
在QEventDispatcherWin32::processEvents中,会不断的调用PeekMessage检查消息队列,然后调用TranslateMessage与DispatchMessage进行消息转发。
bool QEventDispatcherWin32::processEvents(QEventLoop::ProcessEventsFlags flags)
{
Q_D(QEventDispatcherWin32);
if (!d->internalHwnd) {
createInternalHwnd();
wakeUp(); // trigger a call to sendPostedEvents()
}
d->interrupt.storeRelaxed(false);
emit awake();
// To prevent livelocks, send posted events once per iteration.
// QCoreApplication::sendPostedEvents() takes care about recursions.
sendPostedEvents();
bool canWait;
bool retVal = false;
do {
DWORD waitRet = 0;
DWORD nCount = 0;
HANDLE *pHandles = nullptr;
if (d->winEventNotifierActivatedEvent) {
nCount = 1;
pHandles = &d->winEventNotifierActivatedEvent;
}
QVarLengthArray<MSG> processedTimers;
while (!d->interrupt.loadRelaxed()) {
MSG msg;
bool haveMessage;
if (!(flags & QEventLoop::ExcludeUserInputEvents) && !d->queuedUserInputEvents.isEmpty()) {
// process queued user input events
haveMessage = true;
msg = d->queuedUserInputEvents.takeFirst();
} else if(!(flags & QEventLoop::ExcludeSocketNotifiers) && !d->queuedSocketEvents.isEmpty()) {
// process queued socket events
haveMessage = true;
msg = d->queuedSocketEvents.takeFirst();
} else {
haveMessage = PeekMessage(&msg, 0, 0, 0, PM_REMOVE);
if (haveMessage) {
if (flags.testFlag(QEventLoop::ExcludeUserInputEvents)
&& isUserInputMessage(msg.message)) {
// queue user input events for later processing
d->queuedUserInputEvents.append(msg);
continue;
}
if ((flags & QEventLoop::ExcludeSocketNotifiers)
&& (msg.message == WM_QT_SOCKETNOTIFIER && msg.hwnd == d->internalHwnd)) {
// queue socket events for later processing
d->queuedSocketEvents.append(msg);
continue;
}
}
}
if (!haveMessage) {
// no message - check for signalled objects
waitRet = MsgWaitForMultipleObjectsEx(nCount, pHandles, 0, QS_ALLINPUT, MWMO_ALERTABLE);
if ((haveMessage = (waitRet == WAIT_OBJECT_0 + nCount))) {
// a new message has arrived, process it
continue;
}
}
if (haveMessage) {
if (d->internalHwnd == msg.hwnd && msg.message == WM_QT_SENDPOSTEDEVENTS) {
// Set result to 'true', if the message was sent by wakeUp().
if (msg.wParam == WMWP_QT_FROMWAKEUP)
retVal = true;
continue;
}
if (msg.message == WM_TIMER) {
// avoid live-lock by keeping track of the timers we've already sent
bool found = false;
for (int i = 0; !found && i < processedTimers.count(); ++i) {
const MSG processed = processedTimers.constData()[i];
found = (processed.wParam == msg.wParam && processed.hwnd == msg.hwnd && processed.lParam == msg.lParam);
}
if (found)
continue;
processedTimers.append(msg);
} else if (msg.message == WM_QUIT) {
if (QCoreApplication::instance())
QCoreApplication::instance()->quit();
return false;
}
if (!filterNativeEvent(QByteArrayLiteral("windows_generic_MSG"), &msg, 0)) {
TranslateMessage(&msg);
DispatchMessage(&msg);
}
} else if (waitRet - WAIT_OBJECT_0 < nCount) {
activateEventNotifiers();
} else {
// nothing todo so break
break;
}
retVal = true;
}
// still nothing - wait for message or signalled objects
canWait = (!retVal
&& !d->interrupt.loadRelaxed()
&& (flags & QEventLoop::WaitForMoreEvents));
if (canWait) {
emit aboutToBlock();
waitRet = MsgWaitForMultipleObjectsEx(nCount, pHandles, INFINITE, QS_ALLINPUT, MWMO_ALERTABLE | MWMO_INPUTAVAILABLE);
emit awake();
if (waitRet - WAIT_OBJECT_0 < nCount) {
activateEventNotifiers();
retVal = true;
}
}
} while (canWait);
return retVal;
}
2.3 小结
依据上述对QEventLoop的实现分析,大体上可有以下结论:
1. QEventLoop内部实际上是通过QAbstractEventDispatcher子类来完成了消息(事件)分发,这实际上就是GoF's Bridge Pattern。
2. QAbstractEventDispatcher定义了事件分发的接口,而针对具体窗口系统的实现则有QEventDispatcherWin32、QEventDispatcherWinRT、QEventDispatcherUNIX、QEventDispatcherGlib等负责。
3. 每个线程都有唯一的同一事件分发器,线程内的窗体共用同一事件分发器。
4. 在Windows系统下,QEventLoop::processEvents函数大体等价于PeekMessage/TranslateMessage/DispatchMessage。
三、Qt中的事件路由
3.1 消息转换
在Qt中,当窗体接受到底层窗口系统消息之后,又是如何将这些平台相关的消息转换成Qt事件呢?
Ref. from QEvent
Qt's main event loop (QCoreApplication::exec()) fetches native window system events from the event queue, translates them into QEvents, and sends the translated events to QObjects.
In general, events come from the underlying window system (spontaneous() returns true), but it is also possible to manually send events using QCoreApplication::sendEvent() and QCoreApplication::postEvent() (spontaneous() returns false).
实际上,对于QGuiApplication,会基于QPA (Qt Platform Abstraction)来创建QPlatformIntegration,进而创建特定的QAbstractEventDispatcher。
Ref. from Qt Platform Abstraction
The Qt Platform Abstraction (QPA) is the platform abstraction layer for Qt 5 and replaces Qt for Embedded Linux and the platform ports from Qt 4.
QPA plugins are implemented by subclassing various QPlatform* classes. There are several root classes, such as QPlatformIntegration and QPlatformWindow for window system integration and QPlatformTheme for deeper platform theming and integration. QStyle is not a part of QPA.
void QGuiApplicationPrivate::createEventDispatcher()
{
Q_ASSERT(!eventDispatcher);
if (platform_integration == 0)
createPlatformIntegration();
// The platform integration should not mess with the event dispatcher
Q_ASSERT(!eventDispatcher);
eventDispatcher = platform_integration->createEventDispatcher();
}
对于Windows系统,QGuiApplication会通过加载qwindows.dll插件来创建QWindowsIntegration对象。
QAbstractEventDispatcher * QWindowsIntegration::createEventDispatcher() const
{
return new QWindowsGuiEventDispatcher;
}
也就是说,在Windows系统下,QGuiApplication实际上是通过QWindowsGuiEventDispatcher来实现对底层消息的调度。
void QWindowsGuiEventDispatcher::sendPostedEvents()
{
QEventDispatcherWin32::sendPostedEvents();
QWindowSystemInterface::sendWindowSystemEvents(m_flags);
}
bool QWindowSystemInterface::sendWindowSystemEvents(QEventLoop::ProcessEventsFlags flags)
{
int nevents = 0;
while (QWindowSystemInterfacePrivate::windowSystemEventsQueued()) {
QWindowSystemInterfacePrivate::WindowSystemEvent *event = nullptr;
if (QWindowSystemInterfacePrivate::platformFiltersEvents) {
event = QWindowSystemInterfacePrivate::getWindowSystemEvent();
} else {
event = flags & QEventLoop::ExcludeUserInputEvents ?
QWindowSystemInterfacePrivate::getNonUserInputWindowSystemEvent() :
QWindowSystemInterfacePrivate::getWindowSystemEvent();
}
if (!event)
break;
if (QWindowSystemInterfacePrivate::eventHandler) {
if (QWindowSystemInterfacePrivate::eventHandler->sendEvent(event))
nevents++;
} else {
nevents++;
QGuiApplicationPrivate::processWindowSystemEvent(event);
}
// Record the accepted state for the processed event
// (excluding flush events). This state can then be
// returned by flushWindowSystemEvents().
if (event->type != QWindowSystemInterfacePrivate::FlushEvents)
QWindowSystemInterfacePrivate::eventAccepted.storeRelaxed(event->eventAccepted);
delete event;
}
return (nevents > 0);
}
对于来自底层窗口系统的鼠标消息,可以看到QWindowsGuiEventDispatcher最终是通过调用QGuiApplicationPrivate::processMouseEvent,
void QGuiApplicationPrivate::processWindowSystemEvent(QWindowSystemInterfacePrivate::WindowSystemEvent *e)
{
Q_TRACE_SCOPE(QGuiApplicationPrivate_processWindowSystemEvent, e->type);
switch(e->type) {
case QWindowSystemInterfacePrivate::Mouse:
QGuiApplicationPrivate::processMouseEvent(static_cast<QWindowSystemInterfacePrivate::MouseEvent *>(e));
break;
// ...
default:
qWarning() << "Unknown user input event type:" << e->type;
break;
}
}
而正是在这个函数中,实现了底层窗口消息到Qt事件的转换。
void QGuiApplicationPrivate::processMouseEvent(QWindowSystemInterfacePrivate::MouseEvent *e)
{
// ...
QMouseEvent ev(type, localPoint, localPoint, globalPoint, button, e->buttons, e->modifiers, e->source);
ev.setTimestamp(e->timestamp);
if (window->d_func()->blockedByModalWindow && !qApp->d_func()->popupActive()) {
// a modal window is blocking this window, don't allow mouse events through
return;
}
if (doubleClick && (ev.type() == QEvent::MouseButtonPress)) {
// QtBUG-25831, used to suppress delivery in qwidgetwindow.cpp
setMouseEventFlags(&ev, ev.flags() | Qt::MouseEventCreatedDoubleClick);
}
QGuiApplication::sendSpontaneousEvent(window, &ev);
// ...
}
以上代码分析,也可通过在Qt应用程序设置断点,观察函数堆栈调用来进一步佐证。
![](https://img-blog.csdnimg.cn/direct/1044fbda427249eeaedfc1e10281f12e.png)
3.2 事件处理
当底层窗口消息转换成Qt事件之后,最终会由QCoreApplication进行事件路由。
bool QCoreApplication::notify(QObject *receiver, QEvent *event)
{
// no events are delivered after ~QCoreApplication() has started
if (QCoreApplicationPrivate::is_app_closing)
return true;
return doNotify(receiver, event);
}
static bool doNotify(QObject *receiver, QEvent *event)
{
if (receiver == 0) { // serious error
qWarning("QCoreApplication::notify: Unexpected null receiver");
return true;
}
#ifndef QT_NO_DEBUG
QCoreApplicationPrivate::checkReceiverThread(receiver);
#endif
return receiver->isWidgetType() ? false : QCoreApplicationPrivate::notify_helper(receiver, event);
}
bool QCoreApplicationPrivate::notify_helper(QObject *receiver, QEvent * event)
{
// Note: when adjusting the tracepoints in here
// consider adjusting QApplicationPrivate::notify_helper too.
Q_TRACE(QCoreApplication_notify_entry, receiver, event, event->type());
bool consumed = false;
bool filtered = false;
Q_TRACE_EXIT(QCoreApplication_notify_exit, consumed, filtered);
// send to all application event filters (only does anything in the main thread)
if (QCoreApplication::self
&& receiver->d_func()->threadData->thread.loadAcquire() == mainThread()
&& QCoreApplication::self->d_func()->sendThroughApplicationEventFilters(receiver, event)) {
filtered = true;
return filtered;
}
// send to all receiver event filters
if (sendThroughObjectEventFilters(receiver, event)) {
filtered = true;
return filtered;
}
// deliver the event
consumed = receiver->event(event);
return consumed;
}
从中可以看出,Qt事件首先分别让应用程序过滤器、对象过滤器进行处理,最后再交由虚函数QObject::event(QEvent *e)进行处理。
对于QWidget,QWidget::event(QEvent *e)实际上就是通过调用mousePressEvent(QMouseEvent *event) 、mouseReleaseEvent(QMouseEvent *event)等虚函数来处理各种具体的事件。
3.3 小结
基于上述分析,可有以下结论:
1. Qt基于QPA实现了跨窗口系统。QPA实际上是一种插件系统。
2. 在Windows系统下,QGuiApplication创建QWindowsIntegration。
3. QCoreApplication使用QEventDispatcherWin32进行消息调度;
4. QWindowsIntegration使用QWindowsGuiEventDispatcher进行消息调度。
5. QEvent先有系统级的filter处理,再有对象级的filter处理,最后交由目标对象处理。
四、扩展:Qt插件系统
插件本质上是具有特定接口的动态库文件,通过插件可以在不改变软件主体的情况下,增加新的功能特性。
Ref. from Wikipedia
Plug-in is computer software that adds a specific feature to an existing computer program without altering the host program itself. When a program supports plug-ins, it enables customization.
插件系统主要组件一般由插件接口、插件管理器等组成。插件接口定义了插件向外需要暴露的接口;插件管理器负责加载/卸载插件、创建插件接口对象、访问插件元数据等。
Ref. from Building Your Own Plugin Framework: Part 1
A plugin-based system can be divided into three parts:
- The domain-specific system.
The domain-specific system loads the plugins and creates plugin objects via the plugin manager. Once a plugin object is created and the main system has some pointer/reference to it, it can be used just like any other object.
- A plugin manager.
The plugin manager is a pretty generic piece of code. It manages the life-cycle of the plugins and exposes them to the main system. It can find and load plugins, initialize them, register factory functions and be able to unload plugins. It should also let the main system iterate over loaded plugins or registered plugin objects.
- The plugins.
The plugins themselves should conform to the plugin manager protocol and provide objects that conform to the main system expectations.
4.1 QLibrary
QLibray向上屏蔽了底层平台的差异性,提供了运行时加载/卸载动态库、符号引用的功能。
Ref. from QLibray
An instance of a QLibrary object operates on a single shared object file (which we call a "library", but is also known as a "DLL"). A QLibrary provides access to the functionality in the library in a platform independent way.
4.1.1 加载动态库
QLibrary::load是通过QLibraryPrivate::load_sys(), 目前Qt分别针对Windows、Linux提供了两种实现。
对于Windows系统,QLibraryPrivate::load_sys()实际上就是通过调用LoadLibrary这个Win32 API完成了库的加载。
bool QLibraryPrivate::load_sys()
{
#ifndef Q_OS_WINRT
//avoid 'Bad Image' message box
UINT oldmode = SetErrorMode(SEM_FAILCRITICALERRORS|SEM_NOOPENFILEERRORBOX);
#endif
// We make the following attempts at locating the library:
//
// Windows
// if (absolute)
// fileName
// fileName + ".dll"
// else
// fileName + ".dll"
// fileName
//
// NB If it's a plugin we do not ever try the ".dll" extension
QStringList attempts;
if (pluginState != IsAPlugin)
attempts.append(fileName + QLatin1String(".dll"));
// If the fileName is an absolute path we try that first, otherwise we
// use the system-specific suffix first
QFileSystemEntry fsEntry(fileName);
if (fsEntry.isAbsolute())
attempts.prepend(fileName);
else
attempts.append(fileName);
#ifdef Q_OS_WINRT
if (fileName.startsWith(QLatin1Char('/')))
attempts.prepend(QDir::rootPath() + fileName);
#endif
for (const QString &attempt : qAsConst(attempts)) {
#ifndef Q_OS_WINRT
pHnd = LoadLibrary(reinterpret_cast<const wchar_t*>(QDir::toNativeSeparators(attempt).utf16()));
#else // Q_OS_WINRT
QString path = QDir::toNativeSeparators(QDir::current().relativeFilePath(attempt));
pHnd = LoadPackagedLibrary(reinterpret_cast<LPCWSTR>(path.utf16()), 0);
if (pHnd)
qualifiedFileName = attempt;
#endif // !Q_OS_WINRT
// If we have a handle or the last error is something other than "unable
// to find the module", then bail out
if (pHnd || ::GetLastError() != ERROR_MOD_NOT_FOUND)
break;
}
#ifndef Q_OS_WINRT
SetErrorMode(oldmode);
#endif
if (!pHnd) {
errorString = QLibrary::tr("Cannot load library %1: %2").arg(
QDir::toNativeSeparators(fileName), qt_error_string());
} else {
// Query the actual name of the library that was loaded
errorString.clear();
#ifndef Q_OS_WINRT
wchar_t buffer[MAX_PATH];
::GetModuleFileName(pHnd, buffer, MAX_PATH);
QString moduleFileName = QString::fromWCharArray(buffer);
moduleFileName.remove(0, 1 + moduleFileName.lastIndexOf(QLatin1Char('\\')));
const QDir dir(fsEntry.path());
if (dir.path() == QLatin1String("."))
qualifiedFileName = moduleFileName;
else
qualifiedFileName = dir.filePath(moduleFileName);
if (loadHints() & QLibrary::PreventUnloadHint) {
// prevent the unloading of this component
HMODULE hmod;
bool ok = GetModuleHandleEx(GET_MODULE_HANDLE_EX_FLAG_PIN |
GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS,
reinterpret_cast<const wchar_t *>(pHnd),
&hmod);
Q_ASSERT(!ok || hmod == pHnd);
Q_UNUSED(ok);
}
#endif // !Q_OS_WINRT
}
return (pHnd != 0);
}
4.1.2 卸载动态库
默认情况下,当软件退出时,会自动卸载动态链接库。但QLibrary::unload提供了尝试卸载动态库的接口。
Ref. from QLibrary
Unloads the library and returns
true
if the library could be unloaded; otherwise returnsfalse
.This happens automatically on application termination, so you shouldn't normally need to call this function.
If other instances of QLibrary are using the same library, the call will fail, and unloading will only happen when every instance has called unload().
QLibrary::unload实际调用了QLibrary::unload_sys,在Windows系统下,QLibrary::unload_sys封装了对FreeLibrary的调用。
bool QLibraryPrivate::unload_sys()
{
if (!FreeLibrary(pHnd)) {
errorString = QLibrary::tr("Cannot unload library %1: %2").arg(
QDir::toNativeSeparators(fileName), qt_error_string());
return false;
}
errorString.clear();
return true;
}
4.1.3 符号引用
QLibrary::resolve可以对动态库内的函数函数指针,在Window系统下,实际上是通过调用GetProcAddress来实现的。
QFunctionPointer QLibraryPrivate::resolve_sys(const char* symbol)
{
FARPROC address = GetProcAddress(pHnd, symbol);
if (!address) {
errorString = QLibrary::tr("Cannot resolve symbol \"%1\" in %2: %3").arg(
QString::fromLatin1(symbol), QDir::toNativeSeparators(fileName), qt_error_string());
} else {
errorString.clear();
}
return QFunctionPointer(address);
}
4.2 QPluginLoader
QPluginLoader实际上也是通过QLibraryPrivate实现对库文件的加载,与QLibrary不同之处,QPluginLoader假定动态库中存在qt_plugin_instance、qt_plugin_query_metadata等导出函数。
bool QLibraryPrivate::loadPlugin()
{
if (instance) {
libraryUnloadCount.ref();
return true;
}
if (pluginState == IsNotAPlugin)
return false;
if (load()) {
instance = (QtPluginInstanceFunction)resolve("qt_plugin_instance");
return instance;
}
if (qt_debug_component())
qWarning() << "QLibraryPrivate::loadPlugin failed on" << fileName << ":" << errorString;
pluginState = IsNotAPlugin;
return false;
}
static bool qt_get_metadata(QLibraryPrivate *priv, QString *errMsg)
{
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
auto getMetaData = [](QFunctionPointer fptr) {
auto f = reinterpret_cast<const char * (*)()>(fptr);
return qMakePair<const char *, size_t>(f(), INT_MAX);
};
#else
auto getMetaData = [](QFunctionPointer fptr) {
auto f = reinterpret_cast<QPair<const char *, size_t> (*)()>(fptr);
return f();
};
#endif
QFunctionPointer pfn = priv->resolve("qt_plugin_query_metadata");
if (!pfn)
return false;
auto metaData = getMetaData(pfn);
QJsonDocument doc = qJsonFromRawLibraryMetaData(metaData.first, metaData.second, errMsg);
if (doc.isNull())
return false;
priv->metaData = doc.object();
return true;
}
另外,为了方便使用,仅需要提供插件名字,QPluginLoader便会依据QCoreApplication::libraryPaths查找带有前缀、后缀的库文件。
static QString locatePlugin(const QString& fileName)
{
const bool isAbsolute = QDir::isAbsolutePath(fileName);
if (isAbsolute) {
QFileInfo fi(fileName);
if (fi.isFile()) {
return fi.canonicalFilePath();
}
}
QStringList prefixes = QLibraryPrivate::prefixes_sys();
prefixes.prepend(QString());
QStringList suffixes = QLibraryPrivate::suffixes_sys(QString());
suffixes.prepend(QString());
// Split up "subdir/filename"
const int slash = fileName.lastIndexOf(QLatin1Char('/'));
const QStringRef baseName = fileName.midRef(slash + 1);
const QStringRef basePath = isAbsolute ? QStringRef() : fileName.leftRef(slash + 1); // keep the '/'
const bool debug = qt_debug_component();
QStringList paths;
if (isAbsolute) {
paths.append(fileName.left(slash)); // don't include the '/'
} else {
paths = QCoreApplication::libraryPaths();
}
for (const QString &path : qAsConst(paths)) {
for (const QString &prefix : qAsConst(prefixes)) {
for (const QString &suffix : qAsConst(suffixes)) {
#ifdef Q_OS_ANDROID
{
QString pluginPath = basePath + prefix + baseName + suffix;
const QString fn = path + QLatin1String("/lib") + pluginPath.replace(QLatin1Char('/'), QLatin1Char('_'));
if (debug)
qDebug() << "Trying..." << fn;
if (QFileInfo(fn).isFile())
return fn;
}
#endif
const QString fn = path + QLatin1Char('/') + basePath + prefix + baseName + suffix;
if (debug)
qDebug() << "Trying..." << fn;
if (QFileInfo(fn).isFile())
return fn;
}
}
}
if (debug)
qDebug() << fileName << "not found";
return QString();
}
QStringList QLibraryPrivate::suffixes_sys(const QString& fullVersion)
{
Q_UNUSED(fullVersion);
return QStringList(QStringLiteral(".dll"));
}
QStringList QLibraryPrivate::prefixes_sys()
{
return QStringList();
}
参考文献
Erich Gamma. Design Patterns:elements of reusable object-oriented software. Addison Wesley, 1994.
Joseph Ingeno. Handbook of Software Architecture.
Gigi Sayfan. Building Your Own Plugin Framework: Part 1. 2007.