Expose c-library functionality to node - c

I have been working on using a c library in my node project. After little bit investigation I found node-gyp.
I was successfully able to execute example but when I am trying to using third party c library functions in the code it was giving me linking error on run time.
Library can be found here http://bibutils.refbase.org/bibutils_3.40_src.tgz
I compiled the library independently to have *.a objects
I am using following example
https://github.com/nodejs/node-addon-examples/tree/master/5_function_factory/node_0.12
So I have following questions as I can infer
Shall I convert bibutils from make to gyp?
Shall I convert each source file to work with V8? I don't know how to do this.
How can I easily link this project to work with node-gyp with less noise?
Details related to script can be found below. bibutils folder is placed along with addon.cc
binding.gyp looks like
{
"targets": [
{
"target_name": "addon",
"sources": [ "addon.cc" ],
"include_dirs": ["bibutils/lib"],
"library_dirs": ["bibutils/lib/libbibutil.a","bibutils/lib/libbibprogs.a"]
}
]
}
modified addon.cc
#include <node.h>
#include "bibutils.h"
#include "bibprogs.h"
using namespace v8;
void MyFunction(const FunctionCallbackInfo<Value>& args) {
Isolate* isolate = Isolate::GetCurrent();
HandleScope scope(isolate);
/****This is not production code just to check the execution***/
bibl b;
bibl_init( &b );
bibl_free( &b );
/**************************************************************/
args.GetReturnValue().Set(String::NewFromUtf8(isolate, "hello world"));
}
void CreateFunction(const FunctionCallbackInfo<Value>& args) {
Isolate* isolate = Isolate::GetCurrent();
HandleScope scope(isolate);
Local<FunctionTemplate> tpl = FunctionTemplate::New(isolate, MyFunction);
Local<Function> fn = tpl->GetFunction();
// omit this to make it anonymous
fn->SetName(String::NewFromUtf8(isolate, "theFunction"));
args.GetReturnValue().Set(fn);
}
Compilation Result
user1#ubuntu:~/node-addon-examples/5_function_factory/node_0.12$ npm install
> function_factory#0.0.0 install /home/user1/node-addon-examples/5_function_factory/node_0.12
> node-gyp rebuild
make: Entering directory `/home/user1/node-addon-examples/5_function_factory/node_0.12/build'
CXX(target) Release/obj.target/addon/addon.o
SOLINK_MODULE(target) Release/obj.target/addon.node
COPY Release/addon.node
make: Leaving directory `/home/user1/node-addon-examples/5_function_factory/node_0.12/build'
On Execution
user1#ubuntu:~/node-addon-examples/5_function_factory/node_0.12$ node addon.js
node: symbol lookup error: /home/user1/node-addon-examples/5_function_factory/node_0.12/build/Release/addon.node: undefined symbol: _Z9bibl_initP4bibl
Debug Info:
user1#ubuntu:~/node-addon-examples/5_function_factory/node_0.12$ nm -C build/Release/addon.node | grep bibl_init
U bibl_init(bibl*)

The problem was communication between C++ and C. In above case a C header file was included in C++ code. Compile was expecting the C++ code. So on compiling linker was got choked due to mismatch in compiled code.
So I used the extern "C" directive to tell the compiler about C header files by following code.
extern "C" {
#include "bibutils.h"
#include "bibprogs.h"
}

Related

How can I use wasm-bindgen from a program compiled with Emscripten?

I am trying to link a Rust library containing code generated by wasm-bindgen against a program written in C which I would like to compile with Emscripten. My MRE is as follows:
On the Rust side, I have Cargo.toml:
[package]
name = "rust_project"
version = "0.1.0"
edition = "2021"
[lib]
crate-type = ["staticlib"]
[dependencies]
wasm-bindgen="0.2"
and in lib.rs I have:
use wasm_bindgen::prelude::*;
#[wasm_bindgen]
extern "C" {
#[wasm_bindgen(js_namespace = console)]
fn log(s: &str);
}
#[no_mangle]
pub extern "C" fn call_from_c() {
log("Hello, World!");
}
As a first step, I compile this with cargo build --target wasm32-unknown-unknown which produces a librust_project.a. I then set up the following C project with main.c:
/* forward declare the function from Rust */
void call_from_c();
/* call the function from main */
int main() {
call_from_c();
return 0;
}
and CMakeLists.txt:
cmake_minimum_required(VERSION 3.5)
project(c_project)
add_executable(c_project main.c)
target_link_libraries(c_project /path/to/librust_project.a)
Finally, I attempt to put it all together using the Emscripten toolchain as follows:
cmake -DCMAKE_TOOLCHAIN_FILE=path/to/Emscripten.cmake ../
make
Which is where something appears to go wrong at the linking stage with emcc reporting that __wbg_log_941ab916ed5a24bd is an undefined symbol. I suspect that this symbol (among others) is being stripped out as part of an optimization effort but I am not sure at what stage or how I can disable this optimization.
Adding the following linker options in CMake results in compilation with a warning about the undefined symbol:
target_link_libraries(c_project
path/to/librust_project.a
"-s EXPORTED_FUNCTIONS=[\"_main\",\"___wbg_log_941ab916ed5a24bd\"]"
"-s ERROR_ON_UNDEFINED_SYMBOLS=0")
but I believe these missing symbols are problematic and when I run wasm-bindgen (the CLI tool) over c_project.wasm I get the following error:
import of `__wbg_log_941ab916ed5a24bd` doesn't have an adapter listed
How can I prevent the wasm-bindgen imported/exported functions from being stripped during this process?
This is a work-in-progress answer, which hopefully I will be able to turn into a complete answer in the near future. If not perhaps, it will at least serve as a starting point for others who have come down this path.
I have been working on making my MWE even more minimal by removing CMake and Emscripten and just compiling with Clang directly. This is sufficient since I don't need to worry about the standard library in this MWE.
My command for compiling then becomes:
clang -Wall --target=wasm32-unknown-unknown --no-standard-libraries \
-Wl,--export-all \
-Wl,--no-entry \
-Wl,-L/path/to/librust_project_a
-Wl,-lrust_project
-o main.wasm main.c
Worthwhile noting is that I can trigger the following wasm-bindgen error by adding/removing the --export-all linker argument. Suggesting that the LLVM linker was responsible for removing this section before it could be processed with wasm-bindgen.
import of `__wbg_log_941ab916ed5a24bd` doesn't have an adapter listed

How to call functions from a pre-compiled C library within Rust

Long story short, I want to call C code from Rust... I've seen crates like cc and cmake which (to my understanding, which might be wrong) can compile C code for use with the Rust language. Though I am capable of producing a compiled .lib file using the CLion IDE. Now I would like to simply link to said .lib file from Rust and just call the code within, is this possible? I have tried a few things, but they just do not seem to link at all...
project structure:
my_rust_project/
my_c_project/
cmake_build_debug/
...
my_c_project.lib
...
...
a_c_file.h
a_c_file.c
...
src/
main.rs
cargo.roml
build.rs
...
build.rs:
fn main() {
println!("cargo:rustc-link-search=my_c_project/cmake-build-debug/");
println!("cargo:rustc-link-lib=static=my_c_project/cmake-build-debug/my_c_project");
}
main.rs:
#[link(name = "my_c_project/cmake-build-debug/my_c_project", kind = "static")]
extern "C" {
fn hello();
}
fn main() {
unsafe {
//hello(); /* if commented the project compiles... */
}
}
Note how you could comment out any of the two prinln!'s in build.rs as well as the #link directive from main.rs in any combination you could think of (i.e. commenting out #link and leaving the println's, or commenting out one println and leaving the #link, and so on...) and the project will continue to compile, though as soon as you uncomment hello(); from fn main() in main.rs, the project will not compile and produce the following error:
LNK2019: unresolved external symbol hello referenced in function blah blah blah ...
I think there is two possible causes, either the library is not link at all, which seems to be the most logical issue to me, which raises the question: what exactly do the println!'s and #link do in this specific scenarion, they do not cause a panic! or error so they must definitly be doing something right? Whatever it is, it cannot be linking the project.
The other issue I can imagine is that there is in fact proper linking going on-ish? But Rust just doesn't understand where exactly to find the function hello within my_c_project.lib...
For clarity I will provide the contents of my_c_project:
a_c_file.h:
#ifndef MY_C_PROJECT_A_C_FILE_H
#define MY_C_PROJECT_A_C_FILE_H
void hello();
#endif //MY_C_PROJECT_A_C_FILE_H
a_c_file.c:
#include "a_c_file.h"
#include <stdio.h>
void hello() {
printf("Hello, World!\n");
}
I really want to stress that I want to stay away from cc and cmake (crates) since I have the .lib file ready to go... The question remains, how to call the C functions from a .lib file from within a Rust program?
Thanks for the help!
newly tried thing...
changed build.rs to:
fn main() {
println!("cargo:rustc-link-search=my_c_project/cmake-build-debug/");
println!("cargo:rustc-link-lib=static=my_c_project");
}
changed main.rs to:
extern {
fn hello();
}
fn main {
}
renamed my_c_project.lib to libmy_c_project.lib...
This still produces an error:
LINK : fatal error LNK1181: cannot open input file 'libmy_library.lib'
I guess it does not require a .a file per se as it says .lib in the message above.
If anyone ever has this problem... I solved this by continuing the project on Linux using cross compilers to generate the executable for windows.
The matter is to compile everything (source code and libraries/dependencies) for the target platform using the correct compilers ofcourse.

Why linking .cc file works in make file but linking .c file doesn't?

I'm working on a quite large Makefile from the tensorflow repo and I need to add a file link.
After quite some debugging of a link error, I found out that if my file ends with .cc, then the link error disappears, whereas when linking a .c file, the error appears (file content remains the same).
I am linking the file in a Makefile.inc file:
.
.
.
FL_SRCS := \
tensorflow/lite/vis_mi/main.cc \
myFunctions.c \ -->>>>IF I CHANGE THE FILENAME TO myFunctions.cc and link to this .cc file here, it works!!
.
.
.
# Builds a standalone binary.
$(eval $(call vis_test,vis_mi,\
$(FL_SRCS),$(FL_HDRS)))
The link error when using the .c ending ends with:
tensorflow/lite/vis_mi/main.o: In function `main':
tensorflow/lite/vis_mi/main.cc:183: undefined reference to `printMsg()'
../downloads/gcc_embedded/bin/../lib/gcc/arm-none-eabi/7.3.1/../../../../arm-none-eabi/bin/ld: link errors found, deleting executable `tensorflow/lite/vis_mi'
collect2: error: ld returned 1 exit status
gmake: *** [tensorflow/lite/vis_mi/Makefile.inc:578: tensorflow/lite/vis_mi/bin/micro_speech] Error 1
The .c file code:
#include <stdio.h>
#include "myFunctions.h"
void printMsg(){
//do something here
}
And the header file:
#ifndef MYFUNCTIONS_H
#define MYFUNCTIONS_H
void printMsg();
#endif /* MYFUNCTIONS_H */
How can I include a file ending with .c? Makefiles are fairly new to me, and didn't want to include all the details, and if you need any further details from the Makefile to answer this question I'm happy to edit my post.
The name resolution for C and C++ functions is different. This is sometimes causing problem to "C" code that is also valid "C++". For example:
int foo(int x) { return x+1 } ;
The code Will create a function called 'foo' when compiled as "C", and will create a function called '_Z3fooi' when compiled as C++. Decoding the C++ name will (using c++filt) will show foo(int). The reason for the difference is that C++ support polymorphic functions (and methods), so that function names also identify the type of their parameters.
The proper solution is to decide on the language of the code. C++ and C are different language, and while it's possible to write code that will be valid for both, it will limit the ability to leverage each function abilities.
Important constraint: If a project contain both C and C++ code, it is important to remember that only functions that follow the "C" conventions can be called between the languages. This is usually implemented with extern "C" directive, and with #ifdef __cplusplus:
See more: https://www.thegeekstuff.com/2013/01/mix-c-and-cpp/ How to call C++ function from C? Call a C function from C++ code

C code structure evokes linking cycles with CMake

I am trying to build bozorth3 from NIST Biometric Image Software using CMake but having troubles with the linking. This software has on the one side an executable:
//bin/bozorth3.c:
int min_comp_minutiae = MIN_COMP_BOZORTH_MINUTIAE; // defines what libbozorth needs
//...
getopt_spec = malloc_or_exit( (int)strlen(default_getopt_spec) + 1,
"getopt() string" ); // uses a function from libbozorth
...on the other side a library:
//lib/bozorth3.c:
if ( pstruct->nrows < min_computable_minutiae ) { //uses a variable defined in bin
//lib/bz_alloc.c:
char * malloc_or_exit( int nbytes,
const char * what ) { // implements a function used in bin
//code
}
...and a common header:
//include/bozorth3.h
extern int min_comp_minutiae;
extern char *malloc_or_exit(int, const char *);
With the following filesets (exceprt from CMakeLists.txt)
set(LIB_SOURCE_FILES
src/lib/bozorth3/bozorth3.c
src/lib/bozorth3/bz_alloc.c #[[etc...]])
add_library(libbozorth3 ${LIB_SOURCE_FILES})
add_executable(bozorth3 src/bin/bozorth3/bozorth3.c)
Since the executable uses the library I have to add a link: target_link_libraries(bozorth3 libbozorth3). But this is results in an error:
Linking C shared library ..\..\bin\liblibbozorth3.dll
CMakeFiles\libbozorth3.dir/objects.a(bozorth3.c.obj):bozorth3.c:(.rdata$.refptr.min_computable_minutiae[.refptr.min_computable_minutiae]+0x0): undefined reference to `min_computable_minutiae'
It forces me to create a link vice-versa: target_link_libraries(libbozorth3 bozorth3) and overriding ENABLE_EXPORTS, which of course results in an error as well:
Linking C executable ..\..\bin\bozorth3.exe
CMakeFiles\bozorth3.dir/objects.a(bozorth3.c.obj): In function `main':
D:/git/ba-phmf/NBIS/bozorth3/src/bin/bozorth3/bozorth3.c:174: undefined reference to `malloc_or_exit'
And I cant have both because it obviously results in a cycle:
CMake Error: The inter-target dependency graph contains the following strongly connected component (cycle):
"libbozorth3" of type SHARED_LIBRARY
depends on "bozorth3" (weak)
"bozorth3" of type EXECUTABLE
depends on "libbozorth3" (weak)
At least one of these targets is not a STATIC_LIBRARY. Cyclic dependencies are allowed only among static libraries.
I can compile the package using the original makefiles but need to "translate" it into CMake in order to have integration into my project. I tried to analyze the makefiles but couldn't find a solution. The whole package can be found at github.
I am using CLion 2016.2.3, CMake 3.7.0-rc2, Msys2 20160921 with GCC 6.2.0 and ld 2.27 on Windows 7 Professional N x64
Solved it like this:
add_library(libbozorth3 STATIC ${LIB_SOURCE_FILES})
set_property(TARGET libbozorth3 PROPERTY OUTPUT_NAME bozorth3)
target_include_directories(libbozorth3 PRIVATE
include)
add_executable(bozorth3 ${BIN_SOURCE_FILEs})
target_include_directories(bozorth3 PRIVATE
include)
target_link_libraries(bozorth3
PRIVATE
libbozorth3
commonnbis)

Linking to a DLL I created

I am trying to create my own DLL and then make another project load it statically.
My DLL file contains both a header file (called HelloFunc.h):
#include <stdio.h>
extern "C"
{
_declspec(dllexport) void HelloFromDll();
}
And a c file (called HelloFunc.cpp):
#include <stdio.h>
extern "C"
{
_declspec(dllexport) void HelloFromDll()
{
printf("Hello DLL. \n");
}
}
After building the project an Object File Library (.lib) was created.
Then, on my other project I tried to link to it statically.
In linker -> Input -> Additional Dependencies I added my library (I put it in my new project's directory) and then in linker -> Input -> Command Line I saw that it actually linked to it.
However, when I tried to call HelloFromDll() function in my new code, an error says that it is not identified. Note that I also included "HelloFunc.h" but an error says that the source file couldn't be opened.
I'm a little lost and don't know what I've done wrong. Any help will be appreciated :)
You must specify __declspec(dllimport) instead of __declspec(dllexport) when importing a library.
What error message did you received exactly?
[Edited]
When you compile a DLL, you specify __declspec(dllexport). When you compile an application that imports the DLL, you specify __declspec(dllimport).
The problem is that the compiler cannot find HelloFunc.h: Simply copy HelloFunc.h into your new project's directory.
Check that you client & library were compiled in the same mode (debug or release).
This is common bottleneck.

Resources