Monday, August 8, 2016

Use CMake to help build and use a Windows dll

I wanted to use Windows c++ classes and use them in another c++ applications through shared Windows DLLs, while using CMake to generate build files and Visual Studio to compile into final executables. I spent some time figuring out how to do it and the following steps illustrate a simple example.

Create a Windows DLL
The first step is to create a Windows DLL and export the functions that will be called by other methods.

Listing of Animal.h
1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
#include <string>

using namespace std;

class Animal {
private:
    string name;
public:
    Animal(string);
    virtual void print_name();
};

Listing of Animal.cpp
1
 2
 3
 4
 5
 6
 7
 8
 9
10
#include <iostream>
#include "Animal.h"

using namespace std;

Animal::Animal(string name):name (name){}

void Animal::print_name(){
    cout << "Name is " << this->name << endl;
}

The Animal.h and Animal.cpp files are placed in the include and src folders under the [example root folder]\animallib_shared\ folder, as shown below.


The next thing to do is to create the CMakeLists.txt in the Windows DLL project e.g. [example root folder]\animallib_shared\ folder.
1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
cmake_minimum_required(VERSION 2.8.6)
project (animallib)
set (CMAKE_BUILD_TYPE Debug)

#include *.h files under include folder and  
#the project's output folder e.g. Debug
include_directories (include ${PROJECT_BINARY_DIR})

#compile all *.cpp source files under src folder
file (GLOB SOURCES "src/*.cpp")

#output library as animallib.*

#output library export file *.lib and
#output macro definitions include file
include (GenerateExportHeader)
add_library(animallib SHARED ${SOURCES})
GENERATE_EXPORT_HEADER (animallib
    BASE_NAME animallib
    EXPORT_MACRO_NAME animallib_EXPORT
    EXPORT_FILE_NAME animallib_Export.h
    STATIC_DEFINE animallib_BUILT_AS_STATIC
)

Next, we have to modify the header (*.h) and source code (*.cpp) files to add in the Windows export macro animallib_EXPORT and cmake generated header file animallib_Export.h.
Listing of modified animal.h
1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
#include <string>

#include "animallib_Export.h"

using namespace std;

class Animal {
private:
    string name;
public:
    animallib_EXPORT Animal(string);
    virtual animallib_EXPORT void print_name();
};
Listing of modified Animal.cpp

1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
#include <iostream>
#include "Animal.h"

#include "animallib_Export.h"

using namespace std;

animallib_EXPORT Animal::Animal(string name):name (name){}

animallib_EXPORT void Animal::print_name(){
    cout << "Name is " << this->name << endl;
}

Open up a Windows Command prompt and change directory to the build folder of the Windows DLL project e.g. [example root folder]\animallib_shared\build\ folder.

Type in the cmake command (assuming cmake is in the PATH environment variable):
C:> cd \path\to\example\animallib_shared\build
C:> cmake ..
Build files are generated
The generated build files
Open up the generated Visual Studio solution file [example root folder]\animallib_shared\build\animallib.sln in Visual Studio. Then select Build | Build Solution to compile the Windows DLL.
Compilation messages
The generated *.dll and export library *.lib files under [example root folder]\animallib_shared\build\Debug\
Using the Windows DLL in a separate C++ application
Now that the Windows DLL has been generated and the functions exported, the next thing is to use the DLL's classes and functions in an application e.g. UseAnimalLib project under [example root folder]\useanimallib\]

Listing of uselib.cpp

1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
#include "Animal.h"

#include "animallib_Export.h"

int main(int argc, char *argv[]){
    //Create a new animal instance with name Dog
    Animal animal("Dog");

    //try to call the class's print_name method
    animal.print_name();
    return (0);
}

Then create a CMakeLists.txt file under the [example root folder]\useanimallib\ folder.


1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
cmake_minimum_required(VERSION 2.8.6)
project (UseAnimalLib)

set (EXAMPLE_DIR d:/path/to/example/root/folder)

set (PROJECT_LINK_LIBS animallib.dll)
link_directories (${EXAMPLE_DIR}/animallib_shared/build/Debug)

include_directories (${EXAMPLE_DIR}/animallib_shared/include ${EXAMPLE_DIR}/animallib_shared/build)

#compile all *.cpp source files under src folder
file (GLOB SOURCES "src/*.cpp")

add_executable(uselib ${SOURCES})
target_link_libraries (uselib ${PROJECT_LINK_LIBS})

Open up a Windows Command Prompt. Change directory to the [example root folder]\useanimallib\build folder.
C:> cd \path\to\example\useanimallib\build
C:> cmake ..

The build files are generated



The generated build files


Open up the generated solution file e.g. [example root folder]\useanimallib\build\Debug\uselib.sln using Visual Studio and build the solution.
Building the solution
The executable that uses the Windows DLL can now be run in a Command Prompt, assuming the DLL is in the PATH environment variable, as shown below.

2 comments:

Moisés said...

Thanks for the clear, concise and complete tutorial. Hard to come accross high quality resources like this :)

scott said...

Great post! Only issue I had was that everything worked perfectly until the very end where uselib.exe won't print out "Name is Dog". I don't get any errors in the process, but the very last step seems off for me. Your instructions say to build \useanimallib\build\Debug\uselib.sln, but the only sln file I have after using cmake is still in the build directory (not Debug) and it's UseAnimalLib.sln. So I build that to end up with uselib.exe. Interestingly, adding a custom print message will work, just not that from the print_name method of the class packaged into the dll. Any idea what's going on?