A better way to load DLL functions?

2020-07-22 17:55发布

I feel like there's a much, much better way than just having hundreds of signatures in typedefs, and then loading pointers by GetProcAddress. As far as I know, it is the easiest -- but dirty -- when it comes to loading DLL functions.

Is there a less messy way to load DLL functions? Specifically, a large amount of Winapi and Tool Help library functions? I know you can just "include a .lib," but I feel like that would cause unnecessary bloat; nor do I have access to the source code (although Jason C mentioned it is possible to go from a .dll to a .lib).

I was looking to code a library for this. I feel like the major roadblock is dealing with functions that have varying signatures; or is this precisely the reason why everyone uses typedefs instead of "some fanciful loop" to load their DLL functions?

标签: c++ c windows dll
6条回答
Anthone
2楼-- · 2020-07-22 17:58

Visual C++ has an option to delay load a DLL. Your code you links in the DLL's lib file, includes its headers, and call the functions as normal. The Visual C++ linker and runtime take care of calling LoadLibrary() and GetProcAddress() the first time a function is called.

However this will cause a runtime exception if the DLL cannot be loaded for any reason, rather than causing your application to fail to load as with a normally linked DLL.

If this DLL is always loaded and required by your application to run, then you should like to the DLL normally (using the .lib file). In that case you are gaining nothing by delay loading the DLL in that case.

查看更多
够拽才男人
3楼-- · 2020-07-22 18:02

There should be a function provided to load a dll manually given it's file name. In the windows API it's LoadLibrary(). It's generally not used unless you're developing a "plugin" style application or you need to manually control loading and unloading.

You should consider how your code will be used to determine if a static library or a dynamically loaded dll is the best solution for your requirements.

查看更多
【Aperson】
4楼-- · 2020-07-22 18:05

Creating a class as a wrapper of dll, where the public member function of the class are nothing but pointer to that of functions in dll obtained by get proc address would be one fine way to load functions from dll.

It would be better to use RAII ability of c++ to free libraries loaded by freeing libraries in destructors of the class.

This:

typedef int(WINAPI *ShellAboutProc)(HWND, LPCSTR, LPCSTR, HICON);

int main() {
  HMODULE hModule = LoadLibrary(TEXT("Shell32.dll"));

  ShellAboutProc shellAbout =
      (ShellAboutProc)GetProcAddress(hModule, "ShellAboutA");

  shellAbout(NULL, "hello", "world", NULL);

  FreeLibrary(hModule);
}

Can be achieved like this:

class ShellApi {
  DllHelper _dll{"Shell32.dll"};

public:
  decltype(ShellAboutA) *shellAbout = _dll["ShellAboutA"];
};

int main() {
  ShellApi shellApi;
  shellApi.shellAbout(NULL, "hello", "world", NULL);
}

Where the class DllHelper is:

class ProcPtr {
public:
  explicit ProcPtr(FARPROC ptr) : _ptr(ptr) {}

  template <typename T, typename = std::enable_if_t<std::is_function_v<T>>>
  operator T *() const {
    return reinterpret_cast<T *>(_ptr);
  }

private:
  FARPROC _ptr;
};

class DllHelper {
public:
  explicit DllHelper(LPCTSTR filename) : _module(LoadLibrary(filename)) {}

  ~DllHelper() { FreeLibrary(_module); }

  ProcPtr operator[](LPCSTR proc_name) const {
    return ProcPtr(GetProcAddress(_module, proc_name));
  }

  static HMODULE _parent_module;

private:
  HMODULE _module;
};

All credits to Benoit

I can't find a more elegant way than this.

查看更多
爷的心禁止访问
5楼-- · 2020-07-22 18:07

Your functions have to be declared somehow, so you need the function signatures. But, if you feel you are duplicating the function signatures, you can use decltype(&func_name) to eliminate that duplication (given a function func_name with the same target signature). You can wrap that in a macro to make it more palatable.

Another common approach is to shove all the function pointers into one big struct (or a bunch of task-specific structs), and load a pointer to the struct instead (e.g. by loading a function that returns a pointer to the struct). This looks like the following:

Common header

struct func_table {
    void (*f1)(int);
    int (*f2)(void);
};

DLL

static struct func_table table = {
    &f1_Implementation,
    &f2_Implementation,
};

DLLEXPORT struct func_table *getFuncTable(void) {
    return &table;
}

Program using the DLL

static struct func_table *funcs;

void loadFuncTable() {
    struct func_table (*getFuncTable)(void) = GetProcAddress(..., "getFuncTable");
    funcs = getFuncTable();
}

/* call funcs->f1(n) and funcs->f2() */
查看更多
爷、活的狠高调
6楼-- · 2020-07-22 18:12

There are several different ways of loading Windows libraries, but each way serves a different purpose.

Using LoadLibrary() and GetProcAddress() manually is intended to allow runtime decisions about several different dynamic linking situations. These calls are simply system calls which do not affect any aspect of the resulting PE file whatsoever, and the addresses returned by GetProcAddress() are not special in any manner with regards to code generation. This means that the caller is responsible for properly use these symbols at the very syntactical level (for example, using the proper calling convention and the correct number, size and alignment of the arguments, if any, for a function call).

Linking against a .lib file associated with a .dll is different. The client code refers to the runtime content using external identifiers, as if they were statically linked symbols. During the build process, the linker will resolve these identifiers with the symbols found in the .lib file. While, in principle, these symbols could point to anything at all (as any other symbol could), an automatically generated .lib file will simply provide symbols which act as tiny proxies to the memory content that will be filled at load time by the PE loader.

The manner in which these proxies are implemented is dependent on the type of memory access expected. For example, a symbol which refers to a function in the .dll will be implemented as a single indirect jmp instruction, in such a way that the original call instruction from the client code will hit the jmp instruction from the .lib content and will be forwarded to an address resolved by the PE loader at load time. At this address reside the dynamically loaded function's code.

Many more details aside, all of this is used to instruct the PE loader to do the same job as the client code would do by itself: call LoadLibrary() to map a PE into memory and dig through the export tables with GetProcAddress() to find and calculate the proper address to every needed symbol. The PE loader does this automatically at load time from the information found in the import table of the client executable.

In other words, load-time dynamic linking is slower and fatter than run-time dynamic linking by the tiniest amount, if any, but it is intended to provide a much simpler programming interface to the ordinary developer, specially because many libraries are always required and always available. Attempting to provide any additional logic through manual loading is not useful in these circumstances.

Summarizing, do not bother to use LoadLibrary() and GetProcAddress() just because they seem to provide more performance or robustness. Link against .lib files whenever possible.

Going further on the topic, it is even possible to create PE images which don't contain any import tables at all (and can still access system calls and other exported routines). This approach is used by malware to hide suspicious API usage (such as Winsock calls) by stripping any load-time information.

查看更多
\"骚年 ilove
7楼-- · 2020-07-22 18:20

The "bloat" of linking to a .lib file (by "bloat" I'm presuming you mean a few extra kB on the executable, which, ya know...) isn't "unnecessary" if you're using it for the convenience of avoiding dealing with hundreds of GetProcAddress calls. :)

Not quite sure what you mean by "some fanciful loop"; the typedefs used in this context serve a similar purpose to declarations in a header -- they provide the compiler and the human reader information about the signature of the function that is being called. One way or another, you have to provide this.

There are tools out there to generate a .lib from a .dll; but you'll still have to have a header declaring the functions you are calling so that the compiler knows what you're doing. Essentially, a .lib generated for a .dll is just a "stub" that loads the DLL and gets the function addresses for you. Different API, but essentially the same.

The only way around it is to design your DLL interface differently so that you don't have as many functions to deal with. However, in most cases I would not say that avoiding typedefs/declarations for functions is sufficient motivation to do something like this. E.g. you could expose exactly one function like (for example):

void PerformAction (LPCSTR action, DWORD parameter);

And have your implementation of PerformAction do something different depending on "action". That is certainly reasonable in certain situations that are unrelated to your post, but it is not really an appropriate "workaround" for the "problem" you are describing.

You pretty much just have to deal with it. Either create a header with declarations and generate a stub .lib, or create a header with typedefs and use LoadLibrary/GetProcAddress. The former will save you some typing. The latter lets you handle cases where the DLL is not present, or load DLLs that are unknown at design time (e.g. plugins as mentioned in another answer).

查看更多
登录 后发表回答