此bounty已结束.回答此问题可获得+500声望奖励。赏金宽限期三小时后结束。sorush-r希望引起更多关注此问题。
我正在开发一个应用程序,它提供了一个插件接口,让客户在应用程序中开发他们的逻辑。然后在运行时动态加载插件。我们为插件提供了一个干净的C接口,以使事情尽可能地可移植。然而,我们最近发现了一个过渡依赖关系的问题:当一个插件链接到它自己的依赖项时,它恰好也是应用的依赖项,只加载应用附带的版本。
因此在下面的配置中,lib_b.dll
是插件,它使用lib_a.dll
作为私有依赖项。虽然因为Executable
也链接到同一个库的不同版本,但它们的版本没有被选择。
+----------------------+ +-------------------------------+
| | LoadLibrary | |
| Executable.exe ------+--------------+--> plugins |
| | | | | |
| +--> lib_a.dll (v1) | | +--> lib_b.dll |
| | | | |
+----------------------+ | +--> lib_a.dll (v2) |
| |
+-------------------------------+
字符串
我正在寻找一个解决方案,以隔离地址空间和符号的依赖关系,从我的应用程序。这个想法是让Executable
只关心它在运行时从插件加载的符号,而不是插件内部使用的符号。
我们这样做load_library:
HMODULE h = ::LoadLibraryExA(".../plugins/library_b.dll",
NULL, LOAD_LIBRARY_SEARCH_DLL_LOAD_DIR);
型
在插件中唯一有趣的一点是这样达到的:
callback_type do_the_thing_in_b = GetProcAddress(h, "do_the_thing_in_b");
int answer = do_the_thing_in_b(3.14, 42);
型
更新
应用程序可以在任何时候重新编译或更改,但在编译时我们没有插件的信息。这个想法是,客户端可以创建自己的插件,并把它放在那里。
我们也不能修改插件。我们可以扫描该目录并做一些事情,但这是我们可以做的更改的范围。我们不能重新编译它们或决定它们的依赖结构。
插件通过接口库链接到我们的可执行文件并直接从可执行文件调用函数**
2条答案
按热度按时间agxfikkp1#
总之,
我认为你已经击中了“隔离地址空间”的要害,就像DaniilVlasenko在评论中建议的那样。这也是FireFox现在用标签所做的;每一个都是单独的过程。
如果您能够控制接口,那么您就能够将主可执行文件分离到一个进程中,并拥有一个可以加载单个插件的DLL(及其依赖项)的薄垫片进程。您将为每个插件启动其中一个垫片。进程将通过某种机器内RPC进行通信。这一切都是假设数据结构在可执行文件和插件之间不共享,而不是通过函数调用和返回。
那会是什么样子呢?
RPC(例如GPB-RPC)感觉不太对,因为您需要一个RPC以一种方式让可执行文件调用插件,但是需要允许插件反过来调用可执行文件中的函数。
我将使用类似ZeroMQ的东西作为主可执行文件和插件之间的传输,并使用类似GPB的东西来制定消息,1)指示要调用的函数,以及2)它的参数是什么。序列将如下所示:
1.每个插件有2个ZMQREQ/REP套接字对,一对从可执行程序到插件shim进程(调用前向),另一对以相反的方向运行(回调)。每个进程都以REQ和REP套接字结束。
1.在主可执行文件中制定一条消息,该消息将被发送到插件shim,以调用提供的函数
1.通过调用前转REQ套接字从主可执行文件发送该消息,然后继续轮询调用前转REQ套接字和回调REP套接字。这允许主可执行文件或者接收返回值,或者接收执行它自己的函数之一的请求
1.插件函数可能希望调用主可执行文件中的函数。您必须在shim进程中实现所有此类函数的存根版本。瘦垫片中的存根函数制定包含调用参数的消息,并通过其自己的回调REQ套接字将其发送回主可执行文件,然后在回调REQ套接字上的zmq_recv()上阻塞。
1.主可执行文件同时轮询其调用转发REQ和回调REP套接字,ZMQ告诉它回调REP套接字上有一条消息。它读取这个消息,执行指定的函数,收集结果。
1.结果通过回调REP套接字发送回shim进程。主可执行文件返回轮询两个套接字。
1.在zmq_recv上阻塞的shim进程接收结果消息,并将结果返回给DLL插件中的调用函数。
1.插件函数本身最终完成,并将结果返回给shim进程
1.这个最终结果被打包成一条消息,通过shim中的调用转发REP套接字发送回主可执行文件
1.这次主可执行程序被告知在其调用转发REQ套接字上有一条消息就绪-插件的回复。
1.读取该消息,并将数据返回到主可执行文件中想要调用插件的任何内容。
这允许的是
ZMQ将是有用的,因为它听起来像是在主可执行文件和插件之间没有直接的客户机/服务器或RPC关系;它们更加相互依赖。ZMQ是Actor Model,它支持上述类型的模式。
ZeroMQ的一个免费赠品是,掌握了这一点,插件可以很容易地完全或组合在另一台机器上(即一些是本地的,一些是远程的,一些是在世界另一端的Linux上运行的,等等)。
显然,如果存在共享的数据结构,那么拥有两个独立的进程是没有帮助的,尽管我认为这可以通过将它们放在共享内存中来克服。但是,所有插件shim进程都必须与主可执行文件位于同一台机器上。
如果事情是多线程的,我不认为上面概述的模式真的改变了很多。您可能希望消息中的字段指示正在发生的事情。如果插件要操作由主可执行文件创建的信号量、互斥量等,则可能会有点困难。
8oomwypt2#
在加载DLL期间,Windows总是调用函数
RtlDosApplyFileIsolationRedirection_Ustr
。它可以导出,所以很容易就可以上钩。使用此API,我们可以重定向(替换)戴尔名称。所以首先尝试挂接这个API:
字符串
SuspendThreads
(optional)实现可以如下:型
DetourUpdateThread
挂起并保存DetourThread
列表中的线程句柄。并在DetourTransactionAbort
或DetourTransactionCommit
中恢复它。但它不关闭保存的hThread
。作为结果需要由self维护线程的附加列表,用于关闭它处理..(免费)好的。让我们钩住
RtlDosApplyFileIsolationRedirection_Ustr
。现在我们需要实现hook_RtlDosApplyFileIsolationRedirection_Ustr
让我们有下一个API:
型
在下一个代码中包含加载插件:
型
假设
./plugin/
是应用程序文件夹内的文件夹(exe位于其中)并且它包含每个插件的子文件夹(A
,B
,..)型
因此,对于这段代码,我们尝试加载
./plugin/A/some-plugin.dll
如果
some-plugin.dll
有来自lib-Y.dll
的静态依赖(或者在dll入口点调用LoadLibrary
-这真的没问题)并且存在./plugin/A/lob-Y.dll
文件-我们尝试准确地加载./plugin/A/lob-Y.dll
。即使./lib-Y.dll
或/和./plugin/B/lib-Y.dll
已经在进程中加载。型
以及最后-插件路径API的实现:
型
在开始时,我们必须调用
InitPluginPath()
,在退出时调用FreePluginPath()