From 27afb6247b356bf0a147b2985de42deb66c883f0 Mon Sep 17 00:00:00 2001 From: "Christoph J. Scherr" Date: Tue, 12 Sep 2023 18:21:59 +0200 Subject: [PATCH] c-binding demo --- Cargo.lock | 25 +++ members/c-bindings/Cargo.toml | 6 + members/c-bindings/lib/CMakeLists.txt | 2 +- members/c-bindings/lib/Makefile | 230 -------------------------- members/c-bindings/lib/test.c | 12 ++ members/c-bindings/lib/test.h | 5 + members/c-bindings/src/build.rs | 12 ++ members/c-bindings/src/main.rs | 43 ++++- 8 files changed, 102 insertions(+), 233 deletions(-) delete mode 100644 members/c-bindings/lib/Makefile create mode 100644 members/c-bindings/src/build.rs diff --git a/Cargo.lock b/Cargo.lock index 23569ca..1196d1f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5,6 +5,31 @@ version = 3 [[package]] name = "c-bindings" version = "0.1.0" +dependencies = [ + "cc", + "cty", +] + +[[package]] +name = "cc" +version = "1.0.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0" +dependencies = [ + "libc", +] + +[[package]] +name = "cty" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b365fabc795046672053e29c954733ec3b05e4be654ab130fe8f1f94d7051f35" + +[[package]] +name = "libc" +version = "0.2.147" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3" [[package]] name = "rs-basic" diff --git a/members/c-bindings/Cargo.toml b/members/c-bindings/Cargo.toml index f10ea2e..23a9a38 100644 --- a/members/c-bindings/Cargo.toml +++ b/members/c-bindings/Cargo.toml @@ -2,7 +2,13 @@ name = "c-bindings" version = "0.1.0" edition = "2021" +links = "test" # this is the important part! cargo will search for some library called `test` (i.e. libtest.a) +build = "src/build.rs" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +cty = "0.2.2" + +[build-dependencies] +cc = "1.0.83" diff --git a/members/c-bindings/lib/CMakeLists.txt b/members/c-bindings/lib/CMakeLists.txt index 634d518..66e5a57 100644 --- a/members/c-bindings/lib/CMakeLists.txt +++ b/members/c-bindings/lib/CMakeLists.txt @@ -1,7 +1,7 @@ cmake_minimum_required(VERSION 3.9) project(test VERSION 1.0.1 DESCRIPTION "test lib") include(GNUInstallDirs) -add_library(test SHARED test.c) +add_library(test STATIC test.c) set_target_properties(test PROPERTIES VERSION ${PROJECT_VERSION} SOVERSION 1 diff --git a/members/c-bindings/lib/Makefile b/members/c-bindings/lib/Makefile deleted file mode 100644 index aae36f6..0000000 --- a/members/c-bindings/lib/Makefile +++ /dev/null @@ -1,230 +0,0 @@ -# CMAKE generated file: DO NOT EDIT! -# Generated by "Unix Makefiles" Generator, CMake Version 3.22 - -# Default target executed when no arguments are given to make. -default_target: all -.PHONY : default_target - -# Allow only one "make -f Makefile2" at a time, but pass parallelism. -.NOTPARALLEL: - -#============================================================================= -# Special targets provided by cmake. - -# Disable implicit rules so canonical targets will work. -.SUFFIXES: - -# Disable VCS-based implicit rules. -% : %,v - -# Disable VCS-based implicit rules. -% : RCS/% - -# Disable VCS-based implicit rules. -% : RCS/%,v - -# Disable VCS-based implicit rules. -% : SCCS/s.% - -# Disable VCS-based implicit rules. -% : s.% - -.SUFFIXES: .hpux_make_needs_suffix_list - -# Command-line flag to silence nested $(MAKE). -$(VERBOSE)MAKESILENT = -s - -#Suppress display of executed commands. -$(VERBOSE).SILENT: - -# A target that is always out of date. -cmake_force: -.PHONY : cmake_force - -#============================================================================= -# Set environment variables for the build. - -# The shell in which to execute make rules. -SHELL = /bin/sh - -# The CMake executable. -CMAKE_COMMAND = /usr/bin/cmake - -# The command to remove a file. -RM = /usr/bin/cmake -E rm -f - -# Escaping for special characters. -EQUALS = = - -# The top-level source directory on which CMake was run. -CMAKE_SOURCE_DIR = /home/cscherr/Documents/code/rust/rs-basic/members/c-bindings/lib - -# The top-level build directory on which CMake was run. -CMAKE_BINARY_DIR = /home/cscherr/Documents/code/rust/rs-basic/members/c-bindings/lib - -#============================================================================= -# Targets provided globally by CMake. - -# Special rule for the target edit_cache -edit_cache: - @$(CMAKE_COMMAND) -E cmake_echo_color --switch=$(COLOR) --cyan "No interactive CMake dialog available..." - /usr/bin/cmake -E echo No\ interactive\ CMake\ dialog\ available. -.PHONY : edit_cache - -# Special rule for the target edit_cache -edit_cache/fast: edit_cache -.PHONY : edit_cache/fast - -# Special rule for the target rebuild_cache -rebuild_cache: - @$(CMAKE_COMMAND) -E cmake_echo_color --switch=$(COLOR) --cyan "Running CMake to regenerate build system..." - /usr/bin/cmake --regenerate-during-build -S$(CMAKE_SOURCE_DIR) -B$(CMAKE_BINARY_DIR) -.PHONY : rebuild_cache - -# Special rule for the target rebuild_cache -rebuild_cache/fast: rebuild_cache -.PHONY : rebuild_cache/fast - -# Special rule for the target list_install_components -list_install_components: - @$(CMAKE_COMMAND) -E cmake_echo_color --switch=$(COLOR) --cyan "Available install components are: \"Unspecified\"" -.PHONY : list_install_components - -# Special rule for the target list_install_components -list_install_components/fast: list_install_components -.PHONY : list_install_components/fast - -# Special rule for the target install -install: preinstall - @$(CMAKE_COMMAND) -E cmake_echo_color --switch=$(COLOR) --cyan "Install the project..." - /usr/bin/cmake -P cmake_install.cmake -.PHONY : install - -# Special rule for the target install -install/fast: preinstall/fast - @$(CMAKE_COMMAND) -E cmake_echo_color --switch=$(COLOR) --cyan "Install the project..." - /usr/bin/cmake -P cmake_install.cmake -.PHONY : install/fast - -# Special rule for the target install/local -install/local: preinstall - @$(CMAKE_COMMAND) -E cmake_echo_color --switch=$(COLOR) --cyan "Installing only the local directory..." - /usr/bin/cmake -DCMAKE_INSTALL_LOCAL_ONLY=1 -P cmake_install.cmake -.PHONY : install/local - -# Special rule for the target install/local -install/local/fast: preinstall/fast - @$(CMAKE_COMMAND) -E cmake_echo_color --switch=$(COLOR) --cyan "Installing only the local directory..." - /usr/bin/cmake -DCMAKE_INSTALL_LOCAL_ONLY=1 -P cmake_install.cmake -.PHONY : install/local/fast - -# Special rule for the target install/strip -install/strip: preinstall - @$(CMAKE_COMMAND) -E cmake_echo_color --switch=$(COLOR) --cyan "Installing the project stripped..." - /usr/bin/cmake -DCMAKE_INSTALL_DO_STRIP=1 -P cmake_install.cmake -.PHONY : install/strip - -# Special rule for the target install/strip -install/strip/fast: preinstall/fast - @$(CMAKE_COMMAND) -E cmake_echo_color --switch=$(COLOR) --cyan "Installing the project stripped..." - /usr/bin/cmake -DCMAKE_INSTALL_DO_STRIP=1 -P cmake_install.cmake -.PHONY : install/strip/fast - -# The main all target -all: cmake_check_build_system - $(CMAKE_COMMAND) -E cmake_progress_start /home/cscherr/Documents/code/rust/rs-basic/members/c-bindings/lib/CMakeFiles /home/cscherr/Documents/code/rust/rs-basic/members/c-bindings/lib//CMakeFiles/progress.marks - $(MAKE) $(MAKESILENT) -f CMakeFiles/Makefile2 all - $(CMAKE_COMMAND) -E cmake_progress_start /home/cscherr/Documents/code/rust/rs-basic/members/c-bindings/lib/CMakeFiles 0 -.PHONY : all - -# The main clean target -clean: - $(MAKE) $(MAKESILENT) -f CMakeFiles/Makefile2 clean -.PHONY : clean - -# The main clean target -clean/fast: clean -.PHONY : clean/fast - -# Prepare targets for installation. -preinstall: all - $(MAKE) $(MAKESILENT) -f CMakeFiles/Makefile2 preinstall -.PHONY : preinstall - -# Prepare targets for installation. -preinstall/fast: - $(MAKE) $(MAKESILENT) -f CMakeFiles/Makefile2 preinstall -.PHONY : preinstall/fast - -# clear depends -depend: - $(CMAKE_COMMAND) -S$(CMAKE_SOURCE_DIR) -B$(CMAKE_BINARY_DIR) --check-build-system CMakeFiles/Makefile.cmake 1 -.PHONY : depend - -#============================================================================= -# Target rules for targets named test - -# Build rule for target. -test: cmake_check_build_system - $(MAKE) $(MAKESILENT) -f CMakeFiles/Makefile2 test -.PHONY : test - -# fast build rule for target. -test/fast: - $(MAKE) $(MAKESILENT) -f CMakeFiles/test.dir/build.make CMakeFiles/test.dir/build -.PHONY : test/fast - -test.o: test.c.o -.PHONY : test.o - -# target to build an object file -test.c.o: - $(MAKE) $(MAKESILENT) -f CMakeFiles/test.dir/build.make CMakeFiles/test.dir/test.c.o -.PHONY : test.c.o - -test.i: test.c.i -.PHONY : test.i - -# target to preprocess a source file -test.c.i: - $(MAKE) $(MAKESILENT) -f CMakeFiles/test.dir/build.make CMakeFiles/test.dir/test.c.i -.PHONY : test.c.i - -test.s: test.c.s -.PHONY : test.s - -# target to generate assembly for a file -test.c.s: - $(MAKE) $(MAKESILENT) -f CMakeFiles/test.dir/build.make CMakeFiles/test.dir/test.c.s -.PHONY : test.c.s - -# Help Target -help: - @echo "The following are some of the valid targets for this Makefile:" - @echo "... all (the default if no target is provided)" - @echo "... clean" - @echo "... depend" - @echo "... edit_cache" - @echo "... install" - @echo "... install/local" - @echo "... install/strip" - @echo "... list_install_components" - @echo "... rebuild_cache" - @echo "... test" - @echo "... test.o" - @echo "... test.i" - @echo "... test.s" -.PHONY : help - - - -#============================================================================= -# Special targets to cleanup operation of make. - -# Special rule to run CMake to check the build system integrity. -# No rule that depends on this can have commands that come from listfiles -# because they might be regenerated. -cmake_check_build_system: - $(CMAKE_COMMAND) -S$(CMAKE_SOURCE_DIR) -B$(CMAKE_BINARY_DIR) --check-build-system CMakeFiles/Makefile.cmake 0 -.PHONY : cmake_check_build_system - diff --git a/members/c-bindings/lib/test.c b/members/c-bindings/lib/test.c index 511b773..e7eae97 100644 --- a/members/c-bindings/lib/test.c +++ b/members/c-bindings/lib/test.c @@ -1,5 +1,17 @@ #include "test.h" +#include +#include +#include int ret19() { return 19; } + +char* structInfo(MyStruct *st) { + char* buf = malloc(1024*sizeof(char)); + /* snprintf(buf, sizeof(buf), "foo: %d\n", st->foo); */ + /* snprintf(buf, sizeof(buf), "bar: %c", st->bar); */ + sprintf(buf,"Infos about the struct:\nfoo:\t%d\nbar:\t%c\n\ngreetings from C", st->foo, st->bar); + + return buf; +} diff --git a/members/c-bindings/lib/test.h b/members/c-bindings/lib/test.h index c771912..b669d58 100644 --- a/members/c-bindings/lib/test.h +++ b/members/c-bindings/lib/test.h @@ -1 +1,6 @@ int ret19(); +typedef struct MyStruct { + int foo; + char bar; +} MyStruct; +char* stuctInfo(MyStruct st); diff --git a/members/c-bindings/src/build.rs b/members/c-bindings/src/build.rs new file mode 100644 index 0000000..fb5d0c4 --- /dev/null +++ b/members/c-bindings/src/build.rs @@ -0,0 +1,12 @@ +/// +/// we could simply do this to compile the test lib to a static lib, +/// but that solution might not scale as good as calling a professional +/// build system +#[allow(unused)] +fn main() { + // Tell Cargo that if the given file changes, to rerun this build script. + println!("cargo:rerun-if-changed=src/hello.c"); + cc::Build::new() + .file("lib/test.c") + .compile("test"); +} diff --git a/members/c-bindings/src/main.rs b/members/c-bindings/src/main.rs index e7a11a9..50c9968 100644 --- a/members/c-bindings/src/main.rs +++ b/members/c-bindings/src/main.rs @@ -1,3 +1,42 @@ -fn main() { - println!("Hello, world!"); +use cty; + +// we first need to declare bindings for our C code. +// This can be automated too, but I did it myself here. +#[derive(Debug)] +#[repr(C)] +pub struct MyStruct { + pub foo: cty::c_int, + pub bar: cty::c_char, +} + +// The functions too of course +extern "C" { + pub fn ret19() -> isize; + pub fn structInfo(st: &MyStruct) -> *const cty::c_char; +} + +/////////////////////////////////////////////////////////////////////// + +fn main() { + // calling random C functions is generally treated as unsafe. + // Best practice is programming wrapper functions that give it + // confirmed safe inputs + let qux = unsafe { ret19() }; + println!("`ret19()` returned {qux}"); + let st = MyStruct { + foo: 17, bar: 0x41 + }; + // converting a c "string" to a real rust String is + // a bit complicated + let info: &str = unsafe { + // convert the returned value to a rust internal CStr. + std::ffi::CStr::from_ptr( + // the function returns a pointer to a array of chars on the heap + structInfo(&st) + ) + // now to a string slice (result) + .to_str() + .unwrap() + }; + println!("{info}"); }