How the Win32 App Plugin Loads its DLL into its Own Directory

My code is a plugin for a specific application written in C ++ using Visual Studio 8. It uses two DLLs from an external provider. Unfortunately, my plugin does not start because DLLs were 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, the plugin loads normally. This move was considered unacceptably cumbersome for the end user, and I'm looking for a way for my plugin to load its DLLs transparently. What can I do?

Relevant Details:

  • Application host plugins are located in the directory specified by the host application. This directory is not in the DLL search path, and I do not control it.
  • The plugin itself is packaged as a subdirectory of the plugins directory containing the plugin code itself, as well as any resource associated with the plugin (for example, images, configuration files ...). I control what is called a "bunch" inside this subdirectory, but not where it is.
  • The general plug-in installation identifier for this application is intended for the end user to copy the plug-in package into the plug-in directory.

This plugin is a port from the Macintosh plugin version. There is no problem on the Mac, because each binary contains its own path for the dynamic library, which I set as I need for my binary module. To establish that on a Mac, you just need to set up a project in the Xcode IDE. That is why I would like to hope for something like this in Visual Studio, but I could not find anything suitable. In addition, Visual Studio help was a bit of a kind, but Google was not.

Perhaps, for my code, I will explicitly tell Windows about where to find the DLL, but I do not know, as in any case, since my code is not already running, it did not have the opportunity to do this.

As a Mac developer, I understand that I can ask for something very basic. If so, I apologize, but I have run out of hair to pull it out.

+4
source share
4 answers

You are not asking for something very basic. Windows just does not support what you want.

You have several options for solving this problem:

  • Create two DLLs. Your DLL version of the plugin, statically linked to any other DLL files that you need. And a simple "facade" dll, loaded by the hosting application. The torch dll receives a call to SetDllDirectory and then to LoadLibrary to load the DLL implementation with the desired search path, and then for each exported plug-in function, it implements a stub function that uses GetProcAddress to simply pass the call directly to your DLL implementation.

If the plugin interface is complex, but the dll interface you are using is missing, then:

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

  • The last option is the least documented and the most poorly understood by Windows programmers. We mainly use the version of Windows for technology built to support .NET: assembly builds. Do not be alarmed. A "collaborative build" is just a plain old dll, but with an accompanying manifest file that provides more information about this.

The reason we want to do this is because the search order for dlls that are connected via SxS technology is different from the usual dll search order: - Namely, after searching c: \ windows \ WinSxS, the windows will look the same as the DLL that refers to the DLL, not to the EXE folder.

Start by taking an inventory of all the satellite DLLs you need to connect to and create an assembly from them. This means: create a .manifest file with a bunch of files = nodes. You must give the assembly a name. Let's call it "MyAssembly".

Create the file "MyAssembly.manifest" in your dll folder with contents similar to the following: (listing each of the libraries 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, this is your assembly manifest. We are half done.

The next half is to force your DLL to use the assembly, and for this you need to add the manifest resource to your Dll file. This manifest should ultimately 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> 

Obviously, the application manifest (which is a confusing name when embedding in dll) is also allowed to use the <file> node, so it’s 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> 

like a manifest dll. I have not played with this iteration yet, so I'm not sure how this will change the usual dll search path (if at all).


Without knowing your development environment, it's hard to know how to tell you how to add a manifest to the dll. If you edit the .rc file and enter the manifest manually, be aware that Dlls uses resource identifier 2 rather than 1, which it usually uses in exe examples.

If you are using DevStudio 2005 or later, there is a convenient #pragma directive that will make everything magically correct and be in the right places.


If the project settings are set by default, VS2005 and higher will be automatically generated and embed the manifest, if necessary. this #pragma will add additional build 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 
+7
source

Delay loading DLLs are your friend in this situation. I ran into the same problem a while ago, and it is actually quite simple. You /DELAYLOAD linker (flag /DELAYLOAD ) which modules are loaded with a delay, and basically they are not indicated as an explicit import in the PE header, so the bootloader will not complain when it cannot find the specified modules and all function calls from these modules are wrapped to a stub that provides module loading and function detection.

So, let's say you wanted to delay loading the XmlLite library. First, you specify /DELAYLOAD:XmlLite.dll in the linker flags. Then, in the module initialization function (preferably DllMain ), you unzip the XmlLite DLL into a temporary folder and then call LoadLibrary on it. After that, each call to any function exported by XmlLite.dll will be automatically resolved.

+1
source

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

0
source

Assuming the code is native and you can use an explicit dynamic link at runtime (and not any form of implicit link), use GetModuleHandle and GetModuleFileName to find out where your dll works.

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

Then replace the base dll name with the name of the plugin.dll file that you want to download.

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

Call LoadLibrary on the generated line.

0
source

Source: https://habr.com/ru/post/1306917/


All Articles