Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

C++: Manifests and dynamically loading DLLs from different directory

Long story of what I'm trying to achieve

I'm working on a program that dynamically loads DLLs as plugins. I'm compiling the program using Microsoft Visual C++ 2008. Still, let's assume that any Visual C++ version with which Qt works should be supported. The program directory layout is following:

| plugins/
|   plugin1.dll
|   plugin2.dll
| QtCore4.dll
| QtGui4.dll
| program.exe

program.exe discovers all plugins DLL files, performs LoadLibrary() on them and calls a certain signature function to find out if it's actually a plugin or not. This works pretty well on computers which have vcredist for MSVC90 installed. Naturally, to make the program work on all computers I have to redistribute it with the msvc*.dll files and with the appropriate manifest file. Qt DLLs also require the redist to run.

Now, I've set up cmake to automatically copy over appropriate redist DLLs and manifest depending on the selected Visual Studio version. For the sake of simplicity let's keep assuming that I'm working with MSVC90. When the redist gets copied to the program directory the layout looks like this:

| plugins/
|   plugin1.dll
|   plugin2.dll
| QtCore4.dll
| QtGui4.dll
| msvcm90.dll
| msvcp90.dll
| msvcr90.dll
| Microsoft.VC90.CRT.manifest (I'm also aware that this file is bugged in VS2008)
| program.exe

Regarding the bug in manifest file: http://www.cmake.org/pipermail/cmake/2008-September/023822.html

The problem

The program with this layout now works on computers which do not have the redist installed, but the plugins are not getting loaded. In order to get the plugins to load I have to do one of the following:

  1. Copy over the manifest file to plugins/ directory. Remove all references to msvc*.dll files from the manifest file. This works but it's not nice because I'd have to support different versions of edited manifest files depending on the version of used MSVC. Also, I have no idea if this won't break with Visual Studio other than 2008.
  2. Copy entire redist to plugins/ directory. This doesn't require any modification to the manifest file, but now program.exe stupidly tries to load the msvc*.dll files thinking they are plugins. Naturally, this fails gracefully so no big harm is done. The other drawback is that the size of the program package grows by over 1 MB. Both these issues are something that I can live with, though.
  3. Compile plugins with /MT switch. Brief testing has shown that this actually works but I'm not sure if it won't break anything in the future if both Qt and program.exe are /MD.

The question(s)

What's the best solution? What's the correct solution? If there is more than one correct solution then which is the best practice? Am I the first person to ever try to do this?

Update 1 (18 Nov. 2012)

While the question remains unanswered I decided to go for the route that causes the least headache. Until now I've been using solution number 1 and I decided to stick with it. If CMake detects that user is using a different MSVC version than 2008 it will display a warning message saying that automatic packaging is not fully supported.

like image 620
ZalewaPL Avatar asked Nov 04 '12 21:11

ZalewaPL


2 Answers

If your target OS has _WIN32_WINNT >= 0x0502 than you can use function

SetDllDirectory()

before you do loading the plugins.

Put path to main program folder.

The call overrides system load order:

  1. The directory from which the application loaded.
  2. The directory specified by the path in SetDllDirectory() call.

So, you may call the function after application starts up. It is safe in all cases. Good luck!

like image 52
Brian Cannard Avatar answered Oct 14 '22 11:10

Brian Cannard


You can provide full file paths to "LoadLibrary", so you can load your plugins with their paths. I've used this exact layout to load multiple versions of the same library from subdirectories of the current dll in Visual Studio 2005.

You first need to get the current path of the current dll using:

static LPSTR strDLLPath1 = new TCHAR[_MAX_PATH+1];
::GetModuleFileName((HINSTANCE)&__ImageBase, strDLLPath1, _MAX_PATH);

Although if your program.exe is already discovering these plugin files, I would assume you already have access to their full paths.

like image 40
MultiMat Avatar answered Oct 14 '22 11:10

MultiMat