您的位置:首页 > 编程语言 > Qt开发

Qt发布,无法找到qwindows.dll

2014-06-13 14:36 375 查看
I’ve written about deploying a simple Qt app, but what about deploying a more complex, “real” Qt app. This means copying all the needed files: besides the
main executable file there usually are some shared libraries, e.g. DLLs, and some other files like configuration or certificate files. In this blog post I’ll concentrate on getting the binary executable files setup correct; of course there can be other important
files, like the
qmldir
declaration file in the QtQuick.2 subdirectory etc. but that’s a topic for another blog post.
First there’s the option of static linking i.e. one build that packs all of Qt’s DLLs and your code together in a single .exe file. This hugely simplifies
deployment, but it’s a time consuming process that can be tricky; of course if you distribute large quantities of your software, static linking is definitely something you should try.
But if you’re not yet targeting a couple of millions of PCs (or a similar number

)
then you’re building your Qt app the normal, dynamic way. And that app structure in Qt depends heavily on shared or dynamically loaded libraries; on your deployment PC the Qt installer sets these up for you, but on other PCs you need to do it yourself. You
can either use an installer, for example Qt has a good one, or copy the files
manually.

A good idea here is to find a way to install your Qt app on a vanilla computer with minimal intrusion, because maybe you don’t have administrative or physical access to it, instead perhaps relying on a network share or FTP. For Windows this means no
writing to the registry (once the app is installed, it can use the registry for preferences or similar stuff, but this is about the install process) or create files in “system” locations like C:\Windows etc. For Linux and Macs, it means no fiddling with .conf
files in /etc and no file copying to /usr/lib or similar places.
First step anyway is to build your app in release mode and collect all the needed files together. If you’ve read myearlier
post about distributing a bare-bones Qt app, the files and directory structures used in that post are a good start. Also while it is possible to compile and build a Windows Qt app on Mac, here I’m assuming you’re using a Windows PC for Windows deployment,
a Mac for Mac deployment and a Linux installation for Linux stuff. (Re. Linux: I’m only using/testing Ubuntu and Debian, and on these the Qt installation files are the same, but other distros may differ here.)
The DLL (or .so and .dylib) files that a Qt app needs for successful launch can be divided into 3 flavors:

Qt specific DLLs:



For
Windows, say a Qt widgets app, they are at least
Qt5Core.dll
,
Qt5Gui.dll
and
Qt5Widgets.dll
.
For Linux, they’re
libQt5Core.so.5
,
libQt5Gui.so.5
and
libQt5Widgets.so.5
and
on the Mac they’re
QtCore.framework
,
QtGui.framework
and
QtWidgets.framework
.
A “real” Qt app most likely requires some more.

Note: for a typical Qt development installation, the library files/DLLs can be found in two different places, one is in your compiler directory, for example …\mingw48_32\bin, and the other
is a subdirectory of QtCreator. Don’t use the files from the latter place, those DLLs in QtCreator’s subdirectories are for QtCtreator’s own use, it’s a Qt app (of course!). In some cases, like in Linux, the DLLs are identical to the ones in the compiler directory;
in other cases, like for a MinGW-flavored installation of Qt on Windows, they are not (QtCreator is built using Visual Studio and those .dlls do not work for a Qt app built with MinGW!). Instead, always use the library files in your compiler’s …\bin
directory
.

A Qt app, like other apps, relies on the OS to load those library files, which means that they must be placed where the OS can find them. On Windows, this means either next to the .exe file in the same directory
or in a directory specified in the search path, for example C:\Windows\System32. On a PC with Qt installed, the DLLs are located by adding the directory to the search path, either by QtCreator when you run your app from it, or by manually invoking
qtenv2.bat
in
Qt’s compiler’s bin directory.

When copying the DLLs to another PC without Qt installed, usually you don’t want to mess with the search path on other computers, instead placing the DLLs together with the .exe file is probably the best solution.

On Linux, placing the .so files next to the .elf file in the same directory does not cause them to be found/loaded automatically like in Windows; you can add directories to the search paths or ld.so’s .conf
files, but a less intrusive way is using the rpath setting in the .elf file, and that’s what Qt
does. On your development PC with Qt installed, when building your app, QtCreator sets the
rpath
on your .elf file so that it points
to the lib subdirectory inside Qt’s installation directory (where the .so files for Qt are located).

This is quite handy when you want to launch your app from Nautilus or Terminal (don’t need any .bat files like in Windows!) but when you later deploy your app to another Linux computer without Qt installed, then that
rpath
setting
is guaranteed to be wrong

and needs to be fixed.

Note: you can get lucky, for example on Ubuntu, there usually is a Qt installed in /usr/lib/i686-linux-gnu or /usr/lib/x86_64-linux-gnu, so your app might run anyway (ld.so, the dynamic linker/loader, gets those paths from the .conf files in /etc/ld.so.conf.d).
But beware of the versioning problem, the Qt supplied by Ubuntu might be too old for your app to launch ok.

For how to fix that
rpath
setting, look at my my
previous post about deploying a bare bones app for Linux.

On Macs also there’s no default loading of .dylibs when they’re placed together with the .elf file. Apple in Leopard (10.5) introduced a rpath
setting similar to the one in Linux. One minor problem is that there’s no
chrpath
(or
patchelf
)
available for OSX to set the rpath; i.e. it can only be set at link time from QtCreator. And another complication is that the library files are (mostly) placed separately, each in their own directory structure.

All this means, while it’s no problem launching your Qt app from Finder or from Terminal on your dev. Mac with Qt installed, you’re up the creek without a paddle when deploying your Qt app to another Mac with no Qt on it. Since the
rpath
can
not be changed easily, you’ll have to run Apple’s
install_name_tool
on your .elf file multiple times, specifying the new path to each
.dylib and framework of Qt it depends on. And for every Qt .dylib/framework that has a dependency on another .dylib/framework, e.g. QtGui.framework depends on QtCore.framework, those needs a whacking with install_name_tool as well.

Thankfully, someone with a brain has written a utility for this called macdeployqt. You run it on your development Mac, I’ve written about in my
previous post.

C++ compiler specific DLLs:





Some MSVC redist installs on a typical Windows PC

While all 3 platforms depends on these kinds of .DLLs, on Linux and Macs it’s presumed that those files (like libstdc++.so.6 or libstdc++.6.dylib) are present and recent enough to work, but on Windows, mostly
because of the variety of compilers, this needs to be addressed when deploying to another PC. The easiest is to copy them together with the Qt specific DLLs, and in my
previous post I show you how.

However, for the Microsoft compiler DLLs, they recommend not just simple copying them (although that works just fine). Instead you should copy and install a vcredist package (can be found in the Qt’s vcredist
subdirectory), thereby installing the MSVC dlls not together with your app, but somewhere within the bowels of C:\Windows.

The reason for this extra step is to give Microsoft an opportunity to update those .dlls if needed, in case they find a bug or security problem in them. This is in theory a good idea, but for me, it also has
during the years meant a couple of early Monday morning calls from customers complaining about my broken app. And discovering that the reason was Microsoft updating their dlls. To be fair, this was not the Visual C++ dlls, two that I can remember were: ATL
security update and an ADO update.

DLLs loaded by Qt itself a.k.a. plugins:



Welcome
to an interesting nook and cranny of Qt deployment, the DLLs that Qt loads by itself! The DLLs mentioned above, they are all standard DLLs loaded by the OS which means you have to know how Windows, Mac and Linux loads library files. But also
if a file is missing, you will get an error, a message box or some other diagnostic message, like “This application has failed to start because Qt5Core.dll was not found.” then you have something to google on.

Qt plugins on the other hand are more sinister in this regard, for many of them when they are missing/not found in the expected place, your Qt app will fail silently

No
error message or diagnostic output, just an app that doesn’t start or exhibits reduced functionality. For example, a Qt Quick app that cannot find the needed QML/QtQuick plugins will instead display an empty white window. Also when trying to find the DLLs
your app depends on, when running a tool like Dependency Walker or
ldd
on
Linux, the Qt plugin DLLs will not show upbecause these tools only display the normal DLLs requested/imported by the app, and not the plugin DLLs that Qt only knows about. To see them, you need to first launch your app (so that they are loaded into
memory) and then use a tool like ListDLLs for Windows or the
built-in
lsof
program in Mac and Linux.

Another gotcha re. these plugin DLLs: even though you are super sure you’ve placed them in the correct place, you can still be greeted with “… could not find or load the Qt platform plugin…”
This can happen because a DLL that these guys depend is missing. Since Qt is the loader (and not the OS) you won’t get the usual diagnostic “not found” message. Running Dependency Walker or ldd for Linux on the DLL in question, can be helpful for
debugging these kind of errors.

For example, the Visual Studio-flavored non-OpenGL platform plugin
qwindows.dll
needs
libEGL.dll
,
and in Linux
libqxcb.so
depends on
libQt5DBus.so.5
and
libxcb.so
for
the X server stuff. Also an additional gotcha: for non-OpenGL MSVC-flavored deployments, that
libEGL.dll
file has be placed not together
with the
qwindows.dll
but together with the other Qt dlls (because while Qt loads the plugin
qwindows.dll
,
it’s the OS that loads other files it depends on, like
libEGL.dll
).

So how does Qt load these plugins, what are the rules? At start, Qt makes a list of directories where it hopes to find the plugin DLLs. For such a plugin directory to be valid, it has to contain some subdirectories with predetermined names like:
platforms,sqldrivers,imageformats
.
These subdirectories are where Qt expects to find the plugins DLLs. It does so by enumerating all files in the subdirectory, looking for matching plugin
keys. When you install Qt, it creates such a plugin directory directly below the compiler, for example
.../clang_64/plugins .../gcc_64/plugins
or ...\mingw48_32\plugins
. If you look at them, they contain something like 15 different subdirectories. For deploying your app, most likely you only need to copy one or two of those.

The most important plugin type is the
platforms
subdirectory. Those DLLs are called platform
plugins and are needed by all Qt apps (even a minuscule Qt app like the bare bones one in my previous post), and the default one is qwindows.dll (libqxcb.so for Linux or libqcocoa.dylib for the Mac). This is Qt’s QPA (Quarter Pounder Advanced™ just
kidding, it stands for Qt Platform Abstraction layer), it’s responsible
for many OS-specific things, for example translating calls like
setWindowState(Qt::WindowMaximized)
to Windows/Linux/Mac specific system
calls.

I mentioned Qt doesn’t care about the plugin filenames, only about the subdirectory names, let me give you an example:





The filenames for plugins are immaterial

If I rename qwindows.dll to grapefruit.jpg, Qt will still happily load it as a DLL. Contrast that with for example when Windows wants to load
Qt5Core.dll
,
it can be in any directory along the PATH or next to the .exe file for the app, Windows doesn’t really care, as long as the filename is correct. Qt instead fubars if the subdirectory
platforms
cannot
be found.

Thankfully, for these platform plugins, Qt do display an error message in case they cannot be found. In Windows Qt displays a message box, for Macs and Linux these errors usually can be seen when you launch
your app from Terminal. (True also for the normal DLLs.) So if Qt’s plugins are not deployed correctly, that error message is a good diagnostic, my previous
post shows these errors in more glorious detail.

Let’s look at how Qt’s list of plugin directories are determined at app start, and how you can use these rules for successful app deployment.

Remember that this list specifies the main plugin directory (which is
plugins
when you install Qt) and that the real McCoys, the plugin
DLLs themselves, are stored in the subdirectories below it.

Actually there are 5 (!) different choices for establishing the plugin directory:

Binary editing of Qt5Core.dll (or libQt5Core.so.5 or QtCore.framework/QtCore):





Hexediting Qt’s plugin path

This is the big wheel approach to plugin lookup, favored by Qt’s installer. When you install Qt on your development PC, the installer patches Qt5Core.dll with the path you specified for your convenience. This means whenever you start Qt apps on your development
machine, the plugins will always be located fine and dandy. (From the point of view of deployment to another PC, this instead could be considered a disservice. Nevermind.)

To see the current path setting (note: for Windows you might need to download Sysinternal’s
strings utility):

Windows: strings Qt5Core.dll | find "qt_plugpath"
Linux: strings libQt5Core.so.5 | grep qt_plugpath
Macs: strings QtCore.framework/QtCore | grep qt_plugpath


Then if you’re into hex editing yourself, you can apply a new setting for deployment:





Changing to a path more suitable for deployment

You can change to another absolute path, but a relative path works too, it means Qt will use the directory where the .exe file is and append the plugin path to it.

For example, assume you’ve placed your Qt app in the directory
C:\CompanyApps
. Then
if you’ve hex edited
Qt5Core.dll
as above and copied it into \CompanyApps as well, then Qt expects to find
qwindows.dll
in
the directory
C:\CompanyApps\plugins\platforms
.

When I tested on Windows with a relative path I had to prefix it with C: which obviously doesn’t work on Linux and Macs. So while this is a viable method for plugin deployment, I suggest you leave this option
to the big wheels


Note: remember if you’re running your app in Debug mode then the settings in
Qt5Cored.dll
will
apply,not
Qt5Core.dll
.

Setting the environment variable
QT_PLUGIN_PATH
:

Using an environment variable might be an easier option than hex editing Qt5Core.dll, and the effect will be the same, for example:

set QT_PLUGIN_PATH=plugins

will perform the same feat as above, Qt will look for
qwindows.dll
in
C:\CompanyApps\plugins\platforms
.
Of course you can also set an absolute path this way. And if you want a bonanza of plugins directories, you can append additional paths separated with ; (or : in Linux and Macs).

Specifying the plugin path in your code:





Adding the plugin path in main()

You can also specify a plugin path in your code, but since Qt loads the plugins when QApplication is constructed, your window of opportunity is to do it before QApplication’s constructor is called.
The call above is a 3rd alternative for telling Qt to look for
qwindows.dll
in
C:\CompanyApps\plugins\platforms
(using
the same example as above).

Creating a
qt.conf
file:

This is a popular method of setting up a path to the plugins, used for example by the QtCreator app and the macdeployqt
utility on Macs. Create a text file, like this:

[Paths]
Plugins=plugins

If such a file exists in the same directory as the app’s .exe file, Qt will read it and add the
plugins=
path
to its list of plugin directories. The example above accomplishes the same thing as in the previous examples, it causes Qt look for
qwindows.dll
in
...\plugins\platforms
.

One caveat here if you’re on Windows: backslashes in the
Plugins=
setting don’t work,
you should use Linux-style forward slashes.

Using the default directory:

This is by far the favorite method for setting up the plugin directory, since it’s provided for free by Qt


How does this work? Well, when your app starts, Qt as a freebie adds the directory where your app’s .exe file is located to its list of plugin directories.

Note that this is not the same as the examples above, in those we instructed Qt to look for plugins in
C:\CompanyApps\plugins
but
this default setting tells Qt to look for plugins in
C:\CompanyApps
. I.e. an equivalent env. variable setting would be:

set QT_PLUGIN_PATH=.

or an equivalent
qt.conf
file would look like this:

[Paths]
Plugins=.

So if we take the example above again; you’ve deployed your app to the directory
C:\CompanyApps
.
This means that Qt automatically will look for
qwindows.dll
in
C:\CompanyApps\platforms
.
For example, if you look at my previous post you’ll see that for deployment of that bare bones app,
I always go for this option, i.e. placing the
platforms
directory directly below the main directory.

Q: “Then why does Qt Creator go through the trouble of creating a
qt.conf
file
for itself, when it suffices just to copy the plugin subdirectories into the app’s directory?”

One reason could be to reduce litter in the app’s main directory, if we look at Qt Creator’s
qt.conf
file,
it has the line
Plugins=plugins
(same as in our examples above). Since it has 7 different plugin subdirectories (9 in Linux) it makes
sense to stash away all those subdirectories in a separate
plugins
directory.

Another reason for creating a qt.conf file:
macdeployqt
creates one, because on the
Mac there’s an app bundle standard that plugins should reside in their own directory called
PlugIns
.

Remarks: if you use a
qt.conf
file, it supersedes the other settings, so if you make
a spelling error in it, that can cause plugin loading to fail. Also the last two methods (the most popular ones) are currently slightly more brittle due to an obscure
bug, for example if you call QStyleFactory::keys() before QApplication a(argc, argv) in your main.cpp, this preempts the path to the .exe file and a path in
qt.conf
to
be setup as eligible plugin directories.

Finally, if you’re still running into plugin loading problems, you can turn on a trace of the plugin loading process, by setting the environment variable
QT_DEBUG_PLUGINS
to
a nonzero value, like this:

SET QT_DEBUG_PLUGINS=1

(for Linux and Macs instead use
export
), and then launch your app from the CMD/Terminal
window. For Linux and Macs you’ll see lines something like “QFactoryLoader::QFactoryLoader() checking….” displayed in Terminal, but in Windows Qt routes the output to the OutputDebugString() API, so nothing will be displayed in the CMD window. The output can
be seen using Visual Studio or Qt Creator, which isn’t very helpful if you’re experiencing the plugin problems on a non-development PC. An alternative is to download a utility: DbgView
from Sysinternals and open it before you launch your app you’ll be fine.

Bonus contents if you are crazy and not happy with *only* 5 choices:

Remember I said the Qt plugin subdirectory names like
platforms,sqldrivers,imageformats
are
hardwired in Qt’s plugin loader? I lied, you can actually change
platforms
to some other, even an absolute pathname. Note that this
is different from the previous 5 choices, since they are for specifying the
plugins
main directory (where the subdirectories are),
while this setting is for changing only the
platforms
name or location (the settings are independent from each other). Why this is
useful? Really don’t know, but I’m guessing for debugging/testing. Also note that this option is not available for Qt Console apps, only Quick- and Widgets-flavored ones.

Say you want to change to instead use the subdirectory
test
, then you can either
set/export an environment variable:

set QT_QPA_PLUGIN_PATH=test

or give a command line argument when launching your app:

YourQtApp -platformpluginpath test

Note that if this is a relative path (like in my example) then it’s relative to the app’s .exe file, so for a more convoluted case, if you’re using a
qt.conf
file
that overrides the default plugins directory
.
(where your .exe file is located), instead setting it to
plugins
,
then if
test
is a subdirectory in
plugins
,
you would instead have to type:

YourQtApp -platformpluginpath plugins/test


Looking at other Qt apps:

Finally, to understand more of the deployment process, I think it’s useful to look at other (than your own) Qt apps; I’m thinking of one Qt app which is installed
in your Qt folder: QtCreator. Besides creating the version specific (like
5.3
) directory structure where the compiler files
are, Qt’s installer also creates the
Tools
directory which contains QtCreator. On the Mac there is no Tools subdirectory, instead you’ll
find a Qt Creator.app (which is a directory, you open it with a rightclick and Show Package Contents in Finder).
QtCreator is packaged ready to deploy, i.e. you can copy it (the directory or in the Mac case, the app) to another computer and it will start just fine. (For
Windows, you might need to install the vcredist_msvc2010 runtime located in the QtCreator’s lib subdirectory.) If you copy it to a PC without a Qt installation, it might not compile and build apps, but it would still function well as a text/code editor


So let’s look on how it solves its deployment, first the normal DLLs:

In Windows, all the needed DLLs are placed together with
qtcreator.exe
in the same directory (except for the compiler specific DLLs,
which is installed using the vcredist package I mentioned earlier).

In Linux, the .so files are in a neighboring subdirectory:
../lib/qtcreator
, and for the .so files to be found by the ld.so loader,
the .elf file has a custom
RPATH/chrpath
setting:
"$ORIGIN/../lib/qtcreator"
.

In Macs, all the .dylib and framework files QtCreator needs are included in the app bundle (if you do
otool -L
on QtCreator’s .elf file
in the MacOS folder, you’ll see for example
@loader_path/../Frameworks/QtCore.framework....
). It’s look quite similar (but not exactly
the same) as when you run the utility
macdeployqt
on your own Qt app’s folder.
Then the plugin DLLs, how are they located? Well, if we start by looking at
Qt5Core.dll
,
a
strings
search of it finds
"qt_plugpath=c:/work/build/PADDING/plugins"
(on
Linux and Mac it’s more or less the same setting).
Obviously there’s no “/work/build” directory, so clearly the option of using
Qt5Core.dll
for
specifying the plugin directory is unused by QtCreator.

(BTW, this is one of the reasons why this Qt5Core.dll is not replaceable with the Qt5Core.dll in the compiler directory, because the
qt_plugpath
in
that one is used and expected to be valid.)
So, QtCreator does not set any environment variables and does not call
addLibraryPath()
in
main.cpp, nor is the default directory option used. Instead (as I’ve already mentioned above) QtCreator has a
qt.conf
file to specify
its plugin directory, using the relative path
plugins
.
If you want to experiment, momentarily rename or move away the qt.conf file, and then try to start QtCreator. You’ll see that infamous message “… could not
find or load the Qt plugin …. “, same for any other Qt app

Don’t
forget to restore the qt.conf file!
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: