views:

1233

answers:

6

I'm working on a piece of C++ software which runs on all Windows versions between Windows XP and Windows Vista. In my code, I developed a DLL which links against a standard library (the Qt library). Once my software is deployed, it's not unusual that the user doesn't have the exact same Qt build on his system but a slightly different configuration. There might be features disabled (so their Qt build doesn't export the same set of symbols) or the library might even be changed in ways which make the library binary incompatible to the original.

At some point, I'm loading my DLL via a LoadLibrary() call. This pulls in whatever Qt library is on the user's system. If I'm lucky, their Qt build is compatible with what I used while developing my DLLs, so LoadLibrary() succeeds. However, depending on the changes they did to their Qt build, the LoadLibrary() call sometimes fails with

  • "The specified Module could not be found."; this usually happens if their Qt build consists of less DLLs than my Qt build. So my DLL attempts to load e.g. QtFoo.dll but since this dll is not part of their Qt build, loading my DLL fails.
  • "The specified Procedure could not be found."; this usually happens if they changes their Qt build so that certain features are disabled, which results in less exported symbols.

My question is: how can I catch these errors gracefully? Right I'm simply using GetLastError() and then print either of the above two messages. However, it would be much more useful if I knew which module could not be found, or which procedure is missing. I noticed that when running an application in the explorer which links against a missing DLL, explorer manages to yield a nice 'The application foo could not be loaded since the required library blah.dll is missing'. Is there maybe some API available to get more information about why a LoadLibrary() call failed exactly?

+1  A: 

Short of attaching a debugger to your process, I don't think you can. The message that typically pops up when this happens is generated internally by LoadLibrary. SetErrorMode is used in a lot of apps to inhibit messages of this form, I'm gessing that somewhere in your apps framework, its calling SetErrorMode to inhibit the OS message.

The only app ive seen generate its own verbose messages about dll load failures is MS DevStudio - which is attached as a debugger and so has access to a special stream of debug events.

Chris Becke
+2  A: 

At some point, I'm loading my DLL via a LoadLibrary() call. This pulls in whatever Qt library is on the user's system.

Don't do this! The kind of errors you have are the lucky kind, just as easily it could corrupt memory and crash. The canonical way of shipping a Qt application is either shipping the DLLs or linking statically. Check out the Qt deployment guide in the help files.

Later edit:

After reading your comments, I still do not recommend that you use this approach, since you can't be sure that the DLLs are binary compatible even if they load, which could lead to hard to track errors.

Nevertheless, I believe that you could intercept the LoadLibrary calls and see which ones fail. The MS Detours library can be used for this. Also see this Stackoverflow question.

rpg
This is not a simple Qt application; the DLL I'm writing (in fact, the DLL which performs the LoadLibrary() call) is injected into the process of a Qt application. Hence, I need to link against precisely the same Qt library as the process I'm hooking into. This is not always the case, and in those cases I'd like to be able to generate useful error messages.
Frerich Raabe
I still don't think that this is the right approach. Since you can not control what Qt the parent app ships with, you must make sure that your DLL works with ALL Qt builds. This means linking Qt statically.Like I've said, just because you aren't getting the two error messages, it doesn't mean that the app and your DLL are using the same version of Qt.
rpg
I cannot link Qt statically, since I need to use the exact same Qt objects as the application uses (qApp, for instance). I must not use my own copies which I get when linking statically. I know that this approach is suboptimal - I didn't choose it, it's just what's being done here. And given the workload here, I believe this won't change anytime soon. All I want to know is how to get better error messages, just in case.
Frerich Raabe
A: 

Can you be more proactive and check the version of the QT binaries you need before you call LoadLibrary()? Then you can just warn your user they don't have the version your app needs, and maybe even provide a link to the install point for them.

jeffamaphone
I already issue a warning in that case. However, this code still performs a LoadLibrary() and that can fail.
Frerich Raabe
+2  A: 

To expand jeffamaphone's answer, you can try retrieving the file version details before calling LoadLibrary. You can do this using the following function:

BOOL GetFileDetails(LPCTSTR lpszPath, LPDWORD lpMajorVersion, 
                    LPDWORD lpMinorVersion)
{
    DWORD dwVersionHandle;

    DWORD dwVersionSize = GetFileVersionInfoSize((LPTSTR)lpszPath,
                                                 &dwVersionHandle);
    if (dwVersionSize == 0)
        return FALSE;

    LPBYTE lpVersion = new BYTE[dwVersionSize];

    if (!GetFileVersionInfo((LPTSTR)lpszPath, dwVersionHandle, 
                            dwVersionSize, lpVersion))
    {
        delete [] lpVersion;
        return FALSE;
    }

    VS_FIXEDFILEINFO *pVersionInfo = NULL;
    UINT nLength;

    if (!VerQueryValue(lpVersion, _T("\\"), (LPVOID *)&pVersionInfo, &nLength))
    {
        delete [] lpVersion;
        return FALSE;
    }

    *lpMajorVersion = pVersionInfo->dwFileVersionMS;
    *lpMinorVersion = pVersionInfo->dwFileVersionLS;

    return TRUE;
}

You can then check the major/minor version numbers against ones you're expecting.

Alan
This is a uesful functoin, but unfortunately it's insufficient for my needs. Users tend to build their own Qt library with different configure arguments, so certain features might be disabled but the version number doens't change. Also, they might have patched their Qt libraries in ways which break binary compatibility (for instance, by introducing new virtual functions).
Frerich Raabe
For my purposes, this works great. Do LoadLibarary(), use GetModuleFileName() to get the path of the library I just loaded, then call your function.
mhenry1384
+1  A: 

Can MapAndLoad from ImageHLP.DLL may help. It returns a LOADED_IMAGE structure.

MSalters
Interesting idea. It seems to me that I'd need to replicate the LoadLibrary/GetProcAddress functionality; walking the import tables recursively and trying to resolve each symbol myself. And I'm not even sure that's an appropriate replication.
Frerich Raabe
Presumably you'd do so only after LoadLibrary failed. It's not going to affect performance in the sunny-day scenario.
MSalters
I think I'll follow this route, thanks for pointing out the MapAndLoad function!
Frerich Raabe
A: 

You can also have windows check for this by using a manifest file. This file contains the information on the requirements on the used libraries' versions. More accurate and complete info is on the msdn site.

Take a look at the answer to this question about how to use LoadLibrary with a manifest file.

The Qt documentation briefly mentions the usage of a manifest file for VS2005; for earlier versions you would have to create it for your own.

xtofl
Does this even work with older compilers? I need to support MSVC6 as well.
Frerich Raabe
Though I'm not a manifest file expert, I know it can be used as a configuration defining file you place next to your executable, and which the windows library loader will use to check the versions of the dependent dlls.
xtofl