如何在通过LoadLibrary加载DLL时传递参数给DLL初始化函数?

4
如何通过LoadLibrary加载的DLL传递参数给初始化函数?这是否可能?而且不使用某种导出函数或共享内存。

4
为什么你想要这样做?只需要求调用者在您的DLL上调用一些初始化方法即可。 - Luke
1
通常拥有一个单独的初始化函数也更加安全:在DllMain中,你被允许做的事情是有限制的 - 正如MSDN文档所述,"为了提供更复杂的初始化,为DLL创建一个初始化例程。" (http://msdn.microsoft.com/en-us/library/ms885202.aspx)。反之亦然,卸载也是如此。 - BrendanMcK
4个回答

4

没有直接的方法。

最简单的方法可能是通过环境变量。在调用 LoadLibrary 之前,可以使用 setenv 轻松设置它们,然后 DLL(在同一进程中)可以使用 getenv 检索它们。


有比环境变量更好的方法吗?它确实很便携,但我想考虑共享内存。希望不涉及字符串。 - user656208
2
这里使用共享内存没有意义:可执行文件和 dll 都在同一个进程中运行! - Didier Trosset
唯一使用共享内存的实用性在于命名共享内存。这样,dll可以获取可执行文件放置在其中的数据。但是,命名共享内存不具备可移植性。此外,命名共享内存在某种程度上等同于内存映射文件。这导致了另一种发送参数的方式:文件 - Didier Trosset
假设进程的内存寻址是一个非常糟糕的想法。您不能期望东西加载在特定位置,并且使用mmap(或类似的工具)进行固定映射可能不适合。如果您不想写入磁盘怎么办?这就是为什么我说共享内存有意义(如IPC式共享内存,命名内存或无论您称之为)。 - user656208
我猜这里有个误会。要么你想将程序的数据用于DLL中,内存寻址没有问题,因为你的程序和从该程序加载的DLL共享同一个地址空间(这不是假设,而是事实)。要么你想从不同的程序(不同的进程)中访问加载的DLL中的相同数据,那就是另外一回事了。我已经回答了第一个问题。你的问题没有提到各种程序。 - Didier Trosset

1
另一种可能的解决方案是:创建第二个DLL,仅公开“setparam”和“getparam”方法,然后在应用程序(setparam)和dll的DllMain(getparam)中使用它。这些方法的基本形式是使用静态变量实现的,但您可以使用更复杂的技术。尽管稍微复杂一些,但此解决方案具有一些优点:
  • 它不使用任何“全局”约定(除了公共DLL的名称!)
  • 它不会消耗可能有限的资源(例如环境变量)
  • 它是通用的:您可以在任何地方使用相同的DLL。
  • 如果需要,它可以变得强大且复杂:例如,您可以使其线程安全。这只是实现问题。
这是一个最简示例:
// The "helper" DLL //
static int param;
void setparam(int v) { param = v; }
int getparam(void) { return param; }

// The application //
setparam(12345);
LoadLibrary("TheDLL.dll");

// The DLL to which you want to pass parameters //
BOOL WINAPI DllMain(HINSTANCE h,DWORD re,LPVOID res)
{
int param;
  switch (re)
  {
  case DLL_PROCESS_ATTACH:
     param = getparam();
//...

0

一种替代方法

  虽然我不确定这是否属于"共享内存"(因为您还可以使用此方法将数据发送到在单独进程中加载的DLL),但是...您可以使用VirtualAllocEx在特定地址上分配一些内存,使用WriteProcessMemory传入一个包含DLL所需所有数据的结构体,然后在加载DLL之前使用VirtualLock锁定它。

然后,在DLL的入口点函数中,我会使用VirtualUnlock,使用ReadProcessMemory获取那些数据,然后使用VirtualFree清理资源。

虽然有点不连贯,但如果您需要传递的不仅仅是简单字符串,这种方法非常有用。
请注意,为了使此方法工作,您必须在目标进程中具有读写访问权限。
 

示例(伪代码)

// YourApp.cpp

struct DataToSend {
    int myInt;
    char myStr[320];
};
DataToSend m_data = { 1337, "This is a test string...\0" };

// ...
PVOID remoteData = VirtualAllocEx( hTargetProcess, NULL, sizeof(m_data), MEM_RESERVE|MEM_COMMIT, PAGE_READWRITE );
WriteProcessMemory( hTargetProcess, remoteData, &m_data, sizeof(m_data), NULL );
VirtualLock( remoteData, sizeof(m_data) );
// Save the address (DWORD) of remoteData to the registry, to a local file, or using setenv as suggested in other answers here

 

// YourDll.cpp

BOOL APIENTRY DllMain( HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved )
{
    DataToSend m_data = {0};
    PVOID localData = /* address used in YourApp */ NULL;
    //...
    VirtualUnlock( localData, sizeof(m_data) );
    ReadProcessMemory( hProcess, localData, &m_data, sizeof(m_data), NULL );
    VirtualFree( localData, 0, MEM_RELEASE );
}

-1

这种方法很“糟糕”和“丑陋”,但是在进行调用之前,您可以使用内联汇编将参数推送到堆栈上,并以类似的方式将它们弹出。这是一种非常hack-ish的解决方案,但它确实可行。我只是提到它是因为它是可能的,而不是因为这是一个好的做事方式。


1
那怎么可能有效?DLL代码不知道在LoadLibrary调用和实际调用DllMain之间栈帧的大小是多少。 - Adam Rosenfield
因为如果你在做一些不好的事情,而且你知道不能直接传递参数,你可以找到当前帧基础并在其正下方查看。比如说你要替换一个函数,但是不能操作它使用的API,但你必须以某种方式传递参数...所以你传递一个标识符,它不应该出现在当前堆栈帧中,并在函数入口检查它是否是标识符。这很丑陋和糟糕。这就是为什么我指出它是hacky的原因。 - RobotHumans

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接