Is there a better way to load a dll in C++?

2019-02-01 13:57发布

问题:

Right now I do something like this and it seems messy if I end having a lot of functions I want to reference in my DLL. Is there a better and cleaner way of accessing the functions without having to create a typedef for each function definition so that it will compile and load the function properly. I mean the function definitions are already in the .h file and I shouldn't have to redeclare them after I load the function (or do I?) Is there a better solution than using LoadLibary? I don't necessarily need that function if there is a way I can do the same thing within Visual Studio 2005 project settings.


BHannan_Test_Class.h

#include "stdafx.h"
#include <windows.h>

#ifndef BHANNAN_TEST_CLASS_H_
#define BHANNAN_TEST_CLASS_H_

extern "C" {

    // Returns n! (the factorial of n).  For negative n, n! is defined to be 1.
    int __declspec (dllexport) Factorial(int n);

    // Returns true iff n is a prime number.
    bool __declspec (dllexport) IsPrime(int n);

}

#endif  // BHANNAN_TEST_CLASS_H_

BHannan_Test_Class.cpp

#include "stdafx.h"
#include "BHannan_Test_Class.h"

// Returns n! (the factorial of n).  For negative n, n! is defined to be 1.
int Factorial(int n) {
  int result = 1;
  for (int i = 1; i <= n; i++) {
    result *= i;
  }

  return result;
}

// Returns true iff n is a prime number.
bool IsPrime(int n) {
  // Trivial case 1: small numbers
  if (n <= 1) return false;

  // Trivial case 2: even numbers
  if (n % 2 == 0) return n == 2;

  // Now, we have that n is odd and n >= 3.

  // Try to divide n by every odd number i, starting from 3
  for (int i = 3; ; i += 2) {
    // We only have to try i up to the squre root of n
    if (i > n/i) break;

    // Now, we have i <= n/i < n.
    // If n is divisible by i, n is not prime.
    if (n % i == 0) return false;
  }

  // n has no integer factor in the range (1, n), and thus is prime.
  return true;
}

dll_test.cpp

#include <BHannan_Test_Class.h>

typedef int (*FactorialPtr) (int);
FactorialPtr myFactorial=NULL;

// Tests factorial of negative numbers.
TEST(FactorialTest, Negative) {

    HMODULE myDLL = LoadLibrary("BHannan_Sample_DLL.dll");

    if(myDLL) {
        myFactorial = (FactorialPtr) GetProcAddress(myDLL,"Factorial");

        if(myFactorial)
        {
            EXPECT_EQ(1, myFactorial(-5));
            EXPECT_EQ(1, myFactorial(-1));
            EXPECT_TRUE(myFactorial(-10) > 0);
        }

        FreeLibrary(myDLL);
    }

}

回答1:

After building your .dll get the .lib file nearby and link your test application with it. Use functions as they are declared in .h

There's a minor change you need to do in your header file:

#ifdef EXPORTS_API
  #define MY_API_EXPORT __declspec (dllexport)
#else
  #define MY_API_EXPORT __declspec (dllimport)
#endif

extern "C" {
    int MY_API_EXPORT Factorial(int n);

    // do the same for other functions
}

This way, when building your dll you define EXPORTS_API in your project settings and functions get exported, in the client application, no need to define anything.



回答2:

In the Windows world, there are (at least) 4 ways to use DLLs:

  1. Run-Time Dynamic Linking (What you're doing now)
  2. Load-Time Dynamic Linking (the "typical" way of using DLLs)
  3. Delay-Load Dynamic Linking
  4. DLL Forwarding

I don't have to explain Run-Time Dynamic Linking since you're already doing it. I choose not to explain Delay-Load Dynamic Linking now beyond just describing what it is in general terms. Delay Load is essentially the same as Load-Time Dynamic Linking except it's done Just-In-Time instead of at application load. This is not as useful or as beneficial as you might think, it is difficult to work with and tricky to code for. So let's not go there, at least for now. DLL Forwarding is even more exotic than Delay-Loading -- so exotic, I'd never even heard of it until @mox mentioned it in the comments. I'll let you read the link above to learn about it, but suffice it to say that DLL Forwarding is when you call an exported function in one DLL but that request is actually forwarded to another function in a different DLL.

Load-Time Dynamic Linking

This is what I would consider to be Vanilla DLL Linking.

This is what most people are referring to when they refer to using DLLs in their applications. You just #include the DLL's header file and link to the LIB file. No need to GetProcAddress() or create function pointer typedefs. Here's how it works in a nutshell:

1) You typically get 3 files: a DLL with the runtime code, a LIB file and a header file. The header file is just a header file -- it describes all the facilities in the DLL you can use.

2) You write your application, #include'ing the header file from the DLL and making calls to those functions just like you would use any function in any header file. The compiler knows the names of functions and objects you use because they are in the DLL's header file. But it doesn't know where they are in memory yet. That is where the LIB file comes in...

3) You go to the linker settings for your project and add an "additional library dependency," specifying the LIB file. The LIB file tells the linker where the functions and objects you use from the H file reside in memory (in relative terms, not absolute terms, obviously).

4) Compile your app. If you have set everything up correctly it should compile, link and run. When you get "unresolved external reference" linker errors commonly this is due to things not being set up right. You may either have not specified the correct path to the LIB file or you need to include more LIB files.



回答3:

Import libraries (.lib) simplify DLL usage in user code, see e.g. here for a basic tutorial.
They spare the users from loading the DLL, using GetProcAddress() and function pointers themselves - they statically link to the import library instead which does the work for them.



回答4:

Why don't you get VS to generate a shim static library around your DLL. That way all you have to do is add a calling convention in the header file and add a couple of pre-procesor directives. The easiest way to figure out how to do it is to create a new DLL project (Visual C++>Win32 Project, Choose DLL Project, check Import symbols)

alt text http://img341.imageshack.us/img341/7048/dll.png

Use the main header file as an example on how to decorate your classes with the import/export calling convention. This head is the important bit as it explains how to use the functions and classes declared there:

// The following ifdef block is the standard way of creating macros which make exporting 
// from a DLL simpler. All files within this DLL are compiled with the DLLTEST2_EXPORTS
// symbol defined on the command line. this symbol should not be defined on any project
// that uses this DLL. This way any other project whose source files include this file see 
// DLLTEST2_API functions as being imported from a DLL, whereas this DLL sees symbols
// defined with this macro as being exported.

#ifdef DLLTEST2_EXPORTS
#define DLLTEST2_API __declspec(dllexport)
#else
#define DLLTEST2_API __declspec(dllimport)
#endif

// This class is exported from the dlltest2.dll
class DLLTEST2_API Cdlltest2 {
public:
    Cdlltest2(void);
    // TODO: add your methods here.
};

extern DLLTEST2_API int ndlltest2;

DLLTEST2_API int fndlltest2(void);

Then, in the project that uses that DLL simply include the header file and .lib that the DLL project generated. That way it automatically loads the DLL and you can use all the functions as though statically linked.



回答5:

When you build your dll, you should also get a lib file that you can link with. That will do the heavy lifting in the background for you. Include the header file that you've created, but change into dllimport instead of dllexport. You can use a define for that, so that for your dll project it uses dllexport and all others that do not use this define will use dllimport.

You only need to do a manual LoadLibrary and the typedef if you want to dynamically load the dll yourself. If you do the above approach your exe will fail if the dll is not present.

You can also build the dll project into a static library and load that instead, which will also get rid of that problem but will increase the size of your exe. This is of course not an option if you really want it to be a dll.



回答6:

You can link to the DLL's symbols directly instead of using GetProcAddress(), which gets the address of a function at runtime.

Example header file snippet:

#if defined(MY_LIB_STATIC)
#define MY_LIB_EXPORT
#elif defined(MY_LIB_EXPORTS)
#define MY_LIB_EXPORT __declspec(dllexport)
#else
#define MY_LIB_EXPORT __declspec(dllimport)
#endif

#ifdef __cplusplus
extern "C"
{
#endif

int MY_LIB_EXPORT Factorial(int n);

#ifdef __cplusplus
}
#endif

Then in BHannan_Test_Class.cpp, you would #define MY_LIB_EXPORTS before including the header.

In dll_test.cpp you would include the header and just use Factorial() as you would use a normal function. When linking, you want to link to the import library that building your DLL produced. This makes the symbols from the DLL available to the code that links to it.



回答7:

Of course you don't need the typedef

int (* myFactorial)(int) = 0;

HMODULE myDLL = LoadLibrary("BHannan_Sample_DLL.dll");

if(myDLL) {
    myFactorial = reinterpret_cast<int (*) (int)>( GetProcAddress(myDLL,"Factorial"));
    ...
}

or, exploiting C++ initialise and test idiom

if ( HMODULE myDLL = LoadLibrary("BHannan_Sample_DLL.dll") ) {
    if ( int (* myFactorial)(int) = GetProcAddress ( myFactorial, myDLL, "Factorial" ) ) {
        ...
    }
}

given this to tidy up the repetition of the type

template <typename T>
T GetProcAddress ( const T&, HMODULE mod, const char* name) 
{
    return reinterpret_cast<T> (GetProcAddress(mod,name)); 
}

But not using a typedef is generally worse rather than better, as C's function pointer types are a bit tricky to get right unless you're using them regularly. (if you are using them regularly, then your software may be somewhat unorthodox).

The Microsoft dllimport extensions and compiler create a static library which does the loading for you and provides trampolines or thunks, as others have posted. Unless you're creating a plug-in system which doesn't know which dll it will load, then use that instead.