7

LS,

I'm trying to build a static and dynamic library that can be used to link with both dynamically and static. I would like the library to run on as many platforms as possible no matter the compiler used. In order to build the library and a few test programs I'm using CMake to build the library eeg on Linux and on windows using g++ and MSVC++ respectively.

On Linux both the dynamic and static libraries seem to work as I suspected, on windows the .dll seems to link perfectly and my test programs run. However, a program using the static library complains about linking errors. I really miss the point what I'm doing wrong, It could be something in my CMakeLists.txt, but also in the Setup of my library. Below I've made a minimal program that uses my library that demonstrates the issues I'm experiencing. The library consists of two C++ files, an C API that exports the code in the C++ files, a C++ program that uses the C API and finally a CMakeList.txt that can build all programs but the program that uses the static library. This all to yields a wonderfull "Hello, World!".

I know I show a lot of code, but at least it is a minimal project that demonstrates my problems with linking to the static library. I hope someone is kind enough to take a look at this project and explain me what I'm doing wrong.

Kind regards,

hetepeperfan

The C++ file PriCpp.cpp

#include "PriCpp.h"

using namespace std;
string PriMessageBuilder::message() const {
    return "Hello, World!";
}

The header PriCpp.h

#ifndef PRICPP_H
#define PRICPP_H

#include <string>

class PriMessageBuilder{
public:
    std::string message() const;
};

#endif

The C API is: mycapi.h

#ifndef MYCAPI_H
#define MYCAPI_H
#include "builder_export.h"

#ifdef __cplusplus
extern "C" {
#endif

typedef struct {} message_builder;

BUILDER_EXPORT message_builder* message_builder_new();
BUILDER_EXPORT void             message_builder_destroy(
                                        message_builder* builder
                                        );
BUILDER_EXPORT char*            message_builder_message(
                                        message_builder* builder
                                        );
#ifdef __cplusplus
}
#endif

#endif

mycapi.cpp is:

#include "mycapi.h"
#include "PriCpp.h"
#include <cstring>
#include <cstdlib>

message_builder* message_builder_new() {
    PriMessageBuilder* ret = NULL;
    try {
        ret = new PriMessageBuilder();
    } catch (...) {
    }
    return reinterpret_cast<message_builder*>(ret);
}

void message_builder_destroy(message_builder* builder)
{
    PriMessageBuilder* b = reinterpret_cast<PriMessageBuilder*>(builder);
    delete b;
}

char* message_builder_message(message_builder* builder)
{
    PriMessageBuilder* b = reinterpret_cast<PriMessageBuilder*>(builder);
    return strdup(b->message().c_str());
}

The program that I use in combination with the library above. program.cpp

#include <iostream>
#include <cstdlib>
#include <string>
#include "mycapi.h"

using namespace std;

class MessageBuilder {
public:
    MessageBuilder() : m_builder(message_builder_new()) {
    }
    ~MessageBuilder() {
        message_builder_destroy(m_builder);
    }
    string MessageBuilder::message() {
        char* msg = message_builder_message(m_builder);
        string m(msg);
        free (msg);
        return m;
    }
private:
    message_builder* m_builder;
};

int main(int, char**) {
    MessageBuilder m;
    cout << m.message() << endl;
    return 0;
}

and finally CMakeLists.txt to build a makefile or a visual studio solution

#setting up project
cmake_minimum_required(VERSION 2.6)
project(libbuilder CXX) 
include (GenerateExportHeader)

include_directories("${PROJECT_BINARY_DIR}")

#creating library
add_library(builder SHARED PriCpp.cpp PriCpp.h mycapi.cpp mycapi.h)
add_library(builder-static STATIC PriCpp.cpp PriCpp.h mycapi.cpp mycapi.h)

generate_export_header(builder)

set_target_properties(builder PROPERTIES COMPILE_FLAGS -DBUILD_BUILDER_SHARED)
set_target_properties(builder-static PROPERTIES COMPILE_FLAGS -DBUILDER_STATIC_DEFINE)

#creating programs
add_executable(program program.cpp)
add_executable(program-static program.cpp)

target_link_libraries(program builder)
target_link_libraries(program-static builder-static)

If I build the solution I get these linker errors:

1>program.obj : error LNK2019: unresolved external symbol __imp__message_builder_new referenced in function "public: __thiscall MessageBuilder::MessageBuilder(void)" (??0MessageBuilder@@QAE@XZ)
1>program.obj : error LNK2019: unresolved external symbol __imp__message_builder_destroy referenced in function "public: __thiscall MessageBuilder::~MessageBuilder(void)" (??1MessageBuilder@@QAE@XZ)
1>program.obj : error LNK2019: unresolved external symbol __imp__message_builder_message referenced in function "public: class std::basic_string<char,struct std::char_traits<char>,class std::allocator<char> > __thiscall MessageBuilder::message(void)" (?message@MessageBuilder@@QAE?AV?$basic_string@DU?$char_traits@D@std@@V?$allocator@D@2@@std@@XZ)
1>K:\duijn119\programming\cmake\staticsharedlib\build\Debug\program-static.exe : fatal error LNK1120: 3 unresolved externals

The CMake generated include header is:

#ifndef BUILDER_EXPORT_H
#define BUILDER_EXPORT_H

#ifdef BUILDER_STATIC_DEFINE
#  define BUILDER_EXPORT
#  define BUILDER_NO_EXPORT
#else
#  ifndef BUILDER_EXPORT
#    ifdef builder_EXPORTS
        /* We are building this library */
#      define BUILDER_EXPORT __declspec(dllexport)
#    else
        /* We are using this library */
#      define BUILDER_EXPORT __declspec(dllimport)
#    endif
#  endif

#  ifndef BUILDER_NO_EXPORT
#    define BUILDER_NO_EXPORT 
#  endif
#endif

#ifndef BUILDER_DEPRECATED
#  define BUILDER_DEPRECATED __declspec(deprecated)
#endif

#ifndef BUILDER_DEPRECATED_EXPORT
#  define BUILDER_DEPRECATED_EXPORT BUILDER_EXPORT BUILDER_DEPRECATED
#endif

#ifndef BUILDER_DEPRECATED_NO_EXPORT
#  define BUILDER_DEPRECATED_NO_EXPORT BUILDER_NO_EXPORT BUILDER_DEPRECATED
#endif

#define DEFINE_NO_DEPRECATED 0
#if DEFINE_NO_DEPRECATED
# define BUILDER_NO_DEPRECATED
#endif

#endif
hetepeperfan
  • 4,292
  • 1
  • 29
  • 47
  • 1
    Not really sure, but do you really need `extern "C"` in the header since you have C++ implementation? – dmi Feb 19 '16 at 09:28
  • @dmi Yes I'm quite sure I need extern "C". I want to use the c calling conventions, so that there is no namemangling. This makes it easier to call functions from the library from a C program. The C API also hides the classes from the standard C++ library across the dll boundary. That maintains ABI stability between different C/C++ compilers. – hetepeperfan Feb 19 '16 at 09:34
  • Does problem remain if you remove all non-static libraries and executables? If so, please, post `CMakeLists.txt` without them. – Tsyvarev Feb 19 '16 at 09:52
  • Show the `BUILDER_EXPORT` macro – ixSci Feb 19 '16 at 10:13
  • @ixSci The BUILDER_EXPORT is generated by CMake in `#include "builder_export.h"` but I'll include the header. – hetepeperfan Feb 19 '16 at 10:15
  • What if you change `set_target_properties(builder-static PROPERTIES COMPILE_FLAGS -DBUILDER_STATIC_DEFINE)` to `set_target_properties(builder-static PROPERTIES COMPILE_DEFINITIONS -DBUILDER_STATIC_DEFINE)`? – ixSci Feb 19 '16 at 10:19

1 Answers1

5

You need to add set_target_properties(program-static PROPERTIES COMPILE_FLAGS -DBUILDER_STATIC_DEFINE) after add_executable(program-static program.cpp). Currently your static library gets build right but the application gets the library header with wrong declspec(well, there should be no declspec at all and you have one)

Also, it seems more appropriate to use the following notion:

set_target_properties(program-static PROPERTIES COMPILE_DEFINITIONS BUILDER_STATIC_DEFINE)

COMPILE_DEFINITIONS instead of COMPILE_FLAGS

ixSci
  • 13,100
  • 5
  • 45
  • 79
  • Excellent awnser. Guess I'm still a CMake Noob. The define -DBUILDER_STATIC_DEFINE definately is making it to the documentation of my library when linking to the static library. I don't think I would have figured this one out myself, although it is so logical when someone points to it. – hetepeperfan Feb 19 '16 at 10:50