C++ 从 DLL 动态加载函数

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/8696653/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-28 18:59:53  来源:igfitidea点击:

Dynamically load a function from a DLL

c++winapidll

提问by Niklas B.

I'm having a little look at .dll files, I understand their usage and I'm trying to understand how to use them.

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

I have created a .dll file that contains a function that returns an integer named funci()

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

using this code, I (think) I've imported the .dll file into the project(there's no complaints):

使用此代码,我(认为)我已将 .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;
}

However when I try to compile this .cpp file that I think has imported the .dll I have the following error:

但是,当我尝试编译这个我认为已导入 .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 ===|

I know a .dll is different from a header file so I know I can't import a function like this but it's the best I could come up with to show that I've tried.

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

My question is, how can I use the hGetProcIDDLLpointer to access the function within the .dll.

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

I hope this question makes sense and I'm not barking up some wrong tree yet again.

我希望这个问题是有道理的,而且我不会再次咆哮一些错误的树。

回答by Niklas B.

LoadLibrarydoes not do what you think it does. It loads the DLL into the memory of the current process, but it does notmagically import functions defined in it! This wouldn't be possible, as function calls are resolved by the linker at compile time while LoadLibraryis called at runtime (remember that C++ is a statically typedlanguage).

LoadLibrary不会做你认为它会做的事情。它加载DLL到当前进程的内存,但它并不会神奇地导入它定义的功能!这是不可能的,因为函数调用在编译时由链接器解析,而LoadLibrary在运行时调用(请记住,C++ 是一种静态类型语言)。

You need a separate WinAPI function to get the address of dynamically loaded functions: GetProcAddress.

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

Example

例子

#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;
}

Also, you should exportyour function from the DLL correctly. This can be done like this:

此外,您应该正确地从 DLL导出您的函数。这可以像这样完成:

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

As Lundin notes, it's good practice to free the handle to the libraryif you don't need them it longer. This will cause it to get unloaded if no other process still holds a handle to the same DLL.

正如 Lundin 所指出的,如果您不再需要它们,最好释放库的句柄。如果没有其他进程仍然持有同一个 DLL 的句柄,这将导致它被卸载。

回答by Lundin

In addition to the already posted answer, I thought I should share a handy trick I use to load all the DLL functions into the program through function pointers, without writing a separate GetProcAddress call for each and every function. I also like to call the functions directly as attempted in the OP.

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

Start by defining a generic function pointer type:

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

typedef int (__stdcall* func_ptr_t)();

What types that are used aren't really important. Now create an array of that type, which corresponds to the amount of functions you have in the DLL:

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

func_ptr_t func_ptr [DLL_FUNCTIONS_N];

In this array we can store the actual function pointers that point into the DLL memory space.

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

Next problem is that GetProcAddressexpects the function names as strings. So create a similar array consisting of the function names in the DLL:

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

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

Now we can easily call GetProcAddress() in a loop and store each function inside that array:

现在我们可以轻松地在循环中调用 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
  }
}

If the loop was successful, the only problem we have now is calling the functions. The function pointer typedef from earlier isn't helpful, because each function will have its own signature. This can be solved by creating a struct with all the function types:

如果循环成功,我们现在唯一的问题就是调用函数。之前的函数指针 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;

And finally, to connect these to the array from before, create a union:

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

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

Now you can load all the functions from the DLL with the convenient loop, but call them through the by_typeunion member.

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

But of course, it is a bit burdensome to type out something like

但是当然,输入类似的东西有点麻烦

functions.by_type.dll_add_ptr(1, 1);whenever you want to call a function.

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

As it turns out, this is the reason why I added the "ptr" postfix to the names: I wanted to keep them different from the actual function names. We can now smooth out the icky struct syntax and get the desired names, by using some macros:

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

#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)

And voilà, you can now use the function names, with the correct type and parameters, as if they were statically linked to your project:

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

int result = dll_add(1, 1);


Disclaimer: Strictly speaking, conversions between different function pointers are not defined by the C standard and not safe. So formally, what I'm doing here is undefined behavior. However, in the Windows world, function pointers are always of the same size no matter their type and the conversions between them are predictable on any version of Windows I've used.

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

Also, there might in theory be padding inserted in the union/struct, which would cause everything to fail. However, pointers happen to be of the same size as the alignment requirement in Windows. A static_assertto ensure that the struct/union has no padding might be in order still.

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

回答by Peter Driscoll

This is not exactly a hot topic, but I have a factory class that allows a dll to create an instance and return it as a DLL. It is what I came looking for but couldn't find exactly.

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

It is called like,

它被称为,

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

where IHTTP_Server is the pure virtual interface for a class created either in another DLL, or the same one.

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

DEFINE_INTERFACE is used to give a class id an interface. Place inside interface;

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

An interface class looks like,

一个接口类看起来像,

class IMyInterface
{
    DEFINE_INTERFACE(IMyInterface);

public:
    virtual ~IMyInterface() {};

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

The header file is like this

头文件是这样的

#if !defined(SN_FACTORY_H_INCLUDED)
#define SN_FACTORY_H_INCLUDED

#pragma once

The libraries are listed in this macro definition. One line per library/executable. It would be cool if we could call into another executable.

库在此宏定义中列出。每个库/可执行文件一行。如果我们可以调用另一个可执行文件,那就太酷了。

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

Then for each dll/exe you define a macro and list its implementations. Def means that it is the default implementation for the interface. If it is not the default, you give a name for the interface used to identify it. Ie, special, and the name will be IHTTP_Server_special_entry.

然后为每个 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)

With the libraries all setup, the header file uses the macro definitions to define the needful.

随着库的全部设置,头文件使用宏定义来定义需要的。

#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,

This creates an enum for the libraries.

这会为库创建一个枚举。

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

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

This creates an enum for interface implementations.

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

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

    long CallEntryPoint(long id, long interfaceId);

This defines the factory class. Not much to it here.

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

    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

Then the CPP is,

那么CPP是,

#include "sn_factory.h"

#include <windows.h>

Create the external entry point. You can check that it exists using depends.exe.

创建外部入口点。您可以使用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;
        }
    }
}

The macros set up all the data needed.

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

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);
    }
}

Each library includes this "cpp" with a stub cpp for each library/executable. Any specific compiled header stuff.

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

#include "sn_pch.h"

Setup this library.

设置这个库。

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

An include for the main cpp. I guess this cpp could be a .h. But there are different ways you could do this. This approach worked for me.

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

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