4

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

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

相关细节:

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

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

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

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

4

4 回答 4

7

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

您有一些选项可以解决此问题:

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

如果插件接口复杂,而你使用的dll接口不复杂,那么:

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

  • 最后一个选项是 Windows 程序员记录最少且理解最差的选项。基本上,我们使用为支持 .NET 而构建的技术的 Windows 版本:并排程序集。不要害怕。“并排程序集”非常简单,是一个普通的旧 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 时是一个令人困惑的名称)也允许使用<file>节点,因此可能可以跳过创建程序集,而直接使用

<?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 文件并手动输入清单,请知道在 Dlls 中要使用的资源 id 是 2,而不是 exe 示例中通常使用的 1。

如果您使用的是 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
于 2010-04-14T14:18:59.073 回答
2

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

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

于 2010-04-14T21:21:39.070 回答
0

假设本机代码并且您可以使用显式运行时动态链接(而不是任何形式的隐式链接),请使用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 。

于 2010-04-14T14:45:46.760 回答
0

使用GetModuleFileName () 找到你的dll所在的路径。然后使用SetDllDirectory () 将该路径添加到 dll 搜索路径中。

于 2010-04-14T14:17:30.670 回答