views:

229

answers:

4

My code is a plugin for a specific Application, written in C++ using Visual Studio 8. It uses two DLL from an external provider. Unfortunately, my plugin fails to start because the DLLs are not found (I put them in the same directory as the plugin itself).

When I manually move or copy the DLLs to the host application directory, then the plugin loads fine. This moving was deemed unacceptably cumbersome for the end user, and I am looking for a way for my plugin to load its DLLs transparently. What can I do?

Relevant details:

  • the host Application plugins are located in a directory mandated by the host application. That directory is not in the DLL search path and I don't control it.
  • The plugin is itself packaged as a subdirectory of the plugin directory, holding the plugin code itself, but also any resource associated with the plugin (eg images, configuration files…). I control what's inside that subdirectory, called a "bundle", but not where it's located.
  • the common plugin installation idiom for that App is for the end user to copy the plugin bundle to the plugin directory.

This plugin is a port from the Macintosh version of the plugin. On the Mac there is no issue because each binary contains its own dynamic library search path, which I set as I needed to for my plugin binary. To set that on the Mac simply involves a project setting in the Xcode IDE. This is why I would hope for something similar in Visual Studio, but I could not find anything relevant. Moreover, Visual Studio's help was anything but, and neither was Google.

A possible workaround would be for my code to explicitly tell Windows where to find the DLL, but I don't know how, and in any case, since my code is not even started, it hasn't got the opportunity to do so.

As a Mac developer, I realize that I may be asking for something very elementary. If such is the case, I apologize, but I have run out of hair to pull out.

A: 

Use GetModuleFileName() to find the path where your dll is located. Then use SetDllDirectory() to add that path to the dll search path.

Stefan
thats not very helpful as he can't change the code of the hosting application.
Chris Becke
from what I understand, he can change his plugin dll. He can't change the third party dll his plugin loads. But since the issue is about loading those dlls from his plugin dll, he can do something in the code about it.
Stefan
Your suggestion is not workable since my code is never launched in the first place.
Jean-Denis Muys
+3  A: 

You are not asking for something very elementary. Windows simply does not support what you want.

You have some options to work around this issue:

  • Create two DLLs. Your plugin implementation dll, that statically links against whatever other dlls you need. And a simple "facade" dll that is loaded by the hosting app. The facade dll gets to call SetDllDirectory then LoadLibrary to load your implementation dll with the required search path, and then, for each plugin exported function, it implements a stub function that uses GetProcAddress to just pass the call straight to your implementation dll.

If the plugin interface is complicated, but the dll interface you are using is not, then:

  • Give up and just use LoadLibrary (with an explicit path) and GetProcAddress to access the functionality in your satellite dll(s). Pain.

  • The final option is the least documented and most badly understood by windows programmers. Basically we use the windows version of a technology built to support .NET: Side by Side assemblies. Don't be frightened. A "Side by Side assembly" is very simply a regular old dll, but with a accompanying .manifest file that provides some extra information about it.

The reason we want to do this is the search order for dlls that are linked in via the SxS technology is different to the regular dll search order :- Namely - after searching c:\windows\WinSxS, windows will search the same folder as the dll that references the dll, NOT the folder of the exe.

Start by taking an inventory of all the satellite dlls your plugin dll needs to link to, and create an "assembly" from them. Which means: create a .manifest file with a bunch of file= nodes. You need to give the assembly a name. Lets call it "MyAssembly".

Create the file "MyAssembly.manifest" in your dll's folder, with contents similar to the following: (listing each of the dlls you need to include)

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
    <assemblyIdentity name="MyAssembly" processorArchitecture="*" type="win32" version="1.0.0.1"/>
    <file name="firstrequireddll.dll"/>
    <file name="2ndrequireddll.dll"/>
</assembly>

Now, thats your assembly manifest. We are half done.

The next half is to actually get your dll to use the assembly, and to do that you need to add a manifest resource to your Dll file. That manifest ultimately needs to contain the following content :-

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
    <dependency>
        <dependentAssembly>
            <assemblyIdentity type="win32" name="MyAssembly" version="1.0.0.1" processorArchitecture="*"/>
        </dependentAssembly>
    </dependency>
</assembly>

Apparently application manifests (which is a confusing name when embedded in a dll), are also allowed to use a <file> node, so it might be possible to skip creating an assembly, and just go with

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
    <file name="firstrequireddll.dll"/>
    <file name="2ndrequireddll.dll"/>
</assembly>

as the dll's manifest. I havn't toyed with that iteration yet, so Im not sure how that alters the normal dll search path (if at all).


Without knowing your development environment, its hard to know how to advise you how to add a manifest to a dll. If you are editing a .rc file and entering the manifest by hand, know that in Dlls the resource id to use is 2, not 1, which it typically used in exe examples.

If you are using DevStudio 2005 or higher, there is a handy #pragma directive that will make everything magically have the correct id's and be in the correct places.


If the project settings are on their defaults, VS2005 and up will automatically generate, and embed a manifest as appropriate. this #pragma will add additional assembly dependencies to the generated manifest :-

#if _MSC_VER >= 1400 // VS2005 added this directive
#pragma comment(linker, \
    "\"/manifestdependency:type='Win32' "\
    "name='Company.Product.Subsystem' "\
    "version='6.0.0.0' "\
    "processorArchitecture='*' "\
    "language='*'\"")
#endif
Chris Becke
Thanks for the lengthy explanation. With the delay loaded DLL mechanism proposed below failing, I am back to your idea. My development environment is Visual Studio 2008. Being the noob that I am with Windows development, I don't know how to do the second half of your suggestion (adding a manifest to my dll file). I will research this, but you may have a quick hint or two or a link. Info about that pragma might be useful too. FWIW, I don't have a .rc file.
Jean-Denis Muys
I saved your second fragment above to a file named myProduct.manifest in my source directory. Then I added a property to Linker>Manifest File>Additional Manifest Dependencies with the value "$(InputDir)\myProduct.manifest". Build went OK, with the linker reporting "Embedding manifest...". However, I get the same error at launch time, and my plugin never gets executed. I may have done something stupid, but what?
Jean-Denis Muys
1. If you open a .dll file directly with VS it opens it in a resources view, that lets you browse the resources and check the manifest is both present, and contains the correct stuff.2. Once the manifest is being processed, the name of the dependent assembly, MUST match the name of the .manifest (up to the last .), MUST match the name of the assembly identity of the "assembly".
Chris Becke
A: 

Assuming native code and that you can use explicit run-time dynamic link (rather than any form of implicit link), use GetModuleHandle and GetModuleFileName to find out where your dll is running from.

HMODULE hModule = GetModuleHandleW(L"RunningDll.dll");
WCHAR path[MAX_PATH];
GetModuleFileNameW(hModule, path, MAX_PATH);

Then replace the base name of the dll with the name of the plugin.dll you want to load.

CString plugin(path);
int pos = plugin.Find(L"RunningDll.dll");
plugin = plugin.Left(pos);
plugin += L"pluginName.dll";

Call LoadLibrary on the generated string.

sean e
Well, I'm stuck at an earlier stage: my code is not even launched, so I have no opportunity to call anything. Could you elaborate on your assumption that I can use "explicit run-time dynamic link"? How would I go about doing that? What are the conditions that would allow for that?
Jean-Denis Muys
+1  A: 

Delay loaded DLLs are your friend in this situation. I faced the exact same problem a while back and it's actually rather simple. You specify to the linker (/DELAYLOAD flag) which modules are delay-loaded and basically they modules are not listed as explicit imports in the PE header so the loader won't complain when it cannot find the said modules and all the calls to functions from those modules are wrapped in a stub which ensures the module is loaded & the function is found.

So, let's say you wished to delay load the XmlLite library. First you'd specify /DELAYLOAD:XmlLite.dll in the linker flags. Then in your module's initilization function (preferably DllMain) you'd unpack the XmlLite DLL into a temporary folder and then call LoadLibrary on it. From there on it, each call to any function exported by XmlLite.dll would be resolved automatically.

Irwin
Don't call LoadLibrary from DllMain. From http://msdn.microsoft.com/en-us/library/ms682583 "The entry-point function should perform only simple initialization or termination tasks. It must not call the LoadLibrary or LoadLibraryEx function (or a function that calls these functions), because this may create dependency loops in the DLL load order." See also: http://blogs.msdn.com/oldnewthing/archive/2004/01/27/63401.aspx and http://blogs.msdn.com/oldnewthing/archive/2005/05/23/421024.aspx
sean e
I was very hopeful that your suggestion was going to work. Unfortunately it doesn't. I still get an error alert "This application has failed to start because log4cxx.dll was not found. Re-installing the application may fix this problem".Of course, I have log4cxx.dll in a /DELAYLOAD clause in the command line.I don't know why this happens. It may be due to the way by which the host application (4D) loads my code as a plugin. I have no idea how it does it.To reiterate, a break point on the first line of my main routine is never reached.
Jean-Denis Muys