Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

how can a Win32 App plugin load its DLL in its own directory

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.

like image 596
Jean-Denis Muys Avatar asked Jan 22 '23 06:01

Jean-Denis Muys


1 Answers

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
like image 189
Chris Becke Avatar answered Jan 29 '23 09:01

Chris Becke