Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Troubles linking to a static library on windows with CMake

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
like image 637
hetepeperfan Avatar asked Feb 19 '16 08:02

hetepeperfan


People also ask

How are static libraries linked?

Static libraries are either merged with other static libraries and object files during building/linking to form a single executable or loaded at run-time into the address space of their corresponding executable at a static memory offset determined at compile-time/link-time.

What does Cmake Target_link_libraries do?

Specify libraries or flags to use when linking a given target and/or its dependents. Usage requirements from linked library targets will be propagated. Usage requirements of a target's dependencies affect compilation of its own sources.

Is it better to use CMake for libraries or applications?

The bar for clean CMake code is significantly higher for a library than for an application because the CMake code itself affects end users. For an application, some ugliness is tolerable because it doesn't propagate through the dependency tree (you don't typically link to executables).

What is the cmakelist library?

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!".

Do the static and dynamic libraries work on Linux?

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.

Is it possible to mix shared and static versions of libraries?

A single project will not mix both shared and static versions of a library. Certainly for a single target, it is totally illegal to link to both at the same time. This means we don't need to support mixing both types in a single directory.


Video Answer


1 Answers

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

like image 90
ixSci Avatar answered Oct 13 '22 01:10

ixSci