How do I link a C++/CLI library to C++ application - c++

I made a C++/CLI wrapper for using a C# DLL in a C++ application. I used Visual Studio to manage the projects and manage the dependencies, and I got it to work that way.
But I can't get it to work when I use CMake to link the library that Visual Studio built for me.
cmake_minimum_required (VERSION 3.12)
project(playground CXX)
SET(CMAKE_CXX_STANDARD 17)
set(PLAYGROUND_SOURCE_DIR ${CCMAKE_CURRENT_SOURCE_DIR})
set(PLAYGROUND_BINARY_DIR ${CMAKE_CURRENT_BINARY_DIR})
set(PLAYGROUND_SRC
main.cpp)
add_executable(Playground ${PLAYGROUND_SRC} ${PLAYGROUND_HDR})
find_library(comm_dll CommWrapper Lib/CommDLL)
target_link_libraries(Playground PUBLIC
${comm_dll}
)
Using this method gets me an "unresolved external symbol error". I have the .dll, .lib and .h file in the same folder.

As Hans Passant already stated: you must compile your C++/CLI code as Dynamic Library in order to be able to consume it from an unmanaged application. CLI/Managed code cannot run from/cannot reside in static libraries. If you change the C++/CLI library target from Static library to Dynamic library you'll be able to compile successfully your unmanaged C++ application.
One thought from my side: I think you'll be better if you use mixed mode C++/CLI DLLs to consume the managed functionality - you'll be able to free your consumer application completely from referencing the CLR.
The Header of such mixed mode Wrapper for your Element class would look like this:
#pragma once
#pragma unmanaged
#if defined(LIB_EXPORT)
#define DECLSPEC_CLASS __declspec(dllexport)
#else
#define DECLSPEC_CLASS __declspec(dllimport)
#endif
class ElementWrapperPrivate;
class __declspec(dllexport) ElementWrapper
{
private:
ElementWrapperPrivate* helper;
public:
ElementWrapper();
~ElementWrapper();
public:
void ExecuteCommand();
};
And the implementation would look like this:
#include "ElementWrapper.h"
#pragma managed
#include "Element.h"
#include <msclr\auto_gcroot.h>
using namespace System::Runtime::InteropServices;
class ElementWrapperPrivate
{
public:
msclr::auto_gcroot<Element^> elementInst; // For Managed-to-Unmanaged marshalling
};
ElementWrapper::ElementWrapper()
{
helper = new ElementWrapperPrivate();
helper->elementInst = gcnew Element();
}
ElementWrapper::~ElementWrapper()
{
delete helper;
}
void ElementWrapper::ExecuteCommand()
{
helper->elementInst->ExecuteCommand();
}
Then just compile your Element.cpp + ElementWrapper.cpp to a DLL and use the ElementWrapper.h in your unmanaged applications.

Related

Static library able to link with any other object, regardless of C++ runtime used by that object

I am trying to determine if it is possible at all to create a static library that:
Internally uses Microsoft/STL, static release runtime (/MT)
Can be linked to objects not using /MT (e.g., Dynamic DLL Release, Static Debug, etc.)
The specific test code I'm trying to get working is as follows:
Header:
#pragma once
#ifdef __cplusplus
extern "C" {
#endif
int EncapLibConcatenate(int a, int b);
#ifdef __cplusplus
}
#endif
Source:
#include "EncapLib.h"
#include <string>
int EncapLibConcatenate(int a, int b)
{
try {
return std::stoi(std::to_string(a) + std::to_string(b));
} catch (...) {
return 0;
}
}
Linking to the above predictably results in a /FAILIFMISMATCH error when not using /MT for the project that links to the above library. I would like to learn of any compiler / linker options to circumvent this issue. If such do not exist, then any other approaches (such as modifying the .obj COFF files post-build) would also be welcome.
As a side note, I am aware that creating a dynamic library is the idiomatic approach to encapsulating library dependencies; this is a question regarding technical possibilities, not best practices. I have also been able to create a runtime-agnostic static library by eschewing any use of the standard library (restricting to C runtime and win32). This question is specifically about a static library that uses the C++ standard library internally (though pointedly not at its interface).

spdlog crash on factory methods

Yesterday I have started including spdlog into a personal project of mine to use for logging. So far I have had some problems with getting library inclusion to work but those are now solved completely.
Now everything compiles just fine, with all headers found however when I try to create loggers or simply set the pattern for logging the code crashes with a segmentation fault. More specifically no matter which function I call from the spdlog namespace for the very first time in the program causes the crash.
I have a class abstracting some parts from spdlog (based on this repo) as follows:
//Logger.hpp
#ifndef TE_LOGGER_HPP
#define TE_LOGGER_HPP
#include <spdlog/spdlog.h>
namespace te {
class Logger {
public:
static void Init();
inline static std::shared_ptr<spdlog::logger> &getCoreLogger() {
return sCoreLogger;
}
inline static std::shared_ptr<spdlog::logger> &getClientLogger() {
return sClientLogger;
}
private:
static std::shared_ptr<spdlog::logger> sCoreLogger;
static std::shared_ptr<spdlog::logger> sClientLogger;
};
}
#endif //TE_LOGGER_HPP
//Logger.cpp
#include "Logger.hpp"
#include <spdlog/sinks/stdout_color_sinks.h>
std::shared_ptr<spdlog::logger> te::Logger::sCoreLogger;
std::shared_ptr<spdlog::logger> te::Logger::sClientLogger;
void te::Logger::Init() {
//The first of any of the following three lines cause a crash
//no matter the order, regardless of the pattern used in set_pattern
spdlog::set_pattern("%v");
sCoreLogger = spdlog::stdout_color_mt("CORE");
sClientLogger = spdlog::stdout_color_mt("CORE");
sCoreLogger->set_level(spdlog::level::trace);
sClientLogger->set_level(spdlog::level::trace);
}
From the stack traces it seems that the issue is with the formatter class in spdlog being set null for some reason somewhere within the library. I am using the latest CLion, C++14 (I am aware that spdlog is C++11, but I need features from 14 later down the line, also setting -std=c++11 doesn't solve the issue) and the latest version of spdlog as of yesterday (pulled straight from their GitHub repo) on Ubuntu 18.04.
EDIT: As per the request in the comments I have created a small project (single cpp file, include spdlog the way I do in the real project, or the same code and library setup as in the real project referenced from the main.cpp file and linked to accordingly) that aims to reproduce the issue and here are my findings:
* The issue is not present when I use spdlog directly in the executable
* The issue is present if the Logger class is moved into a shared library and linked to from there
Here is the error message I am getting:
Process finished with exit code 139 (interrupted by signal 11: SIGSEGV)
And the CMakeLists.txt files I am using (I nest the library's one into the project since as of now CLion does not support "multiple projects in the same solution" like for example VS does):
#CMakeLists.txt for Library
cmake_minimum_required(VERSION 3.10 FATAL_ERROR)
project(TokenEngine VERSION 0.0.1 LANGUAGES CXX)
set(CMAKE_CXX_STANDARD 14)
set(SOURCE_FILES src/Application.cpp src/Application.hpp src/EntryPoint.hpp src/Logger.cpp src/Logger.hpp)
#include_directories("${CMAKE_CURRENT_SOURCE_DIR}/libs/")
add_library(TokenEngine SHARED ${SOURCE_FILES})
target_include_directories(TokenEngine PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/libs/spdlog-1.x/include")
#Expose the public API of the engine to any project that might use it
target_include_directories(TokenEngine PUBLIC include)
#CMakeLists.txt for top level project
cmake_minimum_required(VERSION 3.10 FATAL_ERROR)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra")
add_definitions(-DTE_PLATFORM_LINUX)
project(Build CXX)
add_subdirectory(TokenEngine)
add_subdirectory(Sandbox)
You are using the same name for both loggers, when you run it you'll get:
$ ./logger
libc++abi.dylib: terminating with uncaught exception of type spdlog::spdlog_ex: logger with name 'CORE' already exists
Abort trap: 6
If you change the name of the client logger to something else it works fine:
sCoreLogger = spdlog::stdout_color_mt("CORE");
sClientLogger = spdlog::stdout_color_mt("CLIENT");
The problem is probably that spdlog’s static objects are defined twice - from inside the shared library and from client code that includes your logger header (which incldes spdlog.h).
Try to remove the include to spdlog.h from the header file and (and use forward declaration of spdlog::logger instead), and include spdlog.h only from your Logger.cpp file.
Edit:
spdlog::logger cannot be forward declared across compilation unit boundries.
The solution is to wrap the logger with some simple class defined in logger.cpp and only export it in logger.h

How can I check #ifdef in a DLL project, when the #define is in the executable project using the dll?

I have a C++ Visual Studio 2015 project consisting of
1 Windows API executable, 1 windows console executable, and 1 dll shared by both executables.
I have a #define USEWINDOWS in the Windows API main.cpp file
I don't have this defined in the console app
In the DLL, I would like to do an #ifdef USEWINDOWS statement, but the scope of the #define seems to be only valid to the Win32 executable, not in the dll.
How can I extend this #define to the DLL without having it affect the undefined USEWINDOWS in the console app?
Thanks
The DLL is shared by both executables, hence there is only one implementation available and therefore you can't use implementation specific compilation flags.
You'll need a runtime flag, i.e. some API parameter which tells the DLL code that it is OK to use "windows stuff". E.g.
api.h
void someAPI(...., bool useWindows);
dll.c
#include "api.h"
void someAPI(...., bool useWindows) {
...
if (useWindows) {
// do "windows stuff" here
}
...
}
app.c
#include "api.h"
...
someAPI(...., true);
console.c
#include "api.h"
...
someAPI(...., false);
(not tested by compilation, but you should get the drift...)

Link a MSVC compiled DLL in a MinGW-built project

Given two C++ projects:
a Win32 C++ project compiled as a x86 DLL under Visual Studio 2015
a Qt GUI application using Desktop Qt 5.5.1 MinGW 32 bit Kit.
What I'm trying to do is to link the first one in the second one. A MWE follows.
DLL header file: libxspectra.h
namespace XSpectra
{
#define LIBXSPECTRA_EXPORTS // already defined into Project Properties
#ifdef LIBXSPECTRA_EXPORTS
#define LIBXSPECTRA_API __declspec(dllexport)
#else
#define LIBXSPECTRA_API __declspec(dllimport)
#endif
LIBXSPECTRA_API int fnlibxspectra(void);
LIBXSPECTRA_API int gnara(void) { return 7; };
int foo() { return 1; };
int bar();
}
DLL source file: libxspectra.cpp
#include "libxspectra.h"
namespace XSpectra
{
LIBXSPECTRA_API int fnlibxspectra(void)
{
return 42;
}
int bar()
{
return 6;
}
}
Qt source file: main.cpp
#include "libxspectra.h"
int main(int argc, char *argv[])
{
XSpectra::foo();
XSpectra::bar();
XSpectra::gnara();
XSpectra::fnlibxspectra();
return 0;
}
Qt application build log
error: undefined reference to XSpectra::bar()
error: undefined reference to _imp___ZN8XSpectra13fnlibxspectraEv
While foo() and gnara() links correctly.
A few notes
I know the problem is not strictly Qt-related, but it's a matter of different compilation toolchains, a field where I'm a real novice. I'm actually asking for advices on this way.
If I comment the #define LIBXSPECTRA_EXPORTS, Visual Studio's Intellisense still marks it as defined, dll compiles, but the behaviour of external application's build process changes. The following error arises:
error: function 'int XSpectra::gnara()' definition is marked dllimport
You can only link MSVC compiled C DLLs with MinGW, and only on 32-bit Windows. The MinGW linker can link directly to the DLL (if the functions are properly exported and not only available through an import library) or the usual import library. See here and here for how to generate a MinGW import library from a DLL.
You'll do it just like with MSVC (compile the dll with the functions marked dllexport, and compile the code using the dll with the functions marked dllimport, or use a .def file or something). Remember you need to export C functions, which means they need to be marked extern "C".
I would strongly suggest though, making the code compatible with MinGW, and just compile everything with that. Or use the MSVC version of Qt.

Creating shared and static libraries using g++ (under Windows)

How do I create static and dynamic libraries for Windows using g++?
I've found a few commands for Linux for creating .so files and I've tried to apply them on a Windows shell, but they build .dll files that my applications fail to link with at runtime.
I've only managed to build .dll files using Visual C++ but I would like to build them manually on the command line, preferably using g++. I would also like to know how to build static libraries too for Windows.
You need to prefix with the attribute :
__declspec(dllexport)...
all the features you want to expose.
See this.
Example for a C function:
__declspec(dllexport) int __cdecl Add(int a, int b)
{
return (a + b);
}
This can be simplified using MACROS: everything is explained on this helpful page.
For C++ classes, you only need to prefix each class (not every single method)
I usually do it that way :
Note : The following also ensures portability...
Include File :
// my_macros.h
//
// Stuffs required under Windoz to export classes properly
// from the shared library...
// USAGE :
// - Add "-DBUILD_LIB" to the compiler options
//
#ifdef __WIN32__
#ifdef BUILD_LIB
#define LIB_CLASS __declspec(dllexport)
#else
#define LIB_CLASS __declspec(dllimport)
#endif
#else
#define LIB_CLASS // Linux & other Unices : leave it blank !
#endif
Usage :
#include "my_macros.h"
class LIB_CLASS MyClass {
}
Then, to build, simply :
Pass the option -DBUILD_LIB to the usual compiler command line
Pass the option -shared to the usual linker command line