【问题标题】:How to pass arguments to DLL initialization (ex. when loaded via LoadLibrary)?如何将参数传递给 DLL 初始化(例如通过 LoadLibrary 加载时)?
【发布时间】:2011-04-15 14:38:13
【问题描述】:

如何将参数传递给通过 LoadLibrary 加载的 DLL 的初始化函数?有可能吗?无需借助某种导出函数或共享内存。

【问题讨论】:

  • 您为什么要这样做?只需要调用者在你的 DLL 上调用一些初始化方法。
  • 拥有一个单独的初始化函数通常也更安全:在 DllMain 中允许您执行的操作是有限制的 - 正如 DLL main 的 MSDN 文档所说,“要提供更复杂的初始化,请创建一个初始化DLL 的例程。” (msdn.microsoft.com/en-us/library/ms885202.aspx)。同样用于取消初始化。

标签: c windows winapi dll


【解决方案1】:

没有直接的方法。

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

【讨论】:

  • 有什么比环境变量更好的方法吗?它有点便携,是的,但我可能想考虑共享内存。不涉及利用字符串的东西。
  • 这里没有使用共享内存的意义:可执行文件和 dll 在同一个进程中运行!
  • 共享内存的唯一用途是命名共享内存。这样,dll 就可以获取可执行文件放置在那里的数据。但是命名共享内存是不可移植的。此外,named 共享内存在某种程度上等同于内存映射文件。这导致了另一种发送参数的方式:一个文件.
  • 关于进程的内存寻址的假设是一个非常糟糕的主意。您不能指望将东西加载到特定位置,并且使用 mmap(或类似设施)进行固定映射可能不合适。如果您不想写入磁盘怎么办?因此,为什么我说共享内存是有道理的(就像在类似 IPC 的共享内存、命名内存,或者你怎么称呼它)。
  • 我想这里有一个误解。您想将程序的数据使用到 DLL 中,并且内存寻址没有问题,因为您的程序和 DLL(从该程序加载)共享相同的地址空间(这里没有假设:这是真的)。或者您想访问从不同程序(不同进程)加载的 DLL 中的相同数据,那是另一回事。我已经回答了第一个。你的问题没有提到各种程序。
【解决方案2】:

另一种可能的解决方案:创建仅公开“setparam”和“getparam”方法的第二个 DLL,而不是在应用程序 (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();
//...

【讨论】:

    【解决方案3】:

    另一种方法

      虽然我不太确定这是否属于“共享内存”(因为您也可以使用此方法将数据发送到在单独进程中加载​​的 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 );
    }
    

    【讨论】:

      【解决方案4】:

      它是“坏的”和“丑陋的”,但您可以在调用之前使用内联 ASM 将参数推送到堆栈,然后以几乎相同的方式将它们弹出。一个非常hack-ish的解决方案,但它可以工作。我只注意到它是因为它是可能的,而不是因为它是做事的好方法。

      【讨论】:

      • 这怎么可能行得通? DLL 代码不知道在LoadLibrary 调用和DllMain 实际被调用之间的堆栈帧有多大。
      • 因为如果你在做坏事,而且你知道你不能直接传递args,你可以找到当前的frame base并在它的正下方查看。假设您正在替换一个函数并且无法操作它使用的 api,但是您必须以某种方式传递 args ......所以您传递一个不应该发生的标识符传递到当前堆栈帧所在的位置并检查它是否是一个标识符关于函数入口。它又丑又坏。这就是为什么我注意到它很hacky。
      猜你喜欢
      • 2010-12-14
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-02-19
      • 1970-01-01
      • 2020-11-09
      • 2010-09-13
      相关资源
      最近更新 更多