1 | // Copyright (C) 2020 The Qt Company Ltd. |
---|---|
2 | // Copyright (C) 2021 Intel Corporation. |
3 | // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only |
4 | #include "qlibrary.h" |
5 | #include "qlibrary_p.h" |
6 | |
7 | #include <q20algorithm.h> |
8 | #include <qbytearraymatcher.h> |
9 | #include <qdebug.h> |
10 | #include <qendian.h> |
11 | #include <qfile.h> |
12 | #include <qfileinfo.h> |
13 | #include <qjsondocument.h> |
14 | #include <qmutex.h> |
15 | #include <qoperatingsystemversion.h> |
16 | #include <qstringlist.h> |
17 | |
18 | #ifdef Q_OS_DARWIN |
19 | # include <private/qcore_mac_p.h> |
20 | #endif |
21 | #include <private/qcoreapplication_p.h> |
22 | #include <private/qloggingregistry_p.h> |
23 | #include <private/qsystemerror_p.h> |
24 | |
25 | #include "qcoffpeparser_p.h" |
26 | #include "qelfparser_p.h" |
27 | #include "qfactoryloader_p.h" |
28 | #include "qmachparser_p.h" |
29 | |
30 | #include <qtcore_tracepoints_p.h> |
31 | |
32 | #include <QtCore/q20map.h> |
33 | |
34 | QT_BEGIN_NAMESPACE |
35 | |
36 | using namespace Qt::StringLiterals; |
37 | |
38 | Q_TRACE_POINT(qtcore, QLibraryPrivate_load_entry, const QString &fileName); |
39 | Q_TRACE_POINT(qtcore, QLibraryPrivate_load_exit, bool success); |
40 | |
41 | // On Unix systema and on Windows with MinGW, we can mix and match debug and |
42 | // release plugins without problems. (unless compiled in debug-and-release mode |
43 | // - why?) |
44 | static constexpr bool PluginMustMatchQtDebug = |
45 | QOperatingSystemVersion::currentType() == QOperatingSystemVersion::Windows |
46 | #if defined(Q_CC_MINGW) |
47 | && QT_CONFIG(debug_and_release) |
48 | #endif |
49 | ; |
50 | |
51 | #ifdef QT_NO_DEBUG |
52 | static constexpr bool QtBuildIsDebug = false; |
53 | #else |
54 | static constexpr bool QtBuildIsDebug = true; |
55 | #endif |
56 | |
57 | Q_LOGGING_CATEGORY_WITH_ENV_OVERRIDE(qt_lcDebugPlugins, "QT_DEBUG_PLUGINS", "qt.core.plugin.loader") |
58 | static Q_LOGGING_CATEGORY_WITH_ENV_OVERRIDE(lcDebugLibrary, "QT_DEBUG_PLUGINS", "qt.core.library") |
59 | |
60 | /*! |
61 | \class QLibrary |
62 | \inmodule QtCore |
63 | \reentrant |
64 | \brief The QLibrary class loads shared libraries at runtime. |
65 | |
66 | |
67 | \ingroup plugins |
68 | |
69 | An instance of a QLibrary object operates on a single shared |
70 | object file (which we call a "library", but is also known as a |
71 | "DLL"). A QLibrary provides access to the functionality in the |
72 | library in a platform independent way. You can either pass a file |
73 | name in the constructor, or set it explicitly with setFileName(). |
74 | When loading the library, QLibrary searches in all the |
75 | system-specific library locations (e.g. \c LD_LIBRARY_PATH on |
76 | Unix), unless the file name has an absolute path. |
77 | |
78 | If the file name is an absolute path then an attempt is made to |
79 | load this path first. If the file cannot be found, QLibrary tries |
80 | the name with different platform-specific file prefixes, like |
81 | "lib" on Unix and Mac, and suffixes, like ".so" on Unix, ".dylib" |
82 | on the Mac, or ".dll" on Windows. |
83 | |
84 | If the file path is not absolute then QLibrary modifies the search |
85 | order to try the system-specific prefixes and suffixes first, |
86 | followed by the file path specified. |
87 | |
88 | This makes it possible to specify shared libraries that are only |
89 | identified by their basename (i.e. without their suffix), so the |
90 | same code will work on different operating systems yet still |
91 | minimise the number of attempts to find the library. |
92 | |
93 | The most important functions are load() to dynamically load the |
94 | library file, isLoaded() to check whether loading was successful, |
95 | and resolve() to resolve a symbol in the library. The resolve() |
96 | function implicitly tries to load the library if it has not been |
97 | loaded yet. Multiple instances of QLibrary can be used to access |
98 | the same physical library. Once loaded, libraries remain in memory |
99 | until the application terminates. You can attempt to unload a |
100 | library using unload(), but if other instances of QLibrary are |
101 | using the same library, the call will fail, and unloading will |
102 | only happen when every instance has called unload(). |
103 | |
104 | A typical use of QLibrary is to resolve an exported symbol in a |
105 | library, and to call the C function that this symbol represents. |
106 | This is called "explicit linking" in contrast to "implicit |
107 | linking", which is done by the link step in the build process when |
108 | linking an executable against a library. |
109 | |
110 | The following code snippet loads a library, resolves the symbol |
111 | "mysymbol", and calls the function if everything succeeded. If |
112 | something goes wrong, e.g. the library file does not exist or the |
113 | symbol is not defined, the function pointer will be \nullptr and |
114 | won't be called. |
115 | |
116 | \snippet code/src_corelib_plugin_qlibrary.cpp 0 |
117 | |
118 | The symbol must be exported as a C function from the library for |
119 | resolve() to work. This means that the function must be wrapped in |
120 | an \c{extern "C"} block if the library is compiled with a C++ |
121 | compiler. On Windows, this also requires the use of a \c dllexport |
122 | macro; see resolve() for the details of how this is done. For |
123 | convenience, there is a static resolve() function which you can |
124 | use if you just want to call a function in a library without |
125 | explicitly loading the library first: |
126 | |
127 | \snippet code/src_corelib_plugin_qlibrary.cpp 1 |
128 | |
129 | \sa QPluginLoader |
130 | */ |
131 | |
132 | /*! |
133 | \enum QLibrary::LoadHint |
134 | |
135 | This enum describes the possible hints that can be used to change the way |
136 | libraries are handled when they are loaded. These values indicate how |
137 | symbols are resolved when libraries are loaded, and are specified using |
138 | the setLoadHints() function. |
139 | |
140 | \value ResolveAllSymbolsHint |
141 | Causes all symbols in a library to be resolved when it is loaded, not |
142 | simply when resolve() is called. |
143 | \value ExportExternalSymbolsHint |
144 | Exports unresolved and external symbols in the library so that they can be |
145 | resolved in other dynamically-loaded libraries loaded later. |
146 | \value LoadArchiveMemberHint |
147 | Allows the file name of the library to specify a particular object file |
148 | within an archive file. |
149 | If this hint is given, the filename of the library consists of |
150 | a path, which is a reference to an archive file, followed by |
151 | a reference to the archive member. |
152 | \value PreventUnloadHint |
153 | Prevents the library from being unloaded from the address space if close() |
154 | is called. The library's static variables are not reinitialized if open() |
155 | is called at a later time. |
156 | \value DeepBindHint |
157 | Instructs the linker to prefer definitions in the loaded library |
158 | over exported definitions in the loading application when resolving |
159 | external symbols in the loaded library. This option is only supported |
160 | on Linux. |
161 | |
162 | \sa loadHints |
163 | */ |
164 | |
165 | static QLibraryScanResult qt_find_pattern(const char *s, qsizetype s_len, QString *errMsg) |
166 | { |
167 | /* |
168 | We used to search from the end of the file so we'd skip the code and find |
169 | the read-only data that usually follows. Unfortunately, in debug builds, |
170 | the debug sections come after and are usually much bigger than everything |
171 | else, making this process slower than necessary with debug plugins. |
172 | |
173 | More importantly, the pattern string may exist in the debug information due |
174 | to it being used in the plugin in the first place. |
175 | */ |
176 | #if defined(Q_OF_ELF) |
177 | return QElfParser::parse(data: {s, s_len}, errMsg); |
178 | #elif defined(Q_OF_MACH_O) |
179 | return QMachOParser::parse(s, s_len, errMsg); |
180 | #elif defined(Q_OS_WIN) |
181 | return QCoffPeParser::parse({s, s_len}, errMsg); |
182 | #else |
183 | # warning "Qt does not know how to efficiently parse your platform's binary format; using slow fall-back." |
184 | #endif |
185 | static constexpr auto matcher = [] { |
186 | // QPluginMetaData::MagicString is not NUL-terminated, but |
187 | // qMakeStaticByteArrayMatcher requires its argument to be, so |
188 | // duplicate here, but statically check we didn't mess up: |
189 | constexpr auto &pattern = "QTMETADATA !"; |
190 | constexpr auto magic = std::string_view(QPluginMetaData::MagicString, |
191 | sizeof(QPluginMetaData::MagicString)); |
192 | static_assert(pattern == magic); |
193 | return qMakeStaticByteArrayMatcher(pattern); |
194 | }(); |
195 | qsizetype i = matcher.indexIn(haystack: {s, s_len}); |
196 | if (i < 0) { |
197 | *errMsg = QLibrary::tr(s: "'%1' is not a Qt plugin").arg(a: *errMsg); |
198 | return QLibraryScanResult{}; |
199 | } |
200 | i += sizeof(QPluginMetaData::MagicString); |
201 | return { .pos: i, .length: s_len - i }; |
202 | } |
203 | |
204 | /* |
205 | This opens the specified library, mmaps it into memory, and searches |
206 | for the QT_PLUGIN_VERIFICATION_DATA. The advantage of this approach is that |
207 | we can get the verification data without have to actually load the library. |
208 | This lets us detect mismatches more safely. |
209 | |
210 | Returns \c false if version information is not present, or if the |
211 | information could not be read. |
212 | Returns true if version information is present and successfully read. |
213 | */ |
214 | static QLibraryScanResult findPatternUnloaded(const QString &library, QLibraryPrivate *lib) |
215 | { |
216 | QFile file(library); |
217 | if (!file.open(flags: QIODevice::ReadOnly)) { |
218 | if (lib) |
219 | lib->errorString = file.errorString(); |
220 | qCWarning(qt_lcDebugPlugins, "%ls: cannot open: %ls", qUtf16Printable(library), |
221 | qUtf16Printable(file.errorString())); |
222 | return {}; |
223 | } |
224 | |
225 | // Files can be bigger than the virtual memory size on 32-bit systems, so |
226 | // we limit to 512 MB there. For 64-bit, we allow up to 2^40 bytes. |
227 | constexpr qint64 MaxMemoryMapSize = |
228 | Q_INT64_C(1) << (sizeof(qsizetype) > 4 ? 40 : 29); |
229 | |
230 | qsizetype fdlen = qMin(a: file.size(), b: MaxMemoryMapSize); |
231 | const char *filedata = reinterpret_cast<char *>(file.map(offset: 0, size: fdlen)); |
232 | |
233 | #ifdef Q_OS_UNIX |
234 | if (filedata == nullptr) { |
235 | // If we can't mmap(), then the dynamic loader won't be able to either. |
236 | // This can't be used as a plugin. |
237 | qCWarning(qt_lcDebugPlugins, "%ls: failed to map to memory: %ls", |
238 | qUtf16Printable(library), qUtf16Printable(file.errorString())); |
239 | return {}; |
240 | } |
241 | #else |
242 | QByteArray data; |
243 | if (filedata == nullptr) { |
244 | // It's unknown at this point whether Windows supports LoadLibrary() on |
245 | // files that fail to CreateFileMapping / MapViewOfFile, so we err on |
246 | // the side of doing a regular read into memory (up to 64 MB). |
247 | data = file.read(64 * 1024 * 1024); |
248 | filedata = data.constData(); |
249 | fdlen = data.size(); |
250 | } |
251 | #endif |
252 | |
253 | QString errMsg = library; |
254 | QLibraryScanResult r = qt_find_pattern(s: filedata, s_len: fdlen, errMsg: &errMsg); |
255 | if (r.length) { |
256 | #if defined(Q_OF_MACH_O) |
257 | if (r.isEncrypted) |
258 | return r; |
259 | #endif |
260 | if (!lib->metaData.parse(input: QByteArrayView(filedata + r.pos, r.length))) { |
261 | errMsg = lib->metaData.errorString(); |
262 | qCDebug(qt_lcDebugPlugins, "Found invalid metadata in lib %ls: %ls", |
263 | qUtf16Printable(library), qUtf16Printable(errMsg)); |
264 | } else { |
265 | qCDebug(qt_lcDebugPlugins, "Found metadata in lib %ls, metadata=\n%s\n", |
266 | qUtf16Printable(library), |
267 | QJsonDocument(lib->metaData.toJson()).toJson().constData()); |
268 | return r; |
269 | } |
270 | } else { |
271 | qCDebug(qt_lcDebugPlugins, "Failed to find metadata in lib %ls: %ls", |
272 | qUtf16Printable(library), qUtf16Printable(errMsg)); |
273 | } |
274 | |
275 | lib->errorString = QLibrary::tr(s: "Failed to extract plugin meta data from '%1': %2") |
276 | .arg(args: library, args&: errMsg); |
277 | return {}; |
278 | } |
279 | |
280 | static void installCoverageTool(QLibraryPrivate *libPrivate) |
281 | { |
282 | #ifdef __COVERAGESCANNER__ |
283 | /* |
284 | __COVERAGESCANNER__ is defined when Qt has been instrumented for code |
285 | coverage by TestCocoon. CoverageScanner is the name of the tool that |
286 | generates the code instrumentation. |
287 | This code is required here when code coverage analysis with TestCocoon |
288 | is enabled in order to allow the loading application to register the plugin |
289 | and then store its execution report. The execution report gathers information |
290 | about each part of the plugin's code that has been used when |
291 | the plugin was loaded by the launching application. |
292 | The execution report for the plugin will go to the same execution report |
293 | as the one defined for the application loading it. |
294 | */ |
295 | |
296 | int ret = __coveragescanner_register_library(libPrivate->fileName.toLocal8Bit()); |
297 | |
298 | if (ret >= 0) { |
299 | qDebug("coverage data for %ls registered", |
300 | qUtf16Printable(libPrivate->fileName)); |
301 | } else { |
302 | qWarning("could not register %ls: error %d; coverage data may be incomplete", |
303 | qUtf16Printable(libPrivate->fileName), |
304 | ret); |
305 | } |
306 | } |
307 | #else |
308 | Q_UNUSED(libPrivate); |
309 | #endif |
310 | } |
311 | |
312 | class QLibraryStore |
313 | { |
314 | public: |
315 | inline ~QLibraryStore(); |
316 | static inline QLibraryPrivate *findOrCreate(const QString &fileName, const QString &version, QLibrary::LoadHints loadHints); |
317 | static inline void releaseLibrary(QLibraryPrivate *lib); |
318 | |
319 | static inline void cleanup(); |
320 | |
321 | private: |
322 | static inline QLibraryStore *instance(); |
323 | |
324 | // all members and instance() are protected by qt_library_mutex |
325 | typedef std::map<QString, QLibraryPrivate *> LibraryMap; |
326 | LibraryMap libraryMap; |
327 | }; |
328 | |
329 | Q_CONSTINIT static QBasicMutex qt_library_mutex; |
330 | Q_CONSTINIT static QLibraryStore *qt_library_data = nullptr; |
331 | Q_CONSTINIT static bool qt_library_data_once; |
332 | |
333 | QLibraryStore::~QLibraryStore() |
334 | { |
335 | qt_library_data = nullptr; |
336 | } |
337 | |
338 | inline void QLibraryStore::cleanup() |
339 | { |
340 | QLibraryStore *data = qt_library_data; |
341 | if (!data) |
342 | return; |
343 | |
344 | // find any libraries that are still loaded but have a no one attached to them |
345 | for (auto &[_, lib] : data->libraryMap) { |
346 | if (lib->libraryRefCount.loadRelaxed() == 1) { |
347 | if (lib->libraryUnloadCount.loadRelaxed() > 0) { |
348 | Q_ASSERT(lib->pHnd.loadRelaxed()); |
349 | lib->libraryUnloadCount.storeRelaxed(newValue: 1); |
350 | #if defined(Q_OS_DARWIN) |
351 | // We cannot fully unload libraries, as we don't know if there are |
352 | // lingering references (in system threads e.g.) to Objective-C classes |
353 | // defined in the library. |
354 | lib->unload(QLibraryPrivate::NoUnloadSys); |
355 | #else |
356 | lib->unload(); |
357 | #endif |
358 | } |
359 | delete std::exchange(obj&: lib, new_val: nullptr); |
360 | } |
361 | } |
362 | |
363 | // dump all objects that remain |
364 | if (lcDebugLibrary().isDebugEnabled()) { |
365 | for (auto &[_, lib] : data->libraryMap) { |
366 | if (lib) |
367 | qCDebug(lcDebugLibrary) |
368 | << "On QtCore unload,"<< lib->fileName << "was leaked, with" |
369 | << lib->libraryRefCount.loadRelaxed() << "users"; |
370 | } |
371 | } |
372 | |
373 | delete data; |
374 | } |
375 | |
376 | static void qlibraryCleanup() |
377 | { |
378 | QLibraryStore::cleanup(); |
379 | } |
380 | Q_DESTRUCTOR_FUNCTION(qlibraryCleanup) |
381 | |
382 | // must be called with a locked mutex |
383 | QLibraryStore *QLibraryStore::instance() |
384 | { |
385 | if (Q_UNLIKELY(!qt_library_data_once && !qt_library_data)) { |
386 | // only create once per process lifetime |
387 | qt_library_data = new QLibraryStore; |
388 | qt_library_data_once = true; |
389 | } |
390 | return qt_library_data; |
391 | } |
392 | |
393 | inline QLibraryPrivate *QLibraryStore::findOrCreate(const QString &fileName, const QString &version, |
394 | QLibrary::LoadHints loadHints) |
395 | { |
396 | auto lazyNewLib = [&] { |
397 | auto result = new QLibraryPrivate(fileName, version, loadHints); |
398 | result->libraryRefCount.ref(); |
399 | return result; |
400 | }; |
401 | |
402 | if (fileName.isEmpty()) // request for empty d-pointer in QLibrary::setLoadHints(); |
403 | return lazyNewLib(); // must return an independent (new) object |
404 | |
405 | QMutexLocker locker(&qt_library_mutex); |
406 | QLibraryStore *data = instance(); |
407 | |
408 | if (Q_UNLIKELY(!data)) { |
409 | locker.unlock(); |
410 | return lazyNewLib(); |
411 | } |
412 | |
413 | QString mapName = version.isEmpty() ? fileName : fileName + u'\0' + version; |
414 | |
415 | QLibraryPrivate *&lib = data->libraryMap[std::move(mapName)]; |
416 | if (lib) { |
417 | // already loaded |
418 | lib->libraryRefCount.ref(); |
419 | lib->mergeLoadHints(loadHints); |
420 | } else { |
421 | lib = lazyNewLib(); |
422 | } |
423 | |
424 | return lib; |
425 | } |
426 | |
427 | inline void QLibraryStore::releaseLibrary(QLibraryPrivate *lib) |
428 | { |
429 | QMutexLocker locker(&qt_library_mutex); |
430 | QLibraryStore *data = instance(); |
431 | |
432 | if (lib->libraryRefCount.deref()) { |
433 | // still in use |
434 | return; |
435 | } |
436 | |
437 | // no one else is using |
438 | Q_ASSERT(lib->libraryUnloadCount.loadRelaxed() == 0); |
439 | |
440 | if (Q_LIKELY(data) && !lib->fileName.isEmpty()) { |
441 | using q20::erase_if; |
442 | const auto n = erase_if(c&: data->libraryMap, p: [lib](const auto &e) { |
443 | return e.second == lib; |
444 | }); |
445 | Q_ASSERT_X(n, "~QLibrary", "Did not find this library in the library map"); |
446 | Q_UNUSED(n); |
447 | } |
448 | delete lib; |
449 | } |
450 | |
451 | QLibraryPrivate::QLibraryPrivate(const QString &canonicalFileName, const QString &version, QLibrary::LoadHints loadHints) |
452 | : fileName(canonicalFileName), fullVersion(version), pluginState(MightBeAPlugin) |
453 | { |
454 | loadHintsInt.storeRelaxed(newValue: loadHints.toInt()); |
455 | if (canonicalFileName.isEmpty()) |
456 | errorString = QLibrary::tr(s: "The shared library was not found."); |
457 | } |
458 | |
459 | QLibraryPrivate *QLibraryPrivate::findOrCreate(const QString &fileName, const QString &version, |
460 | QLibrary::LoadHints loadHints) |
461 | { |
462 | return QLibraryStore::findOrCreate(fileName, version, loadHints); |
463 | } |
464 | |
465 | QLibraryPrivate::~QLibraryPrivate() |
466 | { |
467 | } |
468 | |
469 | void QLibraryPrivate::mergeLoadHints(QLibrary::LoadHints lh) |
470 | { |
471 | // if the library is already loaded, we can't change the load hints |
472 | if (pHnd.loadRelaxed()) |
473 | return; |
474 | |
475 | loadHintsInt.fetchAndOrRelaxed(valueToAdd: lh.toInt()); |
476 | } |
477 | |
478 | QFunctionPointer QLibraryPrivate::resolve(const char *symbol) |
479 | { |
480 | if (!pHnd.loadRelaxed()) |
481 | return nullptr; |
482 | return resolve_sys(symbol); |
483 | } |
484 | |
485 | void QLibraryPrivate::setLoadHints(QLibrary::LoadHints lh) |
486 | { |
487 | // Set the load hints directly for a dummy if this object is not associated |
488 | // with a file. Such object is not shared between multiple instances. |
489 | if (fileName.isEmpty()) { |
490 | loadHintsInt.storeRelaxed(newValue: lh.toInt()); |
491 | return; |
492 | } |
493 | |
494 | // this locks a global mutex |
495 | QMutexLocker lock(&qt_library_mutex); |
496 | mergeLoadHints(lh); |
497 | } |
498 | |
499 | QObject *QLibraryPrivate::pluginInstance() |
500 | { |
501 | // first, check if the instance is cached and hasn't been deleted |
502 | QObject *obj = [&](){ QMutexLocker locker(&mutex); return inst.data(); }(); |
503 | if (obj) |
504 | return obj; |
505 | |
506 | // We need to call the plugin's factory function. Is that cached? |
507 | // skip increasing the reference count (why? -Thiago) |
508 | QtPluginInstanceFunction factory = instanceFactory.loadAcquire(); |
509 | if (!factory) |
510 | factory = loadPlugin(); |
511 | |
512 | if (!factory) |
513 | return nullptr; |
514 | |
515 | obj = factory(); |
516 | |
517 | // cache again |
518 | QMutexLocker locker(&mutex); |
519 | if (inst) |
520 | obj = inst; |
521 | else |
522 | inst = obj; |
523 | return obj; |
524 | } |
525 | |
526 | bool QLibraryPrivate::load() |
527 | { |
528 | if (pHnd.loadRelaxed()) { |
529 | libraryUnloadCount.ref(); |
530 | return true; |
531 | } |
532 | if (fileName.isEmpty()) |
533 | return false; |
534 | |
535 | Q_TRACE(QLibraryPrivate_load_entry, fileName); |
536 | |
537 | bool ret = load_sys(); |
538 | qCDebug(lcDebugLibrary) |
539 | << fileName |
540 | << (ret ? "loaded library": qUtf8Printable(u "cannot load: "+ errorString)); |
541 | if (ret) { |
542 | //when loading a library we add a reference to it so that the QLibraryPrivate won't get deleted |
543 | //this allows to unload the library at a later time |
544 | libraryUnloadCount.ref(); |
545 | libraryRefCount.ref(); |
546 | installCoverageTool(libPrivate: this); |
547 | } |
548 | |
549 | Q_TRACE(QLibraryPrivate_load_exit, ret); |
550 | |
551 | return ret; |
552 | } |
553 | |
554 | bool QLibraryPrivate::unload(UnloadFlag flag) |
555 | { |
556 | if (!pHnd.loadRelaxed()) |
557 | return false; |
558 | if (libraryUnloadCount.loadRelaxed() > 0 && !libraryUnloadCount.deref()) { // only unload if ALL QLibrary instance wanted to |
559 | QMutexLocker locker(&mutex); |
560 | delete inst.data(); |
561 | if (flag == NoUnloadSys || unload_sys()) { |
562 | qCDebug(lcDebugLibrary) << fileName << "unloaded library" |
563 | << (flag == NoUnloadSys ? "(faked)": ""); |
564 | // when the library is unloaded, we release the reference on it so that 'this' |
565 | // can get deleted |
566 | libraryRefCount.deref(); |
567 | pHnd.storeRelaxed(newValue: nullptr); |
568 | instanceFactory.storeRelaxed(newValue: nullptr); |
569 | return true; |
570 | } |
571 | } |
572 | |
573 | return false; |
574 | } |
575 | |
576 | void QLibraryPrivate::release() |
577 | { |
578 | QLibraryStore::releaseLibrary(lib: this); |
579 | } |
580 | |
581 | QtPluginInstanceFunction QLibraryPrivate::loadPlugin() |
582 | { |
583 | if (auto ptr = instanceFactory.loadAcquire()) { |
584 | libraryUnloadCount.ref(); |
585 | return ptr; |
586 | } |
587 | if (pluginState == IsNotAPlugin) |
588 | return nullptr; |
589 | if (load()) { |
590 | auto ptr = reinterpret_cast<QtPluginInstanceFunction>(resolve(symbol: "qt_plugin_instance")); |
591 | instanceFactory.storeRelease(newValue: ptr); // two threads may store the same value |
592 | if (Q_LIKELY(ptr)) |
593 | return ptr; |
594 | errorString = QLibrary::tr(s: "Could not resolve 'qt_plugin_instance' function"); |
595 | } |
596 | qCDebug(qt_lcDebugPlugins) << "QLibraryPrivate::loadPlugin failed on"<< fileName << ":"<< errorString; |
597 | pluginState = IsNotAPlugin; |
598 | return nullptr; |
599 | } |
600 | |
601 | /*! |
602 | Returns \c true if \a fileName has a valid suffix for a loadable |
603 | library; otherwise returns \c false. |
604 | |
605 | \table |
606 | \header \li Platform \li Valid suffixes |
607 | \row \li Windows \li \c .dll, \c .DLL |
608 | \row \li Unix/Linux \li \c .so |
609 | \row \li AIX \li \c .a |
610 | \row \li HP-UX \li \c .sl, \c .so (HP-UXi) |
611 | \row \li \macos and iOS \li \c .dylib, \c .bundle, \c .so |
612 | \endtable |
613 | |
614 | Trailing versioning numbers on Unix are ignored. |
615 | */ |
616 | bool QLibrary::isLibrary(const QString &fileName) |
617 | { |
618 | #if defined(Q_OS_WIN) |
619 | return fileName.endsWith(".dll"_L1, Qt::CaseInsensitive); |
620 | #else // Generic Unix |
621 | # if defined(Q_OS_DARWIN) |
622 | // On Apple platforms, dylib look like libmylib.1.0.0.dylib |
623 | if (fileName.endsWith(".dylib"_L1)) |
624 | return true; |
625 | # endif |
626 | QString completeSuffix = QFileInfo(fileName).completeSuffix(); |
627 | if (completeSuffix.isEmpty()) |
628 | return false; |
629 | |
630 | // if this throws an empty-array error, you need to fix the #ifdef's: |
631 | const QLatin1StringView candidates[] = { |
632 | # if defined(Q_OS_HPUX) |
633 | /* |
634 | See "HP-UX Linker and Libraries User's Guide", section "Link-time Differences between PA-RISC and IPF": |
635 | "In PA-RISC (PA-32 and PA-64) shared libraries are suffixed with .sl. In IPF (32-bit and 64-bit), |
636 | the shared libraries are suffixed with .so. For compatibility, the IPF linker also supports the .sl suffix." |
637 | */ |
638 | "sl"_L1, |
639 | # if defined __ia64 |
640 | "so"_L1, |
641 | # endif |
642 | # elif defined(Q_OS_AIX) |
643 | "a"_L1, |
644 | "so"_L1, |
645 | # elif defined(Q_OS_DARWIN) |
646 | "so"_L1, |
647 | "bundle"_L1, |
648 | # elif defined(Q_OS_UNIX) |
649 | "so"_L1, |
650 | # endif |
651 | }; // candidates |
652 | |
653 | auto isValidSuffix = [&candidates](QStringView s) { |
654 | return std::find(first: std::begin(arr: candidates), last: std::end(arr: candidates), val: s) != std::end(arr: candidates); |
655 | }; |
656 | |
657 | // Examples of valid library names: |
658 | // libfoo.so |
659 | // libfoo.so.0 |
660 | // libfoo.so.0.3 |
661 | // libfoo-0.3.so |
662 | // libfoo-0.3.so.0.3.0 |
663 | |
664 | auto suffixes = qTokenize(h&: completeSuffix, n: u'.'); |
665 | auto it = suffixes.begin(); |
666 | const auto end = suffixes.end(); |
667 | |
668 | auto isNumeric = [](QStringView s) { bool ok; (void)s.toInt(ok: &ok); return ok; }; |
669 | |
670 | while (it != end) { |
671 | if (isValidSuffix(*it++)) |
672 | return q20::ranges::all_of(it, end, isNumeric); |
673 | } |
674 | return false; // no valid suffix found |
675 | #endif |
676 | } |
677 | |
678 | static bool qt_get_metadata(QLibraryPrivate *priv, QString *errMsg) |
679 | { |
680 | auto error = [=](QString &&explanation) { |
681 | *errMsg = QLibrary::tr(s: "'%1' is not a Qt plugin (%2)").arg(args: priv->fileName, args: std::move(explanation)); |
682 | return false; |
683 | }; |
684 | |
685 | QPluginMetaData metaData; |
686 | QFunctionPointer pfn = priv->resolve(symbol: "qt_plugin_query_metadata_v2"); |
687 | if (pfn) { |
688 | metaData = reinterpret_cast<QPluginMetaData (*)()>(pfn)(); |
689 | #if QT_VERSION <= QT_VERSION_CHECK(7, 0, 0) |
690 | } else if ((pfn = priv->resolve(symbol: "qt_plugin_query_metadata"))) { |
691 | metaData = reinterpret_cast<QPluginMetaData (*)()>(pfn)(); |
692 | if (metaData.size < sizeof(QPluginMetaData::MagicHeader)) |
693 | return error(QLibrary::tr(s: "metadata too small")); |
694 | |
695 | // adjust the meta data to point to the header |
696 | auto data = reinterpret_cast<const char *>(metaData.data); |
697 | data += sizeof(QPluginMetaData::MagicString); |
698 | metaData.data = data; |
699 | metaData.size -= sizeof(QPluginMetaData::MagicString); |
700 | #endif |
701 | } else { |
702 | return error(QLibrary::tr(s: "entrypoint to query the plugin meta data not found")); |
703 | } |
704 | |
705 | if (metaData.size < sizeof(QPluginMetaData::Header)) |
706 | return error(QLibrary::tr(s: "metadata too small")); |
707 | |
708 | if (priv->metaData.parse(metaData)) |
709 | return true; |
710 | *errMsg = priv->metaData.errorString(); |
711 | return false; |
712 | } |
713 | |
714 | bool QLibraryPrivate::isPlugin() |
715 | { |
716 | if (pluginState == MightBeAPlugin) |
717 | updatePluginState(); |
718 | |
719 | return pluginState == IsAPlugin; |
720 | } |
721 | |
722 | void QLibraryPrivate::updatePluginState() |
723 | { |
724 | QMutexLocker locker(&mutex); |
725 | errorString.clear(); |
726 | if (pluginState != MightBeAPlugin) |
727 | return; |
728 | |
729 | bool success = false; |
730 | |
731 | #if defined(Q_OS_UNIX) && !defined(Q_OS_DARWIN) |
732 | if (fileName.endsWith(s: ".debug"_L1)) { |
733 | // refuse to load a file that ends in .debug |
734 | // these are the debug symbols from the libraries |
735 | // the problem is that they are valid shared library files |
736 | // and dlopen is known to crash while opening them |
737 | |
738 | // pretend we didn't see the file |
739 | errorString = QLibrary::tr(s: "The shared library was not found."); |
740 | pluginState = IsNotAPlugin; |
741 | return; |
742 | } |
743 | #endif |
744 | |
745 | if (!pHnd.loadRelaxed()) { |
746 | // scan for the plugin metadata without loading |
747 | QLibraryScanResult result = findPatternUnloaded(library: fileName, lib: this); |
748 | #if defined(Q_OF_MACH_O) |
749 | if (result.length && result.isEncrypted) { |
750 | // We found the .qtmetadata section, but since the library is encrypted |
751 | // we need to dlopen() it before we can parse the metadata for further |
752 | // validation. |
753 | qCDebug(qt_lcDebugPlugins, "Library is encrypted. Doing prospective load before parsing metadata"); |
754 | locker.unlock(); |
755 | load(); |
756 | locker.relock(); |
757 | success = qt_get_metadata(this, &errorString); |
758 | } else |
759 | #endif |
760 | { |
761 | success = result.length != 0; |
762 | } |
763 | } else { |
764 | // library is already loaded (probably via QLibrary) |
765 | // simply get the target function and call it. |
766 | success = qt_get_metadata(priv: this, errMsg: &errorString); |
767 | } |
768 | |
769 | if (!success) { |
770 | if (errorString.isEmpty()) { |
771 | if (fileName.isEmpty()) |
772 | errorString = QLibrary::tr(s: "The shared library was not found."); |
773 | else |
774 | errorString = QLibrary::tr(s: "The file '%1' is not a valid Qt plugin.").arg(a: fileName); |
775 | } |
776 | pluginState = IsNotAPlugin; |
777 | return; |
778 | } |
779 | |
780 | pluginState = IsNotAPlugin; // be pessimistic |
781 | |
782 | uint qt_version = uint(metaData.value(k: QtPluginMetaDataKeys::QtVersion).toInteger()); |
783 | bool debug = metaData.value(k: QtPluginMetaDataKeys::IsDebug).toBool(); |
784 | if ((qt_version & 0x00ff00) > (QT_VERSION & 0x00ff00) || (qt_version & 0xff0000) != (QT_VERSION & 0xff0000)) { |
785 | qCDebug(qt_lcDebugPlugins, "In %s:\n" |
786 | " Plugin uses incompatible Qt library (%d.%d.%d) [%s]", |
787 | QFile::encodeName(fileName).constData(), |
788 | (qt_version&0xff0000) >> 16, (qt_version&0xff00) >> 8, qt_version&0xff, |
789 | debug ? "debug": "release"); |
790 | errorString = QLibrary::tr(s: "The plugin '%1' uses incompatible Qt library. (%2.%3.%4) [%5]") |
791 | .arg(args: fileName, |
792 | args: QString::number((qt_version & 0xff0000) >> 16), |
793 | args: QString::number((qt_version & 0xff00) >> 8), |
794 | args: QString::number(qt_version & 0xff), |
795 | args: debug ? "debug"_L1: "release"_L1); |
796 | } else if (PluginMustMatchQtDebug && debug != QtBuildIsDebug) { |
797 | //don't issue a qWarning since we will hopefully find a non-debug? --Sam |
798 | errorString = QLibrary::tr(s: "The plugin '%1' uses incompatible Qt library." |
799 | " (Cannot mix debug and release libraries.)").arg(a: fileName); |
800 | } else { |
801 | pluginState = IsAPlugin; |
802 | } |
803 | } |
804 | |
805 | /*! |
806 | Loads the library and returns \c true if the library was loaded |
807 | successfully; otherwise returns \c false. Since resolve() always |
808 | calls this function before resolving any symbols it is not |
809 | necessary to call it explicitly. In some situations you might want |
810 | the library loaded in advance, in which case you would use this |
811 | function. |
812 | |
813 | \sa unload() |
814 | */ |
815 | bool QLibrary::load() |
816 | { |
817 | if (!d) |
818 | return false; |
819 | if (d.tag() == Loaded) |
820 | return d->pHnd.loadRelaxed(); |
821 | if (d->load()) { |
822 | d.setTag(Loaded); |
823 | return true; |
824 | } |
825 | return false; |
826 | } |
827 | |
828 | /*! |
829 | Unloads the library and returns \c true if the library could be |
830 | unloaded; otherwise returns \c false. |
831 | |
832 | This happens automatically on application termination, so you |
833 | shouldn't normally need to call this function. |
834 | |
835 | If other instances of QLibrary are using the same library, the |
836 | call will fail, and unloading will only happen when every instance |
837 | has called unload(). |
838 | |
839 | Note that on \macos, dynamic libraries cannot be unloaded. |
840 | QLibrary::unload() will return \c true, but the library will remain |
841 | loaded into the process. |
842 | |
843 | \sa resolve(), load() |
844 | */ |
845 | bool QLibrary::unload() |
846 | { |
847 | if (d.tag() == Loaded) { |
848 | d.setTag(NotLoaded); |
849 | return d->unload(); |
850 | } |
851 | return false; |
852 | } |
853 | |
854 | /*! |
855 | Returns \c true if load() succeeded; otherwise returns \c false. |
856 | |
857 | \note Prior to Qt 6.6, this function would return \c true even without a |
858 | call to load() if another QLibrary object on the same library had caused it |
859 | to be loaded. |
860 | |
861 | \sa load() |
862 | */ |
863 | bool QLibrary::isLoaded() const |
864 | { |
865 | return d.tag() == Loaded; |
866 | } |
867 | |
868 | |
869 | /*! |
870 | Constructs a library with the given \a parent. |
871 | */ |
872 | QLibrary::QLibrary(QObject *parent) : QObject(parent) |
873 | { |
874 | } |
875 | |
876 | |
877 | /*! |
878 | Constructs a library object with the given \a parent that will |
879 | load the library specified by \a fileName. |
880 | |
881 | We recommend omitting the file's suffix in \a fileName, since |
882 | QLibrary will automatically look for the file with the appropriate |
883 | suffix in accordance with the platform, e.g. ".so" on Unix, |
884 | ".dylib" on \macos and iOS, and ".dll" on Windows. (See \l{fileName}.) |
885 | */ |
886 | QLibrary::QLibrary(const QString &fileName, QObject *parent) : QObject(parent) |
887 | { |
888 | setFileName(fileName); |
889 | } |
890 | |
891 | /*! |
892 | Constructs a library object with the given \a parent that will |
893 | load the library specified by \a fileName and major version number \a verNum. |
894 | Currently, the version number is ignored on Windows. |
895 | |
896 | We recommend omitting the file's suffix in \a fileName, since |
897 | QLibrary will automatically look for the file with the appropriate |
898 | suffix in accordance with the platform, e.g. ".so" on Unix, |
899 | ".dylib" on \macos and iOS, and ".dll" on Windows. (See \l{fileName}.) |
900 | */ |
901 | QLibrary::QLibrary(const QString &fileName, int verNum, QObject *parent) : QObject(parent) |
902 | { |
903 | setFileNameAndVersion(fileName, verNum); |
904 | } |
905 | |
906 | /*! |
907 | Constructs a library object with the given \a parent that will |
908 | load the library specified by \a fileName and full version number \a version. |
909 | Currently, the version number is ignored on Windows. |
910 | |
911 | We recommend omitting the file's suffix in \a fileName, since |
912 | QLibrary will automatically look for the file with the appropriate |
913 | suffix in accordance with the platform, e.g. ".so" on Unix, |
914 | ".dylib" on \macos and iOS, and ".dll" on Windows. (See \l{fileName}.) |
915 | */ |
916 | QLibrary::QLibrary(const QString &fileName, const QString &version, QObject *parent) |
917 | : QObject(parent) |
918 | { |
919 | setFileNameAndVersion(fileName, version); |
920 | } |
921 | |
922 | /*! |
923 | Destroys the QLibrary object. |
924 | |
925 | Unless unload() was called explicitly, the library stays in memory |
926 | until the application terminates. |
927 | |
928 | \sa isLoaded(), unload() |
929 | */ |
930 | QLibrary::~QLibrary() |
931 | { |
932 | if (d) |
933 | d->release(); |
934 | } |
935 | |
936 | /*! |
937 | \property QLibrary::fileName |
938 | \brief the file name of the library |
939 | |
940 | We recommend omitting the file's suffix in the file name, since |
941 | QLibrary will automatically look for the file with the appropriate |
942 | suffix (see isLibrary()). |
943 | |
944 | When loading the library, QLibrary searches in all system-specific |
945 | library locations (for example, \c LD_LIBRARY_PATH on Unix), unless the |
946 | file name has an absolute path. After loading the library |
947 | successfully, fileName() returns the fully-qualified file name of |
948 | the library, including the full path to the library if one was given |
949 | in the constructor or passed to setFileName(). |
950 | |
951 | For example, after successfully loading the "GL" library on Unix |
952 | platforms, fileName() will return "libGL.so". If the file name was |
953 | originally passed as "/usr/lib/libGL", fileName() will return |
954 | "/usr/lib/libGL.so". |
955 | */ |
956 | |
957 | void QLibrary::setFileName(const QString &fileName) |
958 | { |
959 | setFileNameAndVersion(fileName, version: QString()); |
960 | } |
961 | |
962 | QString QLibrary::fileName() const |
963 | { |
964 | if (d) { |
965 | QMutexLocker locker(&d->mutex); |
966 | return d->qualifiedFileName.isEmpty() ? d->fileName : d->qualifiedFileName; |
967 | } |
968 | return QString(); |
969 | } |
970 | |
971 | /*! |
972 | \fn void QLibrary::setFileNameAndVersion(const QString &fileName, int versionNumber) |
973 | |
974 | Sets the fileName property and major version number to \a fileName |
975 | and \a versionNumber respectively. |
976 | The \a versionNumber is ignored on Windows. |
977 | |
978 | \sa setFileName() |
979 | */ |
980 | void QLibrary::setFileNameAndVersion(const QString &fileName, int verNum) |
981 | { |
982 | setFileNameAndVersion(fileName, version: verNum >= 0 ? QString::number(verNum) : QString()); |
983 | } |
984 | |
985 | /*! |
986 | \since 4.4 |
987 | |
988 | Sets the fileName property and full version number to \a fileName |
989 | and \a version respectively. |
990 | The \a version parameter is ignored on Windows. |
991 | |
992 | \sa setFileName() |
993 | */ |
994 | void QLibrary::setFileNameAndVersion(const QString &fileName, const QString &version) |
995 | { |
996 | QLibrary::LoadHints lh; |
997 | if (d) { |
998 | lh = d->loadHints(); |
999 | d->release(); |
1000 | } |
1001 | QLibraryPrivate *dd = QLibraryPrivate::findOrCreate(fileName, version, loadHints: lh); |
1002 | d = QTaggedPointer(dd, NotLoaded); // we haven't load()ed |
1003 | } |
1004 | |
1005 | /*! |
1006 | Returns the address of the exported symbol \a symbol. The library is |
1007 | loaded if necessary. The function returns \nullptr if the symbol could |
1008 | not be resolved or if the library could not be loaded. |
1009 | |
1010 | Example: |
1011 | \snippet code/src_corelib_plugin_qlibrary.cpp 2 |
1012 | |
1013 | The symbol must be exported as a C function from the library. This |
1014 | means that the function must be wrapped in an \c{extern "C"} if |
1015 | the library is compiled with a C++ compiler. On Windows you must |
1016 | also explicitly export the function from the DLL using the |
1017 | \c{__declspec(dllexport)} compiler directive, for example: |
1018 | |
1019 | \snippet code/src_corelib_plugin_qlibrary.cpp 3 |
1020 | |
1021 | with \c MY_EXPORT defined as |
1022 | |
1023 | \snippet code/src_corelib_plugin_qlibrary.cpp 4 |
1024 | */ |
1025 | QFunctionPointer QLibrary::resolve(const char *symbol) |
1026 | { |
1027 | if (!isLoaded() && !load()) |
1028 | return nullptr; |
1029 | return d->resolve(symbol); |
1030 | } |
1031 | |
1032 | /*! |
1033 | \overload |
1034 | |
1035 | Loads the library \a fileName and returns the address of the |
1036 | exported symbol \a symbol. Note that \a fileName should not |
1037 | include the platform-specific file suffix; (see \l{fileName}). The |
1038 | library remains loaded until the application exits. |
1039 | |
1040 | The function returns \nullptr if the symbol could not be resolved or if |
1041 | the library could not be loaded. |
1042 | |
1043 | \sa resolve() |
1044 | */ |
1045 | QFunctionPointer QLibrary::resolve(const QString &fileName, const char *symbol) |
1046 | { |
1047 | QLibrary library(fileName); |
1048 | return library.resolve(symbol); |
1049 | } |
1050 | |
1051 | /*! |
1052 | \overload |
1053 | |
1054 | Loads the library \a fileName with major version number \a verNum and |
1055 | returns the address of the exported symbol \a symbol. |
1056 | Note that \a fileName should not include the platform-specific file suffix; |
1057 | (see \l{fileName}). The library remains loaded until the application exits. |
1058 | \a verNum is ignored on Windows. |
1059 | |
1060 | The function returns \nullptr if the symbol could not be resolved or if |
1061 | the library could not be loaded. |
1062 | |
1063 | \sa resolve() |
1064 | */ |
1065 | QFunctionPointer QLibrary::resolve(const QString &fileName, int verNum, const char *symbol) |
1066 | { |
1067 | QLibrary library(fileName, verNum); |
1068 | return library.resolve(symbol); |
1069 | } |
1070 | |
1071 | /*! |
1072 | \overload |
1073 | \since 4.4 |
1074 | |
1075 | Loads the library \a fileName with full version number \a version and |
1076 | returns the address of the exported symbol \a symbol. |
1077 | Note that \a fileName should not include the platform-specific file suffix; |
1078 | (see \l{fileName}). The library remains loaded until the application exits. |
1079 | \a version is ignored on Windows. |
1080 | |
1081 | The function returns \nullptr if the symbol could not be resolved or if |
1082 | the library could not be loaded. |
1083 | |
1084 | \sa resolve() |
1085 | */ |
1086 | QFunctionPointer QLibrary::resolve(const QString &fileName, const QString &version, const char *symbol) |
1087 | { |
1088 | QLibrary library(fileName, version); |
1089 | return library.resolve(symbol); |
1090 | } |
1091 | |
1092 | /*! |
1093 | \since 4.2 |
1094 | |
1095 | Returns a text string with the description of the last error that occurred. |
1096 | Currently, errorString will only be set if load(), unload() or resolve() for some reason fails. |
1097 | */ |
1098 | QString QLibrary::errorString() const |
1099 | { |
1100 | QString str; |
1101 | if (d) { |
1102 | QMutexLocker locker(&d->mutex); |
1103 | str = d->errorString; |
1104 | } |
1105 | return str.isEmpty() ? tr(s: "Unknown error") : str; |
1106 | } |
1107 | |
1108 | /*! |
1109 | \property QLibrary::loadHints |
1110 | \brief Give the load() function some hints on how it should behave. |
1111 | |
1112 | You can give some hints on how the symbols are resolved. Usually, |
1113 | the symbols are not resolved at load time, but resolved lazily, |
1114 | (that is, when resolve() is called). If you set the loadHints to |
1115 | ResolveAllSymbolsHint, then all symbols will be resolved at load time |
1116 | if the platform supports it. |
1117 | |
1118 | Setting ExportExternalSymbolsHint will make the external symbols in the |
1119 | library available for resolution in subsequent loaded libraries. |
1120 | |
1121 | If LoadArchiveMemberHint is set, the file name |
1122 | is composed of two components: A path which is a reference to an |
1123 | archive file followed by the second component which is the reference to |
1124 | the archive member. For instance, the fileName \c libGL.a(shr_64.o) will refer |
1125 | to the library \c shr_64.o in the archive file named \c libGL.a. This |
1126 | is only supported on the AIX platform. |
1127 | |
1128 | The interpretation of the load hints is platform dependent, and if |
1129 | you use it you are probably making some assumptions on which platform |
1130 | you are compiling for, so use them only if you understand the consequences |
1131 | of them. |
1132 | |
1133 | By default, none of these flags are set, so libraries will be loaded with |
1134 | lazy symbol resolution, and will not export external symbols for resolution |
1135 | in other dynamically-loaded libraries. |
1136 | |
1137 | \note Hints can only be cleared when this object is not associated with a |
1138 | file. Hints can only be added once the file name is set (\a hints will |
1139 | be or'ed with the old hints). |
1140 | |
1141 | \note Setting this property after the library has been loaded has no effect |
1142 | and loadHints() will not reflect those changes. |
1143 | |
1144 | \note This property is shared among all QLibrary instances that refer to |
1145 | the same library. |
1146 | */ |
1147 | void QLibrary::setLoadHints(LoadHints hints) |
1148 | { |
1149 | if (!d) { |
1150 | d = QLibraryPrivate::findOrCreate(fileName: QString()); // ugly, but we need a d-ptr |
1151 | d->errorString.clear(); |
1152 | } |
1153 | d->setLoadHints(hints); |
1154 | } |
1155 | |
1156 | QLibrary::LoadHints QLibrary::loadHints() const |
1157 | { |
1158 | return d ? d->loadHints() : QLibrary::LoadHints(); |
1159 | } |
1160 | |
1161 | /* Internal, for debugging */ |
1162 | bool qt_debug_component() |
1163 | { |
1164 | static int debug_env = QT_PREPEND_NAMESPACE(qEnvironmentVariableIntValue)(varName: "QT_DEBUG_PLUGINS"); |
1165 | return debug_env != 0; |
1166 | } |
1167 | |
1168 | QT_END_NAMESPACE |
1169 | |
1170 | #include "moc_qlibrary.cpp" |
1171 |
Definitions
- PluginMustMatchQtDebug
- QtBuildIsDebug
- qt_lcDebugPlugins
- lcDebugLibrary
- qt_find_pattern
- findPatternUnloaded
- installCoverageTool
- QLibraryStore
- qt_library_mutex
- qt_library_data
- qt_library_data_once
- ~QLibraryStore
- cleanup
- qlibraryCleanup
- instance
- findOrCreate
- releaseLibrary
- QLibraryPrivate
- findOrCreate
- ~QLibraryPrivate
- mergeLoadHints
- resolve
- setLoadHints
- pluginInstance
- load
- unload
- release
- loadPlugin
- isLibrary
- qt_get_metadata
- isPlugin
- updatePluginState
- load
- unload
- isLoaded
- QLibrary
- QLibrary
- QLibrary
- QLibrary
- ~QLibrary
- setFileName
- fileName
- setFileNameAndVersion
- setFileNameAndVersion
- resolve
- resolve
- resolve
- resolve
- errorString
- setLoadHints
- loadHints
Learn Advanced QML with KDAB
Find out more