How Link External C Library to WebAssembly Build - zlib

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

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...)
*/
}

Yocto / Poky: install and use shared library .so on separate layers

I'm trying to learn how to build custom linux images with Yocto and I'm struggling to create an image with both a shared library and a program that uses it.
I started by following this tutorial and everything went ok. Then I tried to separate the program from the library in two different layers, without success.
I started with the library code:
greetings.c
#include <stdio.h>
#include <string.h>
#include "greetings.h"
void get_greeting(char * buffer) {
if(buffer == NULL) {
return;
}
char greeting[] = "Hello world from the greetings lib\n";
strcpy(buffer, greeting);
return;
}
greetings.h
void get_greeting(char * buffer);
Makefile.am
AUTOMAKE_OPTIONS = foreign
lib_LTLIBRARIES = libgreetings.la
libgreetings_la_SOURCES = greetings.c
include_HEADERS = greetings.h
libgreetings_la_CFLAGS = -Wall -Werror -fPIC
libgreetings_la_LDFLAGS = -shared
ACLOCAL_AMFLAGS = -I m4
configure.ac
AC_INIT([Greetings lib], 1.0)
AM_INIT_AUTOMAKE
AC_PROG_CC
AC_CONFIG_MACRO_DIR([m4])
LT_INIT()
AC_CONFIG_FILES(Makefile)
AC_OUTPUT
I added this code to a git repository and created a "meta-greetings" layer with the layer.conf and recipe files:
layer.conf
# We have a conf and classes directory, add to BBPATH
BBPATH .= ":${LAYERDIR}"
# We have recipes-* directories, add to BBFILES
BBFILES += "${LAYERDIR}/recipes-*/*/*.bb \
${LAYERDIR}/recipes-*/*/*.bbappend"
BBFILE_COLLECTIONS += "meta-greetings"
BBFILE_PATTERN_meta-greetings = "^${LAYERDIR}/"
BBFILE_PRIORITY_meta-greetings = "6"
LAYERDEPENDS_meta-greetings = "core"
LAYERSERIES_COMPAT_meta-greetings = "thud"
IMAGE_INSTALL_append = " greetings"
recipes-greetings/greetings/greetings_0.1.bb
SUMMARY = "bitbake-layers recipe"
DESCRIPTION = "Simple helloworld lib"
DEPENDS = ""
LICENSE = "MIT"
LIC_FILES_CHKSUM = "file://LICENSE;md5=96af5705d6f64a88e035781ef00e98a8"
KBRANCH = "master"
SRCREV = "1a908a8f8616af704ce71d693e88c6d4498f24c4"
SRC_URI = "git://bitbucket.org/Grifo/greetings_lib.git;branch=${KBRANCH};protocol=ssh"
S = "${WORKDIR}/git"
inherit autotools
So far so good, I added this layer to my bblayers file and proceeded to compile the final image. I run it in qemu and even got to see the files in /usr/lib:
However, there's no "libgreetings.so". I don't know if that may be the cause of the problem (still to explain) but the previously mentioned tutorial got similar results so I proceeded.
After that I did the program:
helloworld.c
#include <stdio.h>
#include "greetings.h"
int main(void) {
char greeting[40];
get_greeting(greeting);
printf("Hello world!\n");
printf("%s", greeting);
return 0;
}
Makefile.am
AUTOMAKE_OPTIONS = foreign
bin_PROGRAMS = hello_world
hello_world_SOURCES = helloworld.c
hello_world_LDADD = $(libdir)/libgreetings.so
ACLOCAL_AMFLAGS = -I m4
configure.ac
AC_INIT([Hello world], 1.0)
AM_INIT_AUTOMAKE
AC_PROG_CC
AC_CONFIG_MACRO_DIR([m4])
AC_CONFIG_FILES(Makefile)
AC_OUTPUT
Added this code to git and created a "meta-helloworld" layer with the files:
layer.conf
# We have a conf and classes directory, add to BBPATH
BBPATH .= ":${LAYERDIR}"
# We have recipes-* directories, add to BBFILES
BBFILES += "${LAYERDIR}/recipes-*/*/*.bb \
${LAYERDIR}/recipes-*/*/*.bbappend"
BBFILE_COLLECTIONS += "meta-helloworld"
BBFILE_PATTERN_meta-helloworld = "^${LAYERDIR}/"
BBFILE_PRIORITY_meta-helloworld = "7"
LAYERDEPENDS_meta-helloworld = "core meta-greetings"
LAYERSERIES_COMPAT_meta-helloworld = "thud"
IMAGE_INSTALL_append = " helloworld"
recipes-helloworld/helloworld/helloworld_0.1.bb
SUMMARY = "bitbake-layers helloworld"
DESCRIPTION = "Simple helloworld program"
LICENSE = "MIT"
LIC_FILES_CHKSUM = "file://LICENSE;md5=96af5705d6f64a88e035781ef00e98a8"
KBRANCH = "master"
SRCREV = "6a29425473286028e85e74003f2f57ecaf766354"
SRC_URI = "git://bitbucket.org/Grifo/hello_world.git;branch=${KBRANCH};protocol=ssh"
DEPENDS = "greetings"
S = "${WORKDIR}/git"
inherit autotools
After all this I bitbaked the final image again but got the following error:
(...)
Sstate summary: Wanted 7 Found 0 Missed 7 Current 737 (0% match, 99% complete)
NOTE: Executing SetScene Tasks
NOTE: Executing RunQueue Tasks
ERROR: helloworld-0.1-r0 do_compile: oe_runmake failed
ERROR: helloworld-0.1-r0 do_compile: Function failed: do_compile (log file is located at /var/tmp/workspaces/grifo/poky/build/tmp/work/armv5e-poky-linux-gnueabi/helloworld/0.1-r0/temp/log.do_compile.12040)
ERROR: Logfile of failure stored in: /var/tmp/workspaces/grifo/poky/build/tmp/work/armv5e-poky-linux-gnueabi/helloworld/0.1-r0/temp/log.do_compile.12040
Log data follows:
| DEBUG: SITE files ['endian-little', 'bit-32', 'arm-common', 'arm-32', 'common-linux', 'common-glibc', 'arm-linux', 'arm-linux-gnueabi', 'common']
| DEBUG: Executing shell function do_compile
| NOTE: make -j 8
| make: *** No rule to make target `/usr/lib/libgreetings.so', needed by `hello_world'. Stop.
| ERROR: oe_runmake failed
| WARNING: /var/tmp/workspaces/grifo/poky/build/tmp/work/armv5e-poky-linux-gnueabi/helloworld/0.1-r0/temp/run.do_compile.12040:1 exit 1 from 'exit 1'
| ERROR: Function failed: do_compile (log file is located at /var/tmp/workspaces/grifo/poky/build/tmp/work/armv5e-poky-linux-gnueabi/helloworld/0.1-r0/temp/log.do_compile.12040)
ERROR: Task (/var/tmp/workspaces/grifo/poky/meta-helloworld/recipes-helloworld/helloworld/helloworld_0.1.bb:do_compile) failed with exit code '1'
NOTE: Tasks Summary: Attempted 1966 tasks of which 1965 didn't need to be rerun and 1 failed.
Summary: 1 task failed:
/var/tmp/workspaces/grifo/poky/meta-helloworld/recipes-helloworld/helloworld/helloworld_0.1.bb:do_compile
Summary: There was 1 WARNING message shown.
Summary: There were 2 ERROR messages shown, returning a non-zero exit code.
I am sorry for the really long question but I felt like I need to give all the details since I don't know if the problem comes from my recipe or my autotools files.
Before I built the recipes and compile it using yocto, I first compiled and run it in my host computer using the shell and everything run fine. I compiled and make install the greetings library (/usr/local/lib) and after that compiled the helloworld program which run without any problem.
I know that I could probably do this easily all within the same layer, however I'm trying to do it in separate layers to simulate different projects. Another requirement of mine is to use autotools instead of cmake.
Thank you in advance,
Grifo
EDIT:
I got it to work! Thank you Alexander Kanavin for pointing me in the right direction. I just had to change hello_world_LDADD = $(libdir)/libgreetings.so to hello_world_LDADD = -lgreetings in my helloworld's Makefile.am.
libgreetings.so is a file needed only for development and so it does not get installed to the image (unless you also install libgreetings-dev package - that's where it went).
During cross-compile, you typically specify libraries to link with like this:
-lgreetings
So change hello_world_LDADD = $(libdir)/libgreetings.so to hello_world_LDADD = -lgreetings.
I would start with that. Typically you shouldn't hardcode them like that in the makefile, but rather 'discover' and check the library in configure.ac (e.g. using pkg-config, assuming your library installs the corresponding .pc file), and set the appropriate compiler and linker flags:
PKG_CHECK_MODULES(GREETINGS, [greetings])
Then, in Makefile.am:
hello_world_LDADD = $(GREETINGS_LIBS)

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

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;
}

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

golang cgo check if C function exists

I'm linking a library via CGO, and not all implementations or versions have a feature which I'd like to utilize if possible — namely the presence of a function int feature(void). Is there a way I can check if this symbol is defined before attempting a call?
Any attempted use of C.feature() unsurprisingly results in a build failure on systems with a version of the library that doesn't support the feature.
In case it isn't clear, I want to build against many platforms, which may or may not have the feature. I imagine I'd either need to be able to check if a function exists at runtime (more ideal) or use go:generate to do a check and change the code depending on what it finds (less ideal). Either way, I'm not too sure how exactly to proceed.
Most libraries come with a #define that describes its version.
For example, in version 1.0:
// simple.h
#define LIB_SIMPLE_VERSION 0x00010000
void hello();
In version 1.1:
// simple.h
#define LIB_SIMPLE_VERSION 0x00010001
void hello();
void bye();
You have many choices:
Provide an IsByeAvailable() function to check the existence
Modify the signature of bye() in Go such that it returns an error, or a bool for ok
Panic if the implementation doesn't exist
Code for Choice 1
// simple.go
// #include <simple.h>
import "C"
func IsByeAvailable() bool {
return C.LIB_SIMPLE_VERSION >= 0x00010001
}
func Hello() {
C.hello()
}
func Bye() {
C.bye()
}
// version_support.c
#include <simple.h>
#if LIB_SIMPLE_VERSION < 0x00010001
void bye() { /* Empty */ }
#endif
There are several ways to do that when building your program, but that will leave you with a need to always make two builds and maintain two versions of your program, which is not convenient. In runtime you're very limited because C itself is not reflective and Go runtime features don't (actually can't) give you any benefits.
Still, there are two non-portable (but most probably good enough) hacks you can do. And as the problem is more of a C problem, the hacks are also more of C hacks. One is direct dynamic linker interface, namely dlopen()/dlsym() and the other one is usage of weak symbols in dynamic linker.
Let's first create some setup to test things out:
$ tree
.
├── lib
│   ├── lib1.c
│   ├── lib1.h
│   └── lib2.c
└── some.go
$ cat lib/lib1.h
int feature(void);
int fun(int a);
$ cat lib/lib1.c
int feature(void)
{
return 5;
}
int fun(int a)
{
return a*a;
}
$ cat lib/lib2.c
int fun(int a)
{
return a*a;
}
That's a very simple library that declares two functions and has two implementations, one has both, the other just one. Building them is easy:
$ gcc -shared -o lib/lib1.so.featured lib/lib1.c
$ gcc -shared -o lib/lib1.so.featureless lib/lib2.c
And a symlink to easily switch between two versions:
$ ln -s lib1.so.featured lib/lib1.so
So, for the dlopen()/dlsym() you create a wrapper and use dynamic linker interface to get the feature() pointer like this (yes, you can do that without calling dlsym() on every call, but let's use the very minimum code):
package main;
// #cgo LDFLAGS: -Llib -Wl,-rpath lib -l1 -ldl
// #include "lib/lib1.h"
// #include <stddef.h>
// #include <dlfcn.h>
//
// int feature_wrap(void)
// {
// static void* dlhandle;
// static int (*featurep)(void);
//
// if (!dlhandle)
// dlhandle = dlopen(NULL, RTLD_NOW);
// if (!dlhandle) // error
// return 0;
// featurep = dlsym(dlhandle, "feature");
// if (featurep)
// return featurep();
// else
// return 3;
//}
import "C"
import "fmt"
func main() {
r := C.feature_wrap()
fmt.Println(r)
}
Testing:
$ go build some.go
$ ln -sf lib1.so.featured lib/lib1.so
$ ./some
5
$ ln -sf lib1.so.featureless lib/lib1.so
$ ./some
3
For the weak symbol approach (that is preferrable IMO as it is simpler) you need to redefine you feature() function as weak and also create a wrapper for it that will provide runtime switch between two implementations:
package main;
// #cgo LDFLAGS: -Llib -Wl,-rpath lib -l1
// #include "lib/lib1.h"
// int feature(void) __attribute__((weak));
// int feature_wrap(void)
// {
// if (feature)
// return feature();
// else
// return 3;
//}
import "C"
import "fmt"
func main() {
r := C.feature_wrap()
fmt.Println(r)
}
Testing is the same.
Obviously, once you have proper if, you can do whatever you need to substitute missing feature() including callbacks to Go code.

Resources