Win32应用程序插件如何在自己的目录中加载其DLL

发布于 2024-08-29 01:07:58 字数 797 浏览 10 评论 0原文

我的代码是特定应用程序的插件,使用 Visual Studio 8 用 C++ 编写。它使用来自外部提供商的两个 DLL。不幸的是,我的插件无法启动,因为找不到 DLL(我将它们放在与插件本身相同的目录中)。

当我手动将 DLL 移动或复制到主机应用程序目录时,插件加载正常。对于最终用户来说,这种移动被认为是难以接受的麻烦,我正在寻找一种方法让我的插件透明地加载其 DLL。我能做些什么?

相关详细信息:

  • 主机应用程序插件位于主机应用程序指定的目录中。该目录不在 DLL 搜索路径中,我无法控制它。
  • 插件本身打包为插件目录的子目录,保存插件代码本身,以及与插件关联的任何资源(例如图像、配置文件……)。我控制该子目录内的内容(称为“捆绑包”),但不控制它所在的位置。
  • 该应用程序的常见插件安装习惯是最终用户将插件包复制到插件目录。

该插件是该插件的 Macintosh 版本的移植版本。在 Mac 上,没有问题,因为每个二进制文件都包含自己的动态库搜索路径,我根据插件二进制文件的需要设置了该路径。要在 Mac 上进行设置,只需在 Xcode IDE 中进行项目设置即可。这就是为什么我希望 Visual Studio 中有类似的东西,但我找不到任何相关的东西。此外,Visual Studio 的帮助绝非如此,Google 也不是。

一个可能的解决方法是让我的代码显式告诉 Windows 在哪里可以找到 DLL,但我不知道如何进行,并且无论如何,由于我的代码甚至还没有启动,所以它没有机会这样做。

作为一名 Mac 开发人员,我意识到我可能要求一些非常基本的东西。如果是这样的话,我很抱歉,但我已经没有头发可以拔了。

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.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

原谅过去的我 2024-09-05 01:07:58

你要求的并不是非常基本的东西。 Windows 根本不支持你想要的。

您可以选择一些方法来解决此问题:

  • 创建两个 DLL。您的插件实现 dll,静态链接到您需要的任何其他 dll。以及由托管应用程序加载的简单“外观”dll。 Facade dll 调用 SetDllDirectory,然后调用 LoadLibrary 以使用所需的搜索路径加载您的实现 dll,然后,对于每个插件导出的函数,它实现一个存根函数,该存根函数使用 GetProcAddress 将调用直接传递到您的实现 dll。

如果插件接口很复杂,但您使用的 dll 接口并不复杂,那么:

  • 放弃并仅使用 LoadLibrary(具有显式路径)和 GetProcAddress 来访问卫星 dll 中的功能。痛苦。

  • 最后一个选项是 Windows 程序员记录最少且理解最差的。基本上,我们使用 Windows 版本的技术来支持 .NET:并排程序集。别害怕。 “并排程序集”非常简单,是一个常规的旧 dll,但附带一个提供一些额外信息的 .manifest 文件。

我们想要这样做的原因是通过 SxS 技术链接的 dll 的搜索顺序与常规 dll 搜索顺序不同: - 即 - 在搜索 c:\windows\WinSxS 后,Windows 将搜索与dll 引用 dll,而不是 exe 的文件夹。

首先清点插件 dll 需要链接到的所有卫星 dll,并从中创建一个“程序集”。这意味着:创建一个包含一堆 file= 节点的 .manifest 文件。您需要为程序集命名。我们称之为“MyAssembly”。

在您的 dll 文件夹中创建文件“MyAssembly.manifest”,其内容类似于以下内容:(列出您需要包含的每个 dll)

<?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>

现在,这就是您的程序集清单。我们已经完成了一半。

下半部分是实际让您的 dll 使用该程序集,为此,您需要将清单资源添加到您的 Dll 文件中。该清单最终需要包含以下内容:-

<?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>

显然,应用程序清单(嵌入到 dll 中时是一个令人困惑的名称)也允许使用 节点,因此可能是可以跳过创建程序集,而直接

<?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>

作为 dll 的清单。我还没有玩过那个迭代,所以我不确定它如何改变正常的 dll 搜索路径(如果有的话)。


如果不了解您的开发环境,就很难知道如何建议您如何向 dll 添加清单。如果您正在编辑 .rc 文件并手动输入清单,请注意在 Dll 中要使用的资源 ID 是 2,而不是 1(通常在 exe 示例中使用)。

如果您使用 DevStudio 2005 或更高版本,有一个方便的 #pragma 指令,可以使所有内容神奇地具有正确的 id 并位于正确的位置。


如果项目设置为默认值,VS2005 及更高版本将自动生成并嵌入适当的清单。这个#pragma将添加额外的程序集依赖项
到生成的清单:-

#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

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
初心 2024-09-05 01:07:58

在这种情况下,延迟加载 DLL 是您的朋友。我不久前遇到了完全相同的问题,而且实际上相当简单。您向链接器(/DELAYLOAD 标志)指定哪些模块是延迟加载的,基本上这些模块不会在 PE 标头中列为显式导入,因此加载器在找不到所述模块时不会抱怨模块以及这些模块中对函数的所有调用都包装在一个存根中,以确保模块被加载和使用。函数找到了。

因此,假设您希望延迟加载 XmlLite 库。首先,您需要在链接器标志中指定 /DELAYLOAD:XmlLite.dll。然后,在模块的初始化函数(最好是 DllMain)中,您将 XmlLite DLL 解压到临时文件夹中,然后对其调用 LoadLibrary。从那时起,对 XmlLite.dll 导出的任何函数的每次调用都会自动解析。

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.

肤浅与狂妄 2024-09-05 01:07:58

使用 GetModuleFileName() 查找路径你的 dll 所在的位置。
然后使用 SetDllDirectory() 添加该内容dll 搜索路径的路径。

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

荭秂 2024-09-05 01:07:58

假设本机代码并且您可以使用 显式运行- 时间动态链接(而不是任何形式的隐式链接),使用GetModuleHandleGetModuleFileName 找出您的 dll 运行位置。

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

然后将 dll 的基本名称替换为要加载的plugin.dll 的名称。

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

对生成的字符串调用 LoadLibrary

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.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文