views:

395

answers:

7

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

}
+6  A: 

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.

Dmitry
Great! Does it still load the library dynamically like DLL's are designed to do? It kind of seems weird that you have to include a static library (.lib) and a dynamic one (.dll) in order to do this?
Brian T Hannan
Yes, it uses your dll, that lib only contains info needed by linker, it is just an _exports library_, not really a 'static lib'.
Dmitry
Brian, yes, the library is still loaded dynamically. The difference is that it is loaded as soon as your application starts, rather than waiting for a call to LoadLibrary.
Nick Meyer
The export .lib is necessary to tell the linker the name of the DLL that needs to be loaded. It is very small.
Hans Passant
Awesome answer, that worked like a charm and what exactly what I was looking for!
Brian T Hannan
Is this common practice or is it just a hacky kind of thing? Let's say I'm creating an SDK and will be distributing this functionality to end-users who are programmers. Will they be able to access the underlying code from the .lib file? Would we have to ship all three files (.lib, .dll, and .h)?
Brian T Hannan
Yes it is a common practice, not a hack. Yes you need to deliver all three files. And, you might be surprised, but that's how MS Platform SDK and most other work.
Dmitry
A: 

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.

villintehaspam
+2  A: 

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.

Georg Fritzsche
A: 

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.

asveikau
You can't link 'directly' to a DLLs symbols - its dynamically loaded.
Georg Fritzsche
I am aware of how dynamic linking works. Still, I consider it more "direct" to link in a way that will be resolved at load time by the dynamic linker, than to resolve it after main() has started. That contrast is what I meant to call attention to by saying "direct".
asveikau
+5  A: 

In the Windows world, there are (at least) 3 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

I don't have to explain #1 since you're already doing it. I choose not to explain Delay Load now beyond just descibing 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.

Load-Time Dynamic 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 dependancy," 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.

John Dibling
@John, good answer, but your terminology appears to be backwards according to the link you've included. MSDN defines 'run-time dynamic linking' to be what the OP is doing and 'load-time dynamic linking' to be what you're describing.
Nick Meyer
Quite right, fixed now. Thx.
John Dibling
A: 

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

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.

Igor Zevaka
A GUI tool is no substitute for knowing what the problem is. I would encourage reading and understanding the header files that this wizard produces for you.
asveikau
A: 

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.

Pete Kirkham
You didn't get the point. The whole point was to not have to declare (again) the function definition that is already in the header file. Whether it is with a typedef or not it is ugly to have to re-declare how the function signature looks.
Brian T Hannan
I did get the point, but the question failed to specify whether it was dynamically selecting the DLL or it was known at compile time. For the former, you cannot use dllimport.
Pete Kirkham