How to use a compiled C .so file in rust - c

has updated because of some suggestions
System:macOS 10.14.6
The question I want to ask here is how do I use rust to call a compiled .so file, sorry, I am new to this part.
I have a very simple c file:
#include "add.h"
int add(int a, int b) {
return a + b;
}
Then I used gcc-fPIC -shared -o libadd.so add.c to compile it into a .so file and put it in the lib directory
Then I wrote this in rust's build.rs file:
use std::env;
use std::path::{Path};
fn main() {
let pwd_dir = env::var("CARGO_MANIFEST_DIR").unwrap();
let path = Path::new(&*pwd_dir).join("lib");
println!("cargo:rustc-link-search=native={}", path.to_str().unwrap());
println!("cargo:rustc-link-lib=dylib=add");
// println!("cargo:rustc-link-lib=static=add");
// println!("cargo:rerun-if-changed=src/hello.c");
}
I expect I can get and use this function, main.rs is:
extern { fn add(a: i32, b: i32) -> i32; }
fn main() {
let c = unsafe { let d = add(3, 5); d };
println!("c: {:?}", c);
}
cargo build is ok, but cargo run with error:
Compiling hello-from-generated-code-3 v0.1.0 (/Users/niexiaotao/work/rust-server/rust-ffi/hello-from-generated-code-3)
Finished dev [unoptimized + debuginfo] target(s) in 0.34s
Running `target/debug/hello-from-generated-code-3`
dyld: Library not loaded: libadd.so
Referenced from: /Users/niexiaotao/work/rust-server/rust-ffi/hello-from-generated-code-3/target/debug/hello-from-generated-code-3
Reason: image not found
[1] 81811 abort cargo run
other thing: I change the .so to .a and cargo run is ok.
Sample code here
Thank you for your help!

I ran into the same problem. It seems that Cargo can properly build your library because you're explicitly telling it where to look for the file (using rustc-link-search). However, when you go to run it, Linux doesn't know where it is.
If you were to run ldd on your compiled Rust binary, you'd get something like this:
$ ldd target/debug/hello-from-generated-code-3
linux-vdso.so.1 (0xabcd)
libadd.so => not found <----- ldd can't find your library
...
This is because your .so file isn't found in LD_LIBRARY_PATH. You can fix this either by copying your library into a relevant folder or just setting it when you run your program. For example, you should be able to say:
LD_LIBRARY_PATH=. cargo run
(Or any other path where your .so file is - not necessarily .)

I didn`t use env variables at all. For instance you have /your-crate/lib/libmystuff.so shared library. In order to link it, specify the following:
println!("cargo:rustc-link-search=native=./lib");
println!("cargo:rustc-link-lib=dylib=mystuff");
Pay attention, library without prefix lib, and path is relative.
And just in case. You can also define include folder with your header files:
let headers = Path::new("/headers");
cc::Build::new()
.file("src/foo.c")
.include(headers)
.compile("foo");
Tested on Linux. Best regards.

Related

Modelica external C function call to DLL results in exit with code 0xffffffffc0000135

In short, my question is: How do I build a DLL in Windows 11 using mingw-gcc that can be used as an external function for a Modelica simulation in OpenModelica?
I am able to get the simulation to compile and link, but as soon as it starts execution, the program returns exit code 0xFFFFFFFFC0000135, which I believe is caused by a problem with the DLL.
I have the following directory structure for the simulation and other files:
root
L ExternalFunctionTest.mo (model)
L Resources (folder)
L Include (folder)
L myExtLib.h (external function declaration)
L Library (folder)
L libMyExtLib.dll (build output for myExtLib.c)
L Src (folder)
L myExtLib.c (external function definition)
I have built the DLL using GCC in Windows 11 using gcc (mingw64, Rev5, Built by MSYS2 project 10.2.0) command:
gcc -fPIC -shared -o Resources/Library/libMyExtLib.dll Resources/Src/myExtLib.c
I have also tried adding the options -falign-functions -mstackrealign -msse2 -mfpmath=sse, which I noticed that OpenModelica uses when compiling its own source files, as well as adding -m64. I have also tried switching to use the same compiler used by OpenModelica in its tools/mingw64 subdirectory to build the DLL.
When I run the simulation, everything compiles and links properly, but the simulation crashes immediately with the message:
C:/ ... /modelica_workspace/ExternalFunctionTest.TestModel/TestModel.exe -port=49874 -logFormat=xmltcp -override=startTime=0,stopTime=1,stepSize=0.002,tolerance=1e-6,solver=dassl,outputFormat=mat,variableFilter=.* -r=C:/ ... /modelica_workspace/ExternalFunctionTest.TestModel/TestModel_res.mat -w -lv=LOG_STATS -inputPath=C:/ ... /modelica_workspace/ExternalFunctionTest.TestModel -outputPath=C:/ ... /modelica_workspace/ExternalFunctionTest.TestModel
Process crashed
Process crashed
Simulation process failed. Exited with code 0xffffffffc0000135.
A colleague was able to get everything to work in Linux using the following command to build a .so DLL:
gcc -Wall -fPIC -shared -o Resources/Library/libmyFunction.so Resources/Src/myFunction.c
I tried this using both Modelica v1.18.0 and v1.18.1 (64-bit).
If anyone can shed some light it would be greatly appreciated!
The contents of ExternalFunctionTest.mo:
package ExternalFunctionTest
model TestModel
Real x(start=1);
Real y(start=2);
equation
der(x) = 1;
y = testFunction(x);
end TestModel;
function testFunction
input Real x;
output Real y;
external "C" extFunction(x, y) annotation(Library="libMyExtLib", Include="#include \"myExtLib.h\"");
end testFunction;
end ExternalFunctionTest;
The contents of myExtLib.c:
#include "../Include/myExtLib.h"
void extFunction(const double input, double * const output)
{
*output = 2 * input;
}
The contents of myExtLib.h:
#ifndef MY_EXT_LIB_H__
#define MY_EXT_LIB_H__
void extFunction(const double input, double * const output);
#endif
Follow up:
The problem was related to an issue in OpenModelica version 1.18 where the DLL was not being added to the path prior to executing the simulation, as noted in the comments by Adrian Pop. By upgrading to the latest stable development release, 1.19.0, the problem resolved itself.

"Compiling" go project as C shared object library on AIX 7.2 results in Executable that doesn't run

EDIT: For any poor soul that finds this, in search of a solution for the shared library from go conundrum: I was unable to find a solution that uses go and I would suggest, that until google go provides native c-shared support for AIX you should find an alternative for your project.
I did not go forward with gccgo because that felt like an entirely different can of worms that I was unwilling to delve further into. FWIW I myself am going forward switching to pure C implementation, because there I at least have a (somewhat) firm(er) understanding of it all.
Should anyone find a solution I'd love to hear from you and see how you got around this limitation.
Environment:
AIX 7.2
go1.16.12 aix/ppc64
gcc-8
I want to create a C shared object library (in usual unix vernacular a .so file) out of a golang project on AIX 7.2 so that it can be used by a C application.
I can compile it down to a final a.out binary in my example, but it can then not be executed because the shared object is apparently compiled the wrong way.
So far I have achieved the following:
Suppose my example go "library" sharedLibTest.go:
package main
import (
m "fmt"
)
import "C"
func main() {
fmt.Printf("%s\n", "Golang: main was called")
MyPackage_Init()
MyPackage_Create()
}
//export MyPackage_Init
func MyPackage_Init() {
fmt.Printf("%s\n", "Golang: MyPackage_Init was called")
}
//export MyPackage_Create
func MyPackage_Create() {
fmt.Printf("%s\n", "Golang: MyPackage_Create was called")
}
And some C application that calls these functions in main.c:
#include <stdio.h>
#include "sharedLibTest.h"
int main() {
printf("%s\n", "C: main() called");
MyPackage_Init();
MyPackage_Create();
}
m
Now, because AIX feels the need to do things differently the current golang toolchain does not support directly creating a c-shared object with -buildmode=c-shared. Instead I am trying to do the roundabout way by first creating a static lib with -buildmode=c-archive, compiling that into a shared object using gcc-8 and use that in my "target C application".
I can compile sharedLibTest.go this with
go build -v -buildmode=c-archive -mod vendor -o /home/myuser/workspace/go_proj/sharedLibTest/sharedLibTest.a /home/myuser/workspace/go_proj/sharedLibTest/sharedLibTest.go
Because the symbols MyPackage_Init and MyPackage_Create are not exported by default in AIX, I need to manually create an extra symbol file with
$ cat > file.exp << EOF
> MyPackage_Init
> MyPackage_Create
> EOF
Source
(If there are any ideas how i can omit this file.exp step I'd really appreciate it)
Now with that I can compile a shared object out of that by running
gcc -g -O2 -mcpu=power7 -maix64 -shared -lpthread -Wl,-bE:file.exp -o libsharedLibTest.so -Wl,-bnoobjreorder ./sharedLibTest.a
Now because AIX does not look for .so files but only .a files even if they are shared libraries, I rename the resulting libsharedLibTest.so into libsharedLibTest.a with
mv libsharedLibTest.so libsharedLibTest.a
Lastly I want to compile my C applications with
gcc -L/home/myuser/workspace/go_proj/sharedLibTest -g -O2 -mcpu=power7 -maix64 -Wl,-bnoobjreorder -lsharedLibTest -lpthreads main.c
This succeeds and I get my a.out file as a result.
However, when I try to run this with the following, I only get the error below
LD_LIBRARY_PATH=/home/myuser/workspace/go_proj/sharedLibTest ./a.out
$ ./a.out
exec(): 0509-036 Cannot load program ./a.out because of the following errors:
0509-150 Dependent module /home/myuser/workspace/go_proj/sharedLibTest/libsharedLibTest.a(libsharedLibTest.so) could not be loaded.
0509-187 The local-exec model was used for thread-local
storage, but the module is not the main program.
0509-193 Examine the .loader section header with the
'dump -Hv' command.
Some hours of googling so far have revealed that I might be missing the compile option -fPIC to create "emit position-independent code" however adding that flag to any of the above steps in various combinations has all resulted in the same error.
Clearly I need to add some compile option to tell the shared object not to be thread-local, however I am unclear how. Any ideas?
Few points... mv will not make an archieve, ar will. You need to use ar command to create .a file.
Second, use LIBPATH environment variable in place of LD_LIBRARY_PATH. Use of -fPIC option is irrelevant on AIX.

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

Resolving symbols colision at build time

I have some symbols collision in a C program, some previous search leads to this objcopy the problem is my workflow is a golang cgo one so I don't deal myself with the .o and .a (I know I could be the goal of my lib is to be used by other people so I can't have a custom golang workflow.).
More info of what I need:
I have a bunch of functions doing various things in my go lib, this code is autogenerated and can't be predicted, they sometimes collide (have the same name) with other function later in the build pipeline, so I would like all the C function in my go lib to be renamed, this can either happend at build time using the standart cgo process (basicaly build each file first with gcc and then link them all up) or after the autogeneration of the code (I guess I could run a preprocessor renaming all the functions and there calls in the source but I weren't able to find one).
What I've tried already :
#pragma extern_prefix
This and this seems very promising but whatever I try I can't get it to works :
// test.c
#include <stdio.h>
#pragma extern_prefix "TestPrefix"
int test() {
printf("Hello, World!\n");
return 0;
}
#pragma extern_prefix ""
int main() {
return test();
}
Shell output :
$ gcc test.c -o test && ./test && nm -an test | grep test
Hello, World!
0000000000000000 a test.c
0000000000001149 T test
Unlike what I expected the test symbol isn't prefixed like I expect (with my understanding the symbol should be TestPrefixtest).

How integrate gnatmake/gnatbind/gnatlink in CMake files for C/Ada code?

I wrote a code in a few languages (C, C++, Fortran77, Fortran90) and I can compile it without any sort of problem by using CMake. It works out perfectly.
Now, I would like to add in the main(), which is written in C, some Ada function and I want to compile it by CMake. Given that I am not able to link my Ada function to the main one by using CMake, I get
main.c:(.text.startup+0x16a): undefined reference to adainit
main.c:(.text.startup+0x179): undefined reference to adafunction
main.c:(.text.startup+0x190): undefined reference to adafinal
I did another simplified test by using the main function (written in C) calling the only Ada function, which I coded, and I compiled it by using
gcc -c main.c
gnatmake -c lib_ada.ali
gnatbind -n lib_ada.ali
gnatlink lib_ada.ali main.o -o exe
and it works out. Do you know how I can integrate this approach in a CMakeList.txt?
Note: I think (maybe I mistake) I cannot use the only gnatlink because I need to link all other functions I already have.
Here is reported a minimal reproducible example.
--- main.c ---
#include <stdio.h>
extern int adainit();
extern int adafinal();
extern int Add(int,int);
int main()
{
adainit();
printf ("Sum of 3 and 4 is: %d\n", Add (3,4));
adafinal();
return 0;
}
--- lib_test.adb ---
package body Lib_Test is
function Ada_Add (A, B : Integer) return Integer is
begin
return A + B;
end Ada_Add;
end Lib_Test;
--- lib_test.ads ---
package Lib_Test is
function Ada_Add (A, B : Integer) return Integer;
pragma Export (C, Ada_Add, "Add");
end Lib_Test;
1° test: if you compile by using the following commands:
gcc -c main.c
gnatmake -c lib_test.adb
gnatbind -n lib_test.ali
gnatlink lib_test.ali main.o -o exe
and run ./exe you get Sum of 3 and 4 is: 7.
2° test: I tried to use the following CMake file (CMakeLists.txt) linking the *.a
cmake_minimum_required(VERSION 2.6)
project(Ada2C)
enable_language(C)
set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}/cmake")
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin)
set(CMAKE_VERBOSE_MAKEFILE ON)
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -O3 -m64")
find_library(TEST_lib lib_test.a PATHS ${CMAKE_CURRENT_SOURCE_DIR})
message(STATUS "Finding library: ${TEST_lib}")
add_executable(TEST_release ${CMAKE_CURRENT_SOURCE_DIR}/main.c)
target_link_libraries(TEST_release ${TEST_lib})
I generate library lib_test.a for the Ada function
gnatmake lib_test.adb
ar rc lib_test.a
I run the cmake and make and I get
main.c:(.text.startup+0x16a): undefined reference to adainit
main.c:(.text.startup+0x179): undefined reference to adafunction
main.c:(.text.startup+0x190): undefined reference to adafinal
More of a comment than an answer, but too long for a comment, so here goes:
Compiling Ada code into your binary means that your binary needs access to the GNAT runtime. This is one thing gnatlink does when you use it to link the final executable. The other thing is the b~<something>.ad{s,b} source gnatbind generates which you need to compile and link against as others mentioned.
The cleanest way to embed Ada in C I've seen so far is to create an encapsulated library. This probably does not make sense if your actual problem is with only one Ada function, but it does with larger chunks of Ada. The encapsulated library will be a shared library that has GNAT's runtime baked in. Being a shared library enables it to implicitly handle initialization during library loading so you don't need adainit() / adafinal() anymore.
The easiest way to create an encapsulated library is to use a ada_code.gpr file:
project ada_code is
for Library_Name use "mylib";
for Library_Dir use "lib";
for Library_Kind use "relocatable";
for Library_Standalone use "encapsulated";
for Library_Auto_Init use "true";
for Library_Interface use ("All", "Packages", "In.Your", "Ada.Code");
for Source_Dirs use ("adasrc");
end ada_code;
In CMake, you can then do:
# tell CMake how to call `gprbuild` on the `.gpr` file.
# you may need to replace `gprbuild` with the absolute path to it
# or write code that finds it on your system.
add_custom_target(compile_mylib
COMMAND gprbuild -P ada_code.gpr)
# copy the library file generated by gprbuild to CMake's build tree
# (you may skip this and just link against the file in the source tree)
add_custom_command(
OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/mylib.so
DEPENDS compile_mylib
COMMAND ${CMAKE_COMMAND} -E copy
${CMAKE_SOURCE_DIR}/lib/mylib.so
${CMAKE_CURRENT_BINARY_DIR}/mylib.so)
# ... snip ...
# link to the copied library
# I am not 100% sure this adds the correct dependency to the custom command.
# You may need to experiment a bit yourself
target_link_libraries(TEST_release ${CMAKE_CURRENT_BINARY_DIR}/mylib.so)
In your C file, you can then delete everything related to adainit() and adafinal().

Resources