【问题标题】:Dynamically load a function from a DLL从 DLL 动态加载函数
【发布时间】:2012-01-31 13:42:18
【问题描述】:

我正在查看 .dll 文件,我了解它们的用法,并且我正在尝试了解如何使用它们。

我创建了一个 .dll 文件,其中包含一个返回名为 funci() 的整数的函数

使用此代码,我(认为)我已将 .dll 文件导入到项目中(没有投诉):

#include <windows.h>
#include <iostream>

int main() {
  HINSTANCE hGetProcIDDLL = LoadLibrary("C:\\Documents and Settings\\User\\Desktop  \\fgfdg\\dgdg\\test.dll");

  if (hGetProcIDDLL == NULL) {
    std::cout << "cannot locate the .dll file" << std::endl;
  } else {
    std::cout << "it has been called" << std::endl;
    return -1;
  }

  int a = funci();

  return a;
}

# funci function 

int funci() {
  return 40;
}

但是,当我尝试编译这个我认为已导入 .dll 的 .cpp 文件时,出现以下错误:

C:\Documents and Settings\User\Desktop\fgfdg\onemore.cpp||In function 'int main()':|
C:\Documents and Settings\User\Desktop\fgfdg\onemore.cpp|16|error: 'funci' was not     declared in this scope|
||=== Build finished: 1 errors, 0 warnings ===|

我知道 .dll 与头文件不同,所以我知道我不能导入这样的函数,但这是我能想出的最好的方法来证明我已经尝试过。

我的问题是,如何使用hGetProcIDDLL 指针访问.dll 中的函数。

我希望这个问题是有道理的,而且我不会再找错树了。

【问题讨论】:

  • 查找静态/动态链接。
  • 谢谢,我会调查一下
  • 我缩进了我的代码,但是当我把它推到这里时,格式混乱了,所以我最终将它全部缩进了 4 行

标签: c++ winapi dll


【解决方案1】:

LoadLibrary 并没有按照你的想法去做。它将 DLL 加载到当前进程的内存中,但它确实不会神奇地导入其中定义的函数!这是不可能的,因为函数调用由链接器在编译时解析,而LoadLibrary 在运行时调用(请记住,C++ 是一种statically typed 语言)。

您需要一个单独的 WinAPI 函数来获取动态加载函数的地址:GetProcAddress

示例

#include <windows.h>
#include <iostream>

/* Define a function pointer for our imported
 * function.
 * This reads as "introduce the new type f_funci as the type: 
 *                pointer to a function returning an int and 
 *                taking no arguments.
 *
 * Make sure to use matching calling convention (__cdecl, __stdcall, ...)
 * with the exported function. __stdcall is the convention used by the WinAPI
 */
typedef int (__stdcall *f_funci)();

int main()
{
  HINSTANCE hGetProcIDDLL = LoadLibrary("C:\\Documents and Settings\\User\\Desktop\\test.dll");

  if (!hGetProcIDDLL) {
    std::cout << "could not load the dynamic library" << std::endl;
    return EXIT_FAILURE;
  }

  // resolve function address here
  f_funci funci = (f_funci)GetProcAddress(hGetProcIDDLL, "funci");
  if (!funci) {
    std::cout << "could not locate the function" << std::endl;
    return EXIT_FAILURE;
  }

  std::cout << "funci() returned " << funci() << std::endl;

  return EXIT_SUCCESS;
}

另外,您应该正确地export DLL 中的函数。可以这样完成:

int __declspec(dllexport) __stdcall funci() {
   // ...
}

正如 Lundin 所说,如果您不再需要它们,最好使用 free the handle to the library。如果没有其他进程仍然持有同一个 DLL 的句柄,这将导致它被卸载。

【讨论】:

  • 听起来可能是个愚蠢的问题,但 f_funci 的类型是/应该是什么?
  • 除此之外,答案非常好,易于理解
  • 请注意,f_funci 实际上是一个类型(而不是一个类型)。 f_funci 类型读作“指向返回 int 且不带参数的函数的指针”。有关 C 中函数指针的更多信息,请访问 newty.de/fpt/index.html
  • 再次感谢回复,funci不带参数,返回整数;我编辑了问题以显示已编译的函数?进入.dll。当我尝试在包含“typedef int (f_funci)();”后运行时我收到此错误:C:\Documents and Settings\User\Desktop\fgfdg\onemore.cpp||In function 'int main()':| C:\Documents and Settings\User\Desktop\fgfdg\onemore.cpp|18|error: cannot convert 'int ()()' to 'const CHAR*' for argument '2' to 'int (* GetProcAddress(HINSTANCE__, const CHAR))()'| ||=== 构建完成:1 个错误,0 个警告 ===|
  • 好吧,我忘了那里有演员表(编辑过)。然而,这个错误似乎是另一个错误,你确定你使用了正确的代码吗?如果是,您能否将失败的代码和完整的编译器输出粘贴到pastie.org 上?另外,您在评论中写的 typedef 是错误的(缺少 *,这可能导致错误)
【解决方案2】:

除了已经发布的答案之外,我想我应该分享一个方便的技巧,我可以通过函数指针将所有 DLL 函数加载到程序中,而无需为每个函数编写单独的 GetProcAddress 调用。我也喜欢在 OP 中尝试直接调用函数。

首先定义一个通用函数指针类型:

typedef int (__stdcall* func_ptr_t)();

使用什么类型并不重要。现在创建一个该类型的数组,它对应于 DLL 中的函数数量:

func_ptr_t func_ptr [DLL_FUNCTIONS_N];

在这个数组中,我们可以存储指向 DLL 内存空间的实际函数指针。

下一个问题是GetProcAddress 期望函数名是字符串。因此,在 DLL 中创建一个包含函数名称的类似数组:

const char* DLL_FUNCTION_NAMES [DLL_FUNCTIONS_N] = 
{
  "dll_add",
  "dll_subtract",
  "dll_do_stuff",
  ...
};

现在我们可以轻松地在循环中调用 GetProcAddress() 并将每个函数存储在该数组中:

for(int i=0; i<DLL_FUNCTIONS_N; i++)
{
  func_ptr[i] = GetProcAddress(hinst_mydll, DLL_FUNCTION_NAMES[i]);

  if(func_ptr[i] == NULL)
  {
    // error handling, most likely you have to terminate the program here
  }
}

如果循环成功,我们现在唯一的问题就是调用函数。前面的函数指针 typedef 没有帮助,因为每个函数都有自己的签名。这可以通过创建一个包含所有函数类型的结构来解决:

typedef struct
{
  int  (__stdcall* dll_add_ptr)(int, int);
  int  (__stdcall* dll_subtract_ptr)(int, int);
  void (__stdcall* dll_do_stuff_ptr)(something);
  ...
} functions_struct;

最后,要将它们连接到之前的数组,创建一个联合:

typedef union
{
  functions_struct  by_type;
  func_ptr_t        func_ptr [DLL_FUNCTIONS_N];
} functions_union;

现在您可以使用方便的循环从 DLL 加载所有函数,但通过 by_type 联合成员调用它们。

当然,打出类似

的东西有点累

functions.by_type.dll_add_ptr(1, 1); 每当您想调用函数时。

事实证明,这就是我在名称中添加“ptr”后缀的原因:我想让它们与实际的函数名称不同。我们现在可以通过使用一些宏来平滑 icky 结构语法并获得所需的名称:

#define dll_add (functions.by_type.dll_add_ptr)
#define dll_subtract (functions.by_type.dll_subtract_ptr)
#define dll_do_stuff (functions.by_type.dll_do_stuff_ptr)

瞧,您现在可以使用具有正确类型和参数的函数名称,就好像它们静态链接到您的项目一样:

int result = dll_add(1, 1);

免责声明:严格来说,不同函数指针之间的转换不是由 C 标准定义的,也不安全。如此正式地,我在这里所做的是未定义的行为。但是,在 Windows 世界中,无论函数指针的类型如何,函数指针的大小始终相同,并且它们之间的转换在我使用过的任何 Windows 版本上都是可预测的。

此外,理论上可能会在联合/结构中插入填充,这会导致一切失败。但是,指针的大小恰好与 Windows 中的对齐要求相同。 static_assert 以确保结构/联合没有填充可能仍然是有序的。

【讨论】:

  • 这种 C 风格的方法可行。但是使用 C++ 构造来避免#defines 不是合适吗?
  • @harper 在 C++11 中你可以使用 auto dll_add = ...,但在 C++03 中没有我能想到的可以简化任务的构造(我也没有看到#defines 这里的特殊问题)
  • 由于这都是特定于 WinAPI 的,因此您无需键入自己的 func_ptr_t。相反,您可以使用FARPROC,它是GetProcAddress 的返回类型。这可以让您在不向 GetProcAddress 调用添加强制转换的情况下以更高的警告级别进行编译。
  • @NiklasB。您一次只能将auto 用于一个函数,这违背了在循环中一次性完成的想法。但是数组 std::function 有什么问题
  • @Francesco std::function 类型将与 funcptr 类型一样不同。我猜可变参数模板会有所帮助
【解决方案3】:

这并不完全是一个热门话题,但我有一个工厂类,它允许 dll 创建一个实例并将其作为 DLL 返回。这就是我来寻找但无法准确找到的东西。

它被称为,

IHTTP_Server *server = SN::SN_Factory<IHTTP_Server>::CreateObject();
IHTTP_Server *server2 =
      SN::SN_Factory<IHTTP_Server>::CreateObject(IHTTP_Server_special_entry);

其中 IHTTP_Server 是在另一个 DLL 或同一个 DLL 中创建的类的纯虚拟接口。

DEFINE_INTERFACE 用于为类 id 提供接口。放置在界面内;

一个接口类看起来像,

class IMyInterface
{
    DEFINE_INTERFACE(IMyInterface);

public:
    virtual ~IMyInterface() {};

    virtual void MyMethod1() = 0;
    ...
};

头文件是这样的

#if !defined(SN_FACTORY_H_INCLUDED)
#define SN_FACTORY_H_INCLUDED

#pragma once

这些库在这个宏定义中列出。每个库/可执行文件一行。如果我们可以调用另一个可执行文件会很酷。

#define SN_APPLY_LIBRARIES(L, A)                          \
    L(A, sn, "sn.dll")                                    \
    L(A, http_server_lib, "http_server_lib.dll")          \
    L(A, http_server, "")

然后为每个 dll/exe 定义一个宏并列出其实现。 Def 表示它是接口的默认实现。如果它不是默认值,则为用于识别它的接口命名。即,特殊的,名称将是 IHTTP_Server_special_entry。

#define SN_APPLY_ENTRYPOINTS_sn(M)                                     \
    M(IHTTP_Handler, SNI::SNI_HTTP_Handler, sn, def)                   \
    M(IHTTP_Handler, SNI::SNI_HTTP_Handler, sn, special)

#define SN_APPLY_ENTRYPOINTS_http_server_lib(M)                        \
    M(IHTTP_Server, HTTP::server::server, http_server_lib, def)

#define SN_APPLY_ENTRYPOINTS_http_server(M)

所有库都设置好后,头文件使用宏定义来定义需要的。

#define APPLY_ENTRY(A, N, L) \
    SN_APPLY_ENTRYPOINTS_##N(A)

#define DEFINE_INTERFACE(I) \
    public: \
        static const long Id = SN::I##_def_entry; \
    private:

namespace SN
{
    #define DEFINE_LIBRARY_ENUM(A, N, L) \
        N##_library,

这会为库创建一个枚举。

    enum LibraryValues
    {
        SN_APPLY_LIBRARIES(DEFINE_LIBRARY_ENUM, "")
        LastLibrary
    };

    #define DEFINE_ENTRY_ENUM(I, C, L, D) \
        I##_##D##_entry,

这会为接口实现创建一个枚举。

    enum EntryValues
    {
        SN_APPLY_LIBRARIES(APPLY_ENTRY, DEFINE_ENTRY_ENUM)
        LastEntry
    };

    long CallEntryPoint(long id, long interfaceId);

这定义了工厂类。这里不多说。

    template <class I>
    class SN_Factory
    {
    public:
        SN_Factory()
        {
        }

        static I *CreateObject(long id = I::Id )
        {
            return (I *)CallEntryPoint(id, I::Id);
        }
    };
}

#endif //SN_FACTORY_H_INCLUDED

那么CPP就是,

#include "sn_factory.h"

#include <windows.h>

创建外部入口点。您可以使用depends.exe 检查它是否存在。

extern "C"
{
    __declspec(dllexport) long entrypoint(long id)
    {
        #define CREATE_OBJECT(I, C, L, D) \
            case SN::I##_##D##_entry: return (int) new C();

        switch (id)
        {
            SN_APPLY_CURRENT_LIBRARY(APPLY_ENTRY, CREATE_OBJECT)
        case -1:
        default:
            return 0;
        }
    }
}

宏设置了所有需要的数据。

namespace SN
{
    bool loaded = false;

    char * libraryPathArray[SN::LastLibrary];
    #define DEFINE_LIBRARY_PATH(A, N, L) \
        libraryPathArray[N##_library] = L;

    static void LoadLibraryPaths()
    {
        SN_APPLY_LIBRARIES(DEFINE_LIBRARY_PATH, "")
    }

    typedef long(*f_entrypoint)(long id);

    f_entrypoint libraryFunctionArray[LastLibrary - 1];
    void InitlibraryFunctionArray()
    {
        for (long j = 0; j < LastLibrary; j++)
        {
            libraryFunctionArray[j] = 0;
        }

        #define DEFAULT_LIBRARY_ENTRY(A, N, L) \
            libraryFunctionArray[N##_library] = &entrypoint;

        SN_APPLY_CURRENT_LIBRARY(DEFAULT_LIBRARY_ENTRY, "")
    }

    enum SN::LibraryValues libraryForEntryPointArray[SN::LastEntry];
    #define DEFINE_ENTRY_POINT_LIBRARY(I, C, L, D) \
            libraryForEntryPointArray[I##_##D##_entry] = L##_library;
    void LoadLibraryForEntryPointArray()
    {
        SN_APPLY_LIBRARIES(APPLY_ENTRY, DEFINE_ENTRY_POINT_LIBRARY)
    }

    enum SN::EntryValues defaultEntryArray[SN::LastEntry];
        #define DEFINE_ENTRY_DEFAULT(I, C, L, D) \
            defaultEntryArray[I##_##D##_entry] = I##_def_entry;

    void LoadDefaultEntries()
    {
        SN_APPLY_LIBRARIES(APPLY_ENTRY, DEFINE_ENTRY_DEFAULT)
    }

    void Initialize()
    {
        if (!loaded)
        {
            loaded = true;
            LoadLibraryPaths();
            InitlibraryFunctionArray();
            LoadLibraryForEntryPointArray();
            LoadDefaultEntries();
        }
    }

    long CallEntryPoint(long id, long interfaceId)
    {
        Initialize();

        // assert(defaultEntryArray[id] == interfaceId, "Request to create an object for the wrong interface.")
        enum SN::LibraryValues l = libraryForEntryPointArray[id];

        f_entrypoint f = libraryFunctionArray[l];
        if (!f)
        {
            HINSTANCE hGetProcIDDLL = LoadLibraryA(libraryPathArray[l]);

            if (!hGetProcIDDLL) {
                return NULL;
            }

            // resolve function address here
            f = (f_entrypoint)GetProcAddress(hGetProcIDDLL, "entrypoint");
            if (!f) {
                return NULL;
            }
            libraryFunctionArray[l] = f;
        }
        return f(id);
    }
}

每个库都包含这个“cpp”,每个库/可执行文件都有一个存根 cpp。任何特定的编译头文件。

#include "sn_pch.h"

设置这个库。

#define SN_APPLY_CURRENT_LIBRARY(L, A) \
    L(A, sn, "sn.dll")

主 cpp 的包含。我猜这个cpp可能是一个.h。但是有不同的方法可以做到这一点。这种方法对我有用。

#include "../inc/sn_factory.cpp"

【讨论】:

    猜你喜欢
    • 2020-08-26
    • 2010-09-30
    • 1970-01-01
    • 2011-09-06
    • 1970-01-01
    • 1970-01-01
    • 2013-10-10
    • 1970-01-01
    • 2011-01-22
    相关资源
    最近更新 更多