Trying to add SDL2_mixer and SDL2 as ExternalProject's in CMake - c

I am currently trying to fetch SDL2 and SDL2_mixer as external projects in my CMake project.
SDL2 seems to work fine, but I cannot make SDL2_mixer compile. It fails when trying to link the playwav binary. The problem are the CFLAGS and LDFLAGS variables in the ExternalProject_Add. The same problem occurs when adding these variables while building from the command line without CMake.
Here is my code so far:
cmake_minimum_required(VERSION 2.8)
include(ExternalProject)
project(sdl2_test)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11 -Wall")
set(LIBS_DIR ${PROJECT_BINARY_DIR}/libs)
set(SDL2_VER "2.0.8")
set(SDL2_MIXER_VER "2.0.2")
# SDL library
ExternalProject_Add(sdl2_project
URL http://www.libsdl.org/release/SDL2-${SDL2_VER}.tar.gz
PREFIX ${LIBS_DIR}/SDL2
INSTALL_COMMAND ""
)
ExternalProject_Get_Property(sdl2_project SOURCE_DIR)
ExternalProject_Get_Property(sdl2_project BINARY_DIR)
set(SDL2_SRC ${SOURCE_DIR})
set(SDL2_BIN ${BINARY_DIR})
file(GLOB SDL2_INCLUDE "${SDL2_SRC}/include/*")
file(COPY ${SDL2_INCLUDE} DESTINATION ${SDL2_BIN}/include/)
# SDL_mixer library
ExternalProject_Add(sdl2_mixer_project
URL https://www.libsdl.org/projects/SDL_mixer/release/SDL2_mixer-${SDL2_MIXER_VER}.tar.gz
DEPENDS sdl2_project
PREFIX ${LIBS_DIR}/SDL2_mixer
CONFIGURE_COMMAND
SDL2_CONFIG=${SDL2_BIN}/sdl2-config
CFLAGS=-I${SDL2_BIN}/include
LDFLAGS=-L${SDL2_BIN}
#LIBS=-ldl
<SOURCE_DIR>/configure
--prefix=<INSTALL_DIR>
--enable-shared=no
#--with-sdl-prefix=${SDL2_BIN}
--disable-sdltest
BUILD_COMMAND make
INSTALL_COMMAND ""
)
#file(GLOB SDL2_INCLUDE "${SDL2_SRC}/include/*")
#file(COPY ${SDL2_INCLUDE} DESTINATION ${SDL2_BIN}/include/)
ExternalProject_Get_Property(sdl2_mixer_project SOURCE_DIR)
ExternalProject_Get_Property(sdl2_mixer_project BINARY_DIR)
set(SDL2_MIXER_SRC ${SOURCE_DIR})
set(SDL2_MIXER_BIN ${BINARY_DIR})
include_directories(${SDL2_SRC}/include)
include_directories(${SDL2_MIXER_SRC}/include)
set(SOURCE sdl2test.cc)
add_executable(sdl_test ${SOURCE})
add_dependencies(sdl_test sdl2_project sdl2_mixer_project)
target_link_libraries(sdl_test ${SDL2_BIN}/libSDL2.a)
The file sdl2test.cc is just a dummy file:
#include <iostream>
int main()
{
std::cout << "Hooray" << std::endl;
return 0;
}

Related

Calling C from Rust with external headers in C file

I am trying to call C code from Rust, I succeeded! The only issue I get is when I try to take other libraries with me... the documentation says that you should include all headers in the one file you try to load in Rust, so I do this in the .c file...
#include <stdio.h>
#include <stdbool.h>
#include "dependencies/glfw-3.3.4/include/GLFW/glfw3.h"
int someFunc() {
int i = glfwInit();
glfwTerminate();
return i;
}
But when I run the Rust program, it says glfwInit and glfwTerminate are unresolved symbols... If I do this instead:
...
#include <GLFW/glfw3.h>
...
I get the error in my Rust program that there is no such directory, though the library was linked correctly using CMake... I read something about a cmake crate, so I am guessing it has something to do with that, but at the moment I am completely clueless. I must say that I am new to both Rust and C/C++ (student in Computer Science...) :slight_smile:
Note that when I remove any glfw related stuff, everything works correctly!
I am thinking if I can somehow invoke CMake from the build.rs file, that I can link everything in the C project correctly, right? I just do not understand very much CMake since I have been doing everything with an IDE (CLion by jet brains) and very limited commands in CMakeLists.txt.
This is how the project is organised...
project structure:
// generated with cargo new rust_project ...
.../rust_project/
c_project/ // This was generated with the IDE for C (CLion)...
dependecies/
glfw-3.3.4/
include/
...
CMakeLists.txt
MyCFile.c
...
src
main.rs
build.rs
cargo.toml
...
CMakeLists.txt:
cmake_minimum_required(VERSION 3.17)
project(c_project)
set(CMAKE_C_STANDARD 11)
add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/dependencies/glfw-3.3.4)
include_directories(${CMAKE_CURRENT_SOURCE_DIR}/dependencies/glfw-3.3.4/include)
##some more libraries... not currently included in MyCFile.c !##
add_library(c_project MyCFile.c)
target_link_libraries(c_project PRIVATE <other_libraries> glfw ${GLFW_LIBRARIES} <other_libraries>)
MyCFile.c:
#include <GLFW/glfw3.h>
int someFunc() {
int i = glfwInit();
glfwTerminate();
return i;
}
main.rs:
extern "C" {
fn someFunc() -> i32;
}
fn main() {
unsafe {
println!("{}", someFunc());
}
}
build.rs
extern crate cc;
fn main() {
cc::Build::new()
.file("c_project/MyCFile.c")
.compile("library");
/*
* I am guessing here goes something along the lines:
* "CMakeLists.txt".execute (as pseudo code...)
*/
}
cargo.toml:
[package]
name = "rust_project"
version = "0.1.0"
edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
[build-dependencies]
cc = "1.0.70"
the error message upon "cargo check":
error: failed to run custom build command for `version_zero v0.1.0 (D:\...\rust_project)`
Caused by:
process didn't exit successfully: `D:\...\rust_project\target\debug\build\rust_project-bf398c13c8af8b0c\build-script-build` (exit code: 1)
--- stdout
TARGET = Some("x86_64-pc-windows-msvc")
OPT_LEVEL = Some("0")
HOST = Some("x86_64-pc-windows-msvc")
CC_x86_64-pc-windows-msvc = None
CC_x86_64_pc_windows_msvc = None
HOST_CC = None
CC = None
CFLAGS_x86_64-pc-windows-msvc = None
CFLAGS_x86_64_pc_windows_msvc = None
HOST_CFLAGS = None
CFLAGS = None
CRATE_CC_NO_DEFAULTS = None
CARGO_CFG_TARGET_FEATURE = Some("fxsr,sse,sse2")
DEBUG = Some("true")
running: "C:\\Program Files (x86)\\Microsoft Visual Studio\\2019\\Community\\VC\\Tools\\MSVC\\14.29.30037\\bin\\HostX64\\x64\\cl.exe" "-nologo" "-MD" "-Z7" "-Brepro" "-W4" "-FoD:\\...\\rust_project\\target\\debug\\build\\version_zero-54603a96dee57aac\\out\\c_project/MyCFile.o" "-c" "c_project/MyCFile.c"
MyCFile.c
c_project/MyCFile.c(5): fatal error C1083: Cannot open include file: 'GLFW/glfw3.h': No such file or directory
exit code: 2
--- stderr
error occurred: Command "C:\\Program Files (x86)\\Microsoft Visual Studio\\2019\\Community\\VC\\Tools\\MSVC\\14.29.30037\\bin\\HostX64\\x64\\cl.exe" "-nologo" "-MD" "-Z7" "-Brepro" "-W4" "-FoD:\\...\\rust_project\\target\\debug\\build\\version_zero-54603a96dee57aac\\out\\c_project/MyCFile.o" "-c" "c_project/MyCFile.c" with args "cl.exe" did not execute successfully (status code exit code: 2).
I used: https://crates.io/crates/cc, https://liufuyang.github.io/2020/02/02/call-c-in-rust.html, https://docs.rust-embedded.org/book/interoperability/c-with-rust.html
CMake was installed through: https://cmake.org/download/.
Downloaded the option: "Windows x64 Installer: Installer tool has changed. Uninstall CMake 3.4 or lower first! cmake-3.21.2-windows-x86_64.msi"
Honoustly don't know how you would do it with the zip files...
tried to uninstall the current CMake, and downloaded version 3.4 (and installed it).
Also changed the build.rs file to:
use cmake;
fn main() {
let dst = cmake::build("c_project");
println!("cargo:rustc-link-search=native={}", dst.display());
println!("cargo:rustc-link-lib=static=MyCFile");
}
cargo check compiles just fine, cargo run, will produce an error:
<lots of file paths...>
= note: LINK : fatal error LNK1181: cannot open input file 'MyCFile.lib'
Note that I do not get any indications anymore of cmake not being installed...
Cannot open include file: 'GLFW/glfw3.h': No such file or directory - the error states that the c compiler cannot find the header file glfw3.h from your build point. The cc crate provides the .include for it's builder. Find out where you have the glfw3.h file and pass its path into the include builder method:
fn main() {
cc::Build::new()
.file("c_project/MyCFile.c")
.include("path/to/glfw3.h")
.compile("library");
/*
* I am guessing here goes something along the lines:
* "CMakeLists.txt".execute (as pseudo code...)
*/
}

How Link External C Library to WebAssembly Build

I was reading this article (https://www.smashingmagazine.com/2019/04/webassembly-speed-web-app/) that explained how they used zlib, among other things, to speed up their web project:
To support the zlib library, we use the flag USE_ZLIB; zlib is so common that it’s already been ported to WebAssembly, and Emscripten will include it for us in our project
I would like to use zlib in my own WASM module.
In my C code (compiled with emcc), I wrote this interfacing function:
#include <zlib.h>
int pcf_decompress_zlib(unsigned char *input, int input_length, unsigned char *output, int output_length)
{
uLongf output_length_result = output_length;
int result = uncompress(output, &output_length_result, input, input_length);
if (result != Z_OK) {
return 0;
} else {
return output_length_result;
}
}
I compiled it like so:
emcc decompress.c -O3 -s WASM=1 -s SIDE_MODULE=1 -s "EXPORTED_FUNCTIONS=['_pcf_decompress_zlib']" -s USE_ZLIB=1 -o decompress.wasm
When I did that, emcc automatically downloaded in a zlib library, so it seemed to know how to handle this.
Then in the browser, I have this class:
export class Decompressor {
wasmOnLoad(obj) {
this.instance = obj.instance;
console.log("Loaded WASM");
console.log(obj.instance);
// Don't do anything else yet
}
constructor() {
this.memory = new WebAssembly.Memory({
initial: 1
});
this.heap = new Uint8Array(this.memory.buffer);
this.imports = {
env: {
__memory_base: 0,
memory: this.memory,
abort: function(err) {
throw new Error('abort ' + err);
},
}
};
}
start() {
console.log("startWasm");
WebAssembly.instantiateStreaming(fetch('decompress/decompress.wasm'), this.imports)
.then(this.wasmOnLoad.bind(this));
}
}
And then this in my main JS code loaded from my HTML:
import { Decompressor } from "./decompress/decompress.js";
var l = new Decompressor();
l.start();
When I load the page, Firefox gives me this error:
LinkError: import object field '_uncompress' is not a Function
It appears that the wasm code being emitted doesn't include zlib, and zlib is also not built into the browser. I thought about changing SIDE_MODULE to MAIN_MODULE, but that resulted in dozens of undefined symbols, making the problem even worse.
There would be no point in having emcc provide a USE_ZLIB=1 option if it didn't automatically make zlib available. So what am I missing t make this work? How do I get emcc to statically include the zlib code that it already has into the wasm module I'm compiling?
Thanks.
One way is to include the zlib source during the emcc build. I tested below. First, create this file structure (include the zlib source folder you downloaded)
$ tree -L 2 .
.
├── build.sh
├── dist
├── lib
│   └── zlib-1.2.11
└── src
└── decompress.c
build.sh
ZLIB="lib/zlib-1.2.11"
emcc \
-O3 \
-s WASM=1 \
-s EXPORTED_FUNCTIONS="[ \
'_free', '_malloc' \
, '_pcf_decompress_zlib' \
]" \
-I $ZLIB \
-o dist/decompress.wasm \
$ZLIB/*.c \
src/decompress.c
Now, configure zlib and build!
$ lib/zlib-1.2.11/configure `# you only need to run this once`
$ ./build.sh

Linking C++ static libraries with QtCreator

I am using QtCreator 4.2.1 with MinGW4.9.2 32 bit compiler (Win 8.1 platform).
My pgi project only has a main.cpp which uses objects and functions belonging to my PhygenicLib static library. This static library in turn relies on functions and classes and constants defined in the Mathlib2 static library along with the eigen3 template library (template-based, only headers, no .cpp!) found on the web.
Here is the PhygenicLib.pro which compiles nicely into the libPhygenicLib.a file (debug mode):
QT -= gui
TARGET = PhygenicLib
TEMPLATE = lib
CONFIG += staticlib
DEFINES += QT_DEPRECATED_WARNINGS
INCLUDEPATH += d:/Qt-apps/Mathlib2 d:/Qt-apps/eigen3
SOURCES += phygenic.cpp \ cinemeca_pg.cpp \ mecagen.cpp \
rc_perf_pg.cpp \ xploit_pg.cpp \ utilities.cpp \
Analex.cpp \ Anasyntax.cpp
HEADERS += phygenic.h \ cinemeca_pg.h \ mecagen.h \ rc_perf_pg.h \
xploit_pg.h \ utilities.h \ Analex.h \ Anasyntax.h
unix { target.path = /usr/lib
INSTALLS += target }
Here is the pgi.pro (debug mode):
QT += core
QT -= gui
CONFIG += c++11
TARGET = pgi
CONFIG += console
CONFIG -= app_bundle
TEMPLATE = app
LIBS += d:/Qt-apps/build-Mathlib2-Desktop_Qt_5_5_1_MinGW_32bit-Release/release/libMathlib2.a \
d:/Qt-apps/build-PhygenicLib-Desktop_Qt_5_5_1_MinGW_32bit-Debug/debug/libPhygenicLib.a
INCLUDEPATH += d:/Qt-apps/PhygenicLib \
d:/Qt-apps/Mathlib2 \
d:/Qt-apps/eigen3
SOURCES += main.cpp
HEADERS +=
DEFINES += QT_DEPRECATED_WARNINGS
Note that Mathlib2 is compiled in release mode not debug mode, but that should not be a problem (?). Now, here is my main.cpp:
#include <iostream>
#include "Anasyntax.h"
using namespace std;
int main(int, char**)
{
ofstream fdmp("test"+extFichier[DMP]);
try { // bloc try
fdmp<<"--- test généraux interpréteur ---"<<endl ;
Anasyntax asynt ;
asynt.interprete(fdmp);
} catch (std::exception const& err) {
fdmp << err.what()<< endl<<" - Erreur fatale." << endl ;
cerr << err.what()<< endl<<"- Erreur fatale." << endl ;
}
cout<<'\a' ;
fdmp << "--- FIN DE L'EXECUTION --- "<< std::endl;
fdmp.close() ;
return EXIT_SUCCESS ;
}
#include Anasyntax.h is pulling all the headers from the various libraries wherever required (root of the include tree, so to say).
Now when I try to compile and link the project, I get the following diagnostic (sorry the language, QtCreator installed in French):
14:48:12: Exécution des étapes pour le projet pgi...
14:48:12: Débute : "C:\Qt\Tools\mingw492_32\bin\mingw32-make.exe" clean
C:/Qt/Tools/mingw492_32/bin/mingw32-make -f Makefile.Debug clean
mingw32-make[1]: Entering directory 'D:/Qt-apps/build-pgi-Desktop_Qt_5_5_1_MinGW_32bit-Debug'
del debug\main.o
Impossible de trouver D:\Qt-apps\build-pgi-Desktop_Qt_5_5_1_MinGW_32bit-Debug\debug\main.o
mingw32-make[1]: Leaving directory 'D:/Qt-apps/build-pgi-Desktop_Qt_5_5_1_MinGW_32bit-Debug'
C:/Qt/Tools/mingw492_32/bin/mingw32-make -f Makefile.Release clean
mingw32-make[1]: Entering directory 'D:/Qt-apps/build-pgi-Desktop_Qt_5_5_1_MinGW_32bit-Debug'
del release\main.o
Impossible de trouver D:\Qt-apps\build-pgi-Desktop_Qt_5_5_1_MinGW_32bit-Debug\release\main.o
mingw32-make[1]: Leaving directory 'D:/Qt-apps/build-pgi-Desktop_Qt_5_5_1_MinGW_32bit-Debug'
14:48:13: Le processus "C:\Qt\Tools\mingw492_32\bin\mingw32-make.exe" s'est terminé normalement.
14:48:13: Configuration inchangée, étape qmake sautée.
14:48:13: Débute : "C:\Qt\Tools\mingw492_32\bin\mingw32-make.exe"
C:/Qt/Tools/mingw492_32/bin/mingw32-make -f Makefile.Debug
mingw32-make[1]: Entering directory 'D:/Qt-apps/build-pgi-Desktop_Qt_5_5_1_MinGW_32bit-Debug'
g++ -c -pipe -fno-keep-inline-dllexport -g -std=c++0x -frtti -Wall -Wextra -fexceptions -mthreads -DUNICODE -DQT_DEPRECATED_WARNINGS -DQT_QML_DEBUG -DQT_CORE_LIB -I..\pgi -I. -Id:\Qt-apps\PhygenicLib -Id:\Qt-apps\Mathlib2 -Id:\Qt-apps\eigen3 -IC:\Qt\5.5\mingw492_32\include -IC:\Qt\5.5\mingw492_32\include\QtCore -Idebug -IC:\Qt\5.5\mingw492_32\mkspecs\win32-g++ -o debug\main.o ..\pgi\main.cpp
g++ -Wl,-subsystem,console -mthreads -o debug\pgi.exe debug/main.o d:/Qt-apps/build-Mathlib2-Desktop_Qt_5_5_1_MinGW_32bit-Release/release/libMathlib2.a d:/Qt-apps/build-PhygenicLib-Desktop_Qt_5_5_1_MinGW_32bit-Debug/debug/libPhygenicLib.a -LC:/Qt/5.5/mingw492_32/lib -lQt5Cored
d:/Qt-apps/build-PhygenicLib-Desktop_Qt_5_5_1_MinGW_32bit-Debug/debug/libPhygenicLib.a(mecagen.o): In function `ZN10Solide_dyn4calcEv':
D:\Qt-apps\build-PhygenicLib-Desktop_Qt_5_5_1_MinGW_32bit-Debug/../PhygenicLib/mecagen.cpp:220: undefined reference to `rotation_WM(V3)'
D:\Qt-apps\build-PhygenicLib-Desktop_Qt_5_5_1_MinGW_32bit-Debug/../PhygenicLib/mecagen.cpp:221: undefined reference to `Mat_transp_3x3(Mat3x3)'
and so on, and so on...
It looks like g++ is trying to link mecagen.cpp (which is already compiled as part of libPhygenicLib.a) and not finding references to symbols which are part of Mathlib2 library (includepath= d:/Qt-apps/Mathlib2, LIB= d:/.../release/libMathlib2.a).
I found a walk-around solution: just inserting in the main.cpp a reference to any Mathlib2 object or function, such as function coupure hereafter, enables to build and run the project executable without any warning/error:
int main(int, char**)
{
ofstream fdmp("test"+extFichier[DMP]);
coupure(0.,0.,0.) ; // any object or function from Mathlib2 works as well!
try { // etc...
So the problem is overcome but it drives me nuts not to understand why it won't work without inserting a useless code line... I have to say I was too lazy to ever learn about g++, make, qmake and makeFile sort of things; just pushing QtCreator buttons is so easy and comfortable...
I read again some posts with somewhat similar issues and found one comment regarding the order of declaring static libraries which may be critical in some cases.
And indeed, if I declared the LIBS in my pgi.pro in the reverse order, as follows:
LIBS += d:/Qt-apps/build-PhygenicLib-Desktop_Qt_5_5_1_MinGW_32bit-Debug/debug/libPhygenicLib.a \
d:/Qt-apps/build-Mathlib2-Desktop_Qt_5_5_1_MinGW_32bit-Release/release/libMathlib2.a
the project then compiles and links fine without any extraneous reference to a Mathlib2 object or function in the main.cpp. I got the lesson and will now take care of declaring LIBS in the appropriate order...

C - Cmake compiling program with libcurl

I'm trying to use some curl code to test the lib but I can't compile it :(
I'm using Clion (Cmake + gcc) and I've got a libcurl.a, a libcurl.dll and a libcurl.dlla
What am I suppose to do with those 3 files ?
This is my CmakeLists.txt :
cmake_minimum_required(VERSION 3.10)
project(curl_test2 C)
set(CMAKE_C_STANDARD 99)
ADD_DEFINITIONS( -DCURL_STATICLIB )
include_directories( "include" )
set(SRCS"
srcs/main.c")
set(HEADERS
"include/curl/curl.h"
"include/curl/easy.h")
link_directories("lib")
add_executable(curl_test2 ${SRCS} ${HEADERS})
target_link_libraries(curl_test2 "curl")
this is my project:
include
--curl
--curl.h
srcs
--main.c
lib
--libcurl.a
(--dlls
--libcurl.dll
--libcurl.dlla) <- i'm not using them for now
this is my main.c (just a libcurl example, not really important - i'm just trying to compile):
#include <stdio.h>
#include "curl/curl.h"
int main(void)
{
CURL *curl;
CURLcode res;
curl_global_init(CURL_GLOBAL_ALL);
curl = curl_easy_init();
if(curl) {
curl_easy_setopt(curl, CURLOPT_URL, "http://postit.example.com/moo.cgi");
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, "name=daniel&project=curl");
res = curl_easy_perform(curl);
if(res != CURLE_OK)
fprintf(stderr, "curl_easy_perform() failed: %s\n",
curl_easy_strerror(res));
curl_easy_cleanup(curl);
}
curl_global_cleanup();
return 0;
}
When I'm trying to build the compiler drop this error : "undefined reference to `_imp__curl_global_init'" (and all other curl function calls)
Can you help me ? I'm a bit lost - I did not really use Cmake before..
Thank you !
You link with static curl library (libcurl.a) which required CURL_STATICLIB definition. Add this in your CMakeLists.txt between "project" and "add_executable" commands:
add_definitions( -DCURL_STATICLIB )
Moreover, your file .dlla is not .dll.a ? In that case, .dll.a file is (I think) generated by MinGW toolchain and is required only if you link dynamically with curl library (as .dll file).
I downloaded the sources of cURL library here and compiled it (with Visual Compiler 2015). It produces shared libraries (libcurl.dll and libcurl_imp.lib) that I move in the lib directory (I should rename libcurl_imp.lib to curl.lib). I move also the include directory.
Then, I used this CMakeLists.txt :
cmake_minimum_required(VERSION 3.10)
project(curl_test2 C)
set(CMAKE_C_STANDARD 99)
set(SRCS "srcs/main.c")
set( CURL_LIBRARY ${CMAKE_SOURCE_DIR}/lib )
set( CURL_INCLUDE_DIR ${CMAKE_SOURCE_DIR}/include )
find_package( CURL )
include_directories( ${CURL_INCLUDE_DIRS} )
link_directories( ${CURL_LIBRARIES} )
add_executable(curl_test2 ${SRCS})
target_link_libraries(curl_test2 curl)
With it, your project compiles and the execution works.
I also tried with cURL static library (for that, if you use CMake to compile cURL, add -DCURL_STATICLIB=ON in cmake command line). And I moved the produced library libcurl.lib to lib\curl.lib.
I used this CMakeLists.txt :
cmake_minimum_required(VERSION 3.10)
project(curl_test2 C)
set(CMAKE_C_STANDARD 99)
set(SRCS "srcs/main.c")
add_definitions( -DCURL_STATICLIB )
set( CURL_LIBRARY ${CMAKE_SOURCE_DIR}/lib )
set( CURL_INCLUDE_DIR ${CMAKE_SOURCE_DIR}/include )
find_package( CURL )
include_directories( ${CURL_INCLUDE_DIRS} )
link_directories( ${CURL_LIBRARIES} )
add_executable(curl_test2 ${SRCS})
target_link_libraries(curl_test2 curl wldap32 ws2_32)
And it works too.

How to make visual studio compile exports for C DLL the way MinGW does [duplicate]

With VS2005, I want to create a DLL and automatically export all symbols without adding __declspec(dllexport) everywhere, and without hand-creating .def files. Is there a way to do this?
Short answer
You can do it with help of the new version of the CMake (any version cmake-3.3.20150721-g9cd2f-win32-x86.exe or higher).
Currently it's in the dev branch.
Later, the feature will be added in the release version of the cmake-3.4.
Link to the cmake dev:
cmake_dev
Link to an article which describe the technic:
Create dlls on Windows without declspec() using new CMake export all feature
Link to an example project:
cmake_windows_export_all_symbols
Long answer
Caution:
All information below is related to the MSVC compiler or Visual Studio.
If you use other compilers like gcc on Linux or MinGW gcc compiler on Windows you don't have linking errors due to not exported symbols, because gcc compiler export all symbols in a dynamic library (dll) by default instead of MSVC or Intel windows compilers.
In windows you have to explicitly export symbol from a dll.
More info about this is provided by links:
Exporting from a DLL
HowTo: Export C++ classes from a DLL
So if you want to export all symbols from dll with MSVC (Visual Studio compiler) you have two options:
Use the keyword __declspec(dllexport) in the class/function's definition.
Create a module definition (.def) file and use the .def file when building the DLL.
1. Use the keyword __declspec(dllexport) in the class/function's definition
1.1. Add "__declspec(dllexport) / __declspec(dllimport)" macros to a class or method you want to use. So if you want to export all classes you should add this macros to all of them
More info about this is provided by link:
Exporting from a DLL Using __declspec(dllexport)
Example of usage (replace "Project" by real project name):
// ProjectExport.h
#ifndef __PROJECT_EXPORT_H
#define __PROJECT_EXPORT_H
#ifdef USEPROJECTLIBRARY
#ifdef PROJECTLIBRARY_EXPORTS
#define PROJECTAPI __declspec(dllexport)
#else
#define PROJECTAPI __declspec(dllimport)
#endif
#else
#define PROJECTAPI
#endif
#endif
Then add "PROJECTAPI" to all classes.
Define "USEPROJECTLIBRARY" only if you want export/import symbols from dll.
Define "PROJECTLIBRARY_EXPORTS" for the dll.
Example of class export:
#include "ProjectExport.h"
namespace hello {
class PROJECTAPI Hello {}
}
Example of function export:
#include "ProjectExport.h"
PROJECTAPI void HelloWorld();
Caution: don't forget to include "ProjectExport.h" file.
1.2. Export as C functions.
If you use C++ compiler for compilation code is written on C, you could add extern "C" in front of a function to eliminate name mangling
More info about C++ name mangling is provided by link:
Name Decoration
Example of usage:
extern "C" __declspec(dllexport) void HelloWorld();
More info about this is provided by link:
Exporting C++ Functions for Use in C-Language Executables
2. Create a module definition (.def) file and use the .def file when building the DLL
More info about this is provided by link:
Exporting from a DLL Using DEF Files
Further I describe three approach about how to create .def file.
2.1. Export C functions
In this case you could simple add function declarations in the .def file by hand.
Example of usage:
extern "C" void HelloWorld();
Example of .def file (__cdecl naming convention):
EXPORTS
_HelloWorld
2.2. Export symbols from static library
I tried approach suggested by "user72260".
He said:
Firstly, you could create static library.
Then use "dumpbin /LINKERMEMBER" to export all symbols from static library.
Parse the output.
Put all results in a .def file.
Create dll with the .def file.
I used this approach, but it's not very convinient to always create two builds (one as a static and the other as a dynamic library). However, I have to admit, this approach really works.
2.3. Export symbols from .obj files or with help of the CMake
2.3.1. With CMake usage
Important notice: You don't need any export macros to a classes or functions!
Important notice: You can't use /GL (Whole Program Optimization) when use this approach!
Create CMake project based on the "CMakeLists.txt" file.
Add the following line to the "CMakeLists.txt" file:
set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS ON)
Then create Visual Studio project with help of "CMake (cmake-gui)".
Compile the project.
Example of usage:
Root folder
CMakeLists.txt (Root folder)
cmake_minimum_required(VERSION 2.6)
project(cmake_export_all)
set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS ON)
set(dir ${CMAKE_CURRENT_SOURCE_DIR})
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${dir}/bin")
set(SOURCE_EXE main.cpp)
include_directories(foo)
add_executable(main ${SOURCE_EXE})
add_subdirectory(foo)
target_link_libraries(main foo)
main.cpp (Root folder)
#include "foo.h"
int main() {
HelloWorld();
return 0;
}
Foo folder (Root folder / Foo folder)
CMakeLists.txt (Foo folder)
project(foo)
set(SOURCE_LIB foo.cpp)
add_library(foo SHARED ${SOURCE_LIB})
foo.h (Foo folder)
void HelloWorld();
foo.cpp (Foo folder)
#include <iostream>
void HelloWorld() {
std::cout << "Hello World!" << std::endl;
}
Link to the example project again:
cmake_windows_export_all_symbols
CMake uses the different from "2.2. Export symbols from static library" approach.
It does the following:
1) Create "objects.txt" file in the build directory with information of .obj files are used in a dll.
2) Compile the dll, that is create .obj files.
3) Based on "objects.txt" file information extract all symbols from .obj file.
Example of usage:
DUMPBIN /SYMBOLS example.obj > log.txt
More info about this is provided by link:
/SYMBOLS
4) Parse extracted from .obj file information.
In my opinion I would use calling convection, for example "__cdecl/__fastcall", "SECTx/UNDEF" symbol field (the third column), "External/Static" symbol field (the fifth column), "??", "?" information for parsing an .obj files.
I don't know how exactly CMake parse an .obj file.
However, CMake is open source, so you could find out if it's interested for you.
Link to the CMake project:
CMake_github
5) Put all exported symbols in a .def file.
6) Link a dll with usage of a .def created file.
Steps 4)-5), that is parse .obj files and create a .def file before linking and using the .def file CMake does with help of "Pre-Link event".
While "Pre-Link event" fires you could call any program you want.
So in case of "CMake usage" "Pre-Link event" call the CMake with the following information about where to put the .def file and where the "objects.txt" file and with argument "-E __create_def".
You could check this information by creating CMake Visusal Studio project with "set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS ON)" and then check the ".vcxproj" project file for dll.
If you try to compile a project without "set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS ON)" or with "set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS OFF)" you will get linking errors, due to the fact that symbols are not exported from a dll.
More info about this is provided by link:
Understanding Custom Build Steps and Build Events
2.3.2. Without CMake usage
You simple could create a small program for parsing .obj file by youself without CMake usege. Hovewer, I have to admit that CMake is very usefull program especially for cross-platform development.
It can be done...
The way we do it here is to use the /DEF option of the linker to pass a "module definition file" containing a list of our exports. I see from your question that you know about these files. However, we do not do it by hand. The list of exports itself is created by the dumpbin /LINKERMEMBER command, and manipulating the output via a simple script to the format of a module definition file.
It is a lot of work to setup, but it allows us to compile code created without dllexport declarations for Unix on Windows.
I want to create a DLL and automatically export all symbols without adding __declspec(dllexport) everywhere and without hand-creating .def files. Is threre a way to do this?
This is a late answer, but it provides the details for Maks's answer in Section (2). It also avoids scripts and uses a C++ program called dump2def. The source code for dump2def is below.
Finally, the steps below assume you are working from a Visual Studio Developer Prompt, which is a Windows Terminal where vcvarsall.bat has been run. You need to ensure the build tools like cl.exe, lib.exe, link.exe and nmake.exe are on-path.
More info about this is provided by link:
Exporting from a DLL Using DEF
Files
...
The instruction below use:
static.lib - static library archive (*.a file on Linux)
dynamic.dll - dynamic library (*.so file on Linux)
import.lib - dynamic library (import library on Windows)
Also note that though you are exporting everything from the DLL, clients still must use declspec(dllimport) on all symbols (classes, functions and data) that they use. Also see on MSDN.
First, take your objects and create a static archive:
AR = lib.exe
ARFLAGS = /nologo
CXX_SRCS = a.cpp b.cpp c.cpp ...
LIB_OBJS = a.obj b.obj c.obj ...
static.lib: $(LIB_OBJS)
$(AR) $(ARFLAGS) $(LIB_OBJS) /out:$#
Second, run dumpbin.exe /LINKERMEMEBER on the archive to create a *.dump file:
dynamic.dump:
dumpbin /LINKERMEMBER static.lib > dynamic.dump
Third, run dump2def.exe on the *.dump file to produce the *.def file. The source code for dump2def.exe is below.
dynamic.def: static.lib dynamic.dump
dump2def.exe dynamic.dump dynamic.def
Fourth, build the DLL:
LD = link.exe
LDFLAGS = /OPT:REF /MACHINE:X64
LDLIBS = kernel32.lib
dynamic.dll: $(LIB_OBJS) dynamic.def
$(LD) $(LDFLAGS) /DLL /DEF:dynamic.def /IGNORE:4102 $(LIB_OBJS) $(LDLIBS) /out:$#
/IGNORE:4102 is used to avoid this warning. It is expected in this case:
dynamic.def : warning LNK4102: export of deleting destructor 'public: virtual v
oid * __ptr64 __cdecl std::exception::`scalar deleting destructor'(unsigned int)
__ptr64'; image may not run correctly
When the dynamic.dll recipe is invoked, it creates a dynamic.lib import file and dynamic.exp file, too:
> cls && nmake /f test.nmake dynamic.dll
...
Creating library dynamic.lib and object dynamic.exp
And:
C:\Users\Test\testdll>dir *.lib *.dll *.def *.exp
Volume in drive C is Windows
Volume Serial Number is CC36-23BE
Directory of C:\Users\Test\testdll
01/06/2019 08:33 PM 71,501,578 static.lib
01/06/2019 08:33 PM 11,532,052 dynamic.lib
Directory of C:\Users\Test\testdll
01/06/2019 08:35 PM 5,143,552 dynamic.dll
Directory of C:\Users\Test\testdll
01/06/2019 08:33 PM 1,923,070 dynamic.def
Directory of C:\Users\Test\testdll
01/06/2019 08:35 PM 6,937,789 dynamic.exp
5 File(s) 97,038,041 bytes
0 Dir(s) 139,871,186,944 bytes free
Gluing it together here is what the Nmake makefile looks like. It is part of a real Nmake file:
all: test.exe
test.exe: pch.pch static.lib $(TEST_OBJS)
$(LD) $(LDFLAGS) $(TEST_OBJS) static.lib $(LDLIBS) /out:$#
static.lib: $(LIB_OBJS)
$(AR) $(ARFLAGS) $(LIB_OBJS) /out:$#
dynamic.map:
$(LD) $(LDFLAGS) /DLL /MAP /MAPINFO:EXPORTS $(LIB_OBJS) $(LDLIBS) /out:dynamic.dll
dynamic.dump:
dumpbin.exe /LINKERMEMBER static.lib /OUT:dynamic.dump
dynamic.def: static.lib dynamic.dump
dump2def.exe dynamic.dump
dynamic.dll: $(LIB_OBJS) dynamic.def
$(LD) $(LDFLAGS) /DLL /DEF:dynamic.def /IGNORE:4102 $(LIB_OBJS) $(LDLIBS) /out:$#
clean:
$(RM) /F /Q pch.pch $(LIB_OBJS) pch.obj static.lib $(TEST_OBJS) test.exe *.pdb
And here is the source code for dump2def.exe:
#include <iostream>
#include <fstream>
#include <sstream>
#include <string>
#include <vector>
#include <set>
typedef std::set<std::string> SymbolMap;
void PrintHelpAndExit(int code)
{
std::cout << "dump2def - create a module definitions file from a dumpbin file" << std::endl;
std::cout << " Written and placed in public domain by Jeffrey Walton" << std::endl;
std::cout << std::endl;
std::cout << "Usage: " << std::endl;
std::cout << " dump2def <infile>" << std::endl;
std::cout << " - Create a def file from <infile> and write it to a file with" << std::endl;
std::cout << " the same name as <infile> but using the .def extension" << std::endl;
std::cout << " dump2def <infile> <outfile>" << std::endl;
std::cout << " - Create a def file from <infile> and write it to <outfile>" << std::endl;
std::exit(code);
}
int main(int argc, char* argv[])
{
// ******************** Handle Options ******************** //
// Convenience item
std::vector<std::string> opts;
for (size_t i=0; i<argc; ++i)
opts.push_back(argv[i]);
// Look for help
std::string opt = opts.size() < 3 ? "" : opts[1].substr(0,2);
if (opt == "/h" || opt == "-h" || opt == "/?" || opt == "-?")
PrintHelpAndExit(0);
// Add <outfile> as needed
if (opts.size() == 2)
{
std::string outfile = opts[1];
std::string::size_type pos = outfile.length() < 5 ? std::string::npos : outfile.length() - 5;
if (pos == std::string::npos || outfile.substr(pos) != ".dump")
PrintHelpAndExit(1);
outfile.replace(pos, 5, ".def");
opts.push_back(outfile);
}
// Check or exit
if (opts.size() != 3)
PrintHelpAndExit(1);
// ******************** Read MAP file ******************** //
SymbolMap symbols;
try
{
std::ifstream infile(opts[1].c_str());
std::string::size_type pos;
std::string line;
// Find start of the symbol table
while (std::getline(infile, line))
{
pos = line.find("public symbols");
if (pos == std::string::npos) { continue; }
// Eat the whitespace after the table heading
infile >> std::ws;
break;
}
while (std::getline(infile, line))
{
// End of table
if (line.empty()) { break; }
std::istringstream iss(line);
std::string address, symbol;
iss >> address >> symbol;
symbols.insert(symbol);
}
}
catch (const std::exception& ex)
{
std::cerr << "Unexpected exception:" << std::endl;
std::cerr << ex.what() << std::endl;
std::cerr << std::endl;
PrintHelpAndExit(1);
}
// ******************** Write DEF file ******************** //
try
{
std::ofstream outfile(opts[2].c_str());
// Library name, cryptopp.dll
std::string name = opts[2];
std::string::size_type pos = name.find_last_of(".");
if (pos != std::string::npos)
name.erase(pos);
outfile << "LIBRARY " << name << std::endl;
outfile << "DESCRIPTION \"Crypto++ Library\"" << std::endl;
outfile << "EXPORTS" << std::endl;
outfile << std::endl;
outfile << "\t;; " << symbols.size() << " symbols" << std::endl;
// Symbols from our object files
SymbolMap::const_iterator it = symbols.begin();
for ( ; it != symbols.end(); ++it)
outfile << "\t" << *it << std::endl;
}
catch (const std::exception& ex)
{
std::cerr << "Unexpected exception:" << std::endl;
std::cerr << ex.what() << std::endl;
std::cerr << std::endl;
PrintHelpAndExit(1);
}
return 0;
}
I've written a small program to parse the output of "dumpbin /linkermember" on the .lib file. I have upwards of 8,000 function references to export from one DLL.
The problem with doing it on a DLL is that you have to link the DLL without the exported definitions once to create the .lib file, then generate the .def which means you now have to relink the DLL again with the .def file to actually have the references exported.
Working with static libraries is easier. Compile all your sources into static libs, run dumbin, generate a .def with your little program, then link the libs together into a DLL now that the export names are available.
Unfortunately my company won't allow me to show you the source. The work involved is recognizing which "public symbols" in the dump output are not needed in your def file. You have to throw away a lot of those references, NULL_IMPORT_DESCRIPTOR, NULL_THUNK_DATA, __imp*, etc.
Thanks #Maks for the detailed answer.
Below is an example of what I used in Pre-Link event to generate def file from obj. I hope it will be helpful for someone.
dumpbin /SYMBOLS $(Platform)\$(Configuration)\mdb.obj | findstr /R "().*External.*mdb_.*" > $(Platform)\$(Configuration)\mdb_symbols
(echo EXPORTS & for /F "usebackq tokens=2 delims==|" %%E in (`type $(Platform)\$(Configuration)\mdb_symbols`) do #echo %%E) > $(Platform)\$(Configuration)\lmdb.def
Basically I just took one of objects (mdb.obj) and grepped mdb_* functions. Then parsed output to keep just names taking into account amount of spaces for indentation (one after splitting into tokens and another in echo. I don't know if it's matter though).
Real world script probably will kind of more complex though.
Perhaps somebody finds useful my Python script for converting .dump to .def.
import sys, os
functions = []
startPoint = False
# Exclude standard API like sprintf to avoid multiple definition link error
excluded_functions = [ 'sprintf', 'snprintf', 'sscanf', 'fprintf' ]
if len(sys.argv) < 2:
print('Usage: %s <Input .dump file> <Output .def file>.' % sys.argv[0])
print('Example: %s myStaticLib.dump exports.def' % sys.argv[0])
sys.exit(1)
print('%s: Processing %s to %s' % (sys.argv[0], sys.argv[1], sys.argv[2]))
fin = open(sys.argv[1], 'r')
lines = fin.readlines()
fin.close()
# Reading
for l in lines:
l_str = l.strip()
if (startPoint == True) and (l_str == 'Summary'): # end point
break
if (startPoint == False) and ("public symbols" in l_str):
startPoint = True
continue
if (startPoint == True) and l_str is not '':
funcName = l_str.split(' ')[-1]
if funcName not in excluded_functions:
functions.append(" " + funcName)
# Writing
fout = open(sys.argv[2], 'w')
fout.write('EXPORTS\n')
for f in functions:
fout.write('%s\n' % f)
fout.close()
With this script you can get the .def file for your .lib in two steps:
dumpbin /LINKERMEMBER:1 myStaticLib.lib > myExports.dump
python dump2def.py myExports.dump myExports.def
No, you will need a macro that resolves to __declspec(dllexport) when it's included by the .cpp file that implements the exported functions, and resolves to __declspec(dllimport) otherwise.

Resources