从 DLL 动态加载函数

我来看看。Dll 文件,我了解他们的用法,我试图了解如何使用它们。

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

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

但是,当我尝试编译这个。Cpp 文件,我认为它已经导入了。Dll 我有以下错误:

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 中的函数。

我希望这个问题有道理,我不会再找错人了。

248361 次浏览

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

You need a separate WinAPI function to get the address of dynamically loaded functions: 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 export your function from the DLL correctly. This can be done like this:

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

As Lundin notes, it's good practice to free the handle to the library if 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.

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.

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:

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.

Next problem is that GetProcAddress expects the function names as strings. So create a similar array consisting of the function names in the 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:

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 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_type union member.

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.

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:

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

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_assert to ensure that the struct/union has no padding might be in order still.

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.

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.

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

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.

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

#include "sn_factory.h"


#include <windows.h>

Create the external entry point. You can check that it exists using 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.

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

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