Skip to content

Commit fa73df8

Browse files
authored
Merge pull request #9 from midassystems/c_bindings
C bindings
2 parents 5c1200c + 4d70834 commit fa73df8

File tree

17 files changed

+1014
-8
lines changed

17 files changed

+1014
-8
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ __pycache__/
77

88
# C extensions
99
*.so
10+
*.bin
1011

1112
# Distribution / packaging
1213
.Python

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
members = [
33
"rust",
44
"python",
5-
# "mbn_c",
5+
"c",
66
]
77
resolver = "2"
88

c/CMakeLists.txt

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
cmake_minimum_required(VERSION 3.14)
2+
project(mbn_c VERSION 1.0 LANGUAGES CXX)
3+
4+
# Path to vcpkg toolchain
5+
set(CMAKE_TOOLCHAIN_FILE "${CMAKE_SOURCE_DIR}/vcpkg_installed/scripts/buildsystems/vcpkg.cmake")
6+
7+
# Use modern C++ standards
8+
set(CMAKE_CXX_STANDARD 20)
9+
set(CMAKE_CXX_STANDARD_REQUIRED True)
10+
set(CMAKE_OSX_DEPLOYMENT_TARGET "13.3")
11+
12+
13+
enable_testing()
14+
find_package(GTest CONFIG REQUIRED)
15+
16+
# Path to the cbindgen-generated header
17+
set(CBINDGEN_HEADER_DIR "${CMAKE_SOURCE_DIR}/../target")
18+
19+
# Rust binary
20+
set(RUST_PROJECT_DIR "${CMAKE_SOURCE_DIR}/..")
21+
set(RUST_LIB_DIR "${RUST_PROJECT_DIR}/target/debug")
22+
set(RUST_LIB "${RUST_LIB_DIR}/libmbn_c.a")
23+
24+
# Add a custom command to build Rust library using Cargo
25+
add_custom_command(
26+
OUTPUT "${RUST_LIB}"
27+
COMMAND cargo build --manifest-path "${RUST_PROJECT_DIR}/Cargo.toml"
28+
WORKING_DIRECTORY "${RUST_PROJECT_DIR}"
29+
COMMENT "Building Rust library with Cargo"
30+
VERBATIM
31+
)
32+
33+
# Add a custom target to ensure Rust builds before the C++ tests
34+
add_custom_target(build_rust ALL DEPENDS "${RUST_LIB}")
35+
36+
37+
# Test executable
38+
add_executable(MbnTests
39+
tests/test_decode_c.cpp
40+
tests/test_encode_c.cpp
41+
tests/test_records.cpp
42+
)
43+
44+
# Include the cbindgen header directory
45+
target_include_directories(MbnTests PRIVATE
46+
"${CBINDGEN_HEADER_DIR}" # Include the directory containing mbn.h
47+
"${RUST_LIB_DIR}"
48+
)
49+
50+
# Link the Rust library
51+
target_link_libraries(MbnTests PRIVATE "${RUST_LIB}")
52+
53+
# Link Google Test to the tests
54+
target_link_libraries(MbnTests PRIVATE
55+
GTest::gtest
56+
GTest::gtest_main
57+
)
58+
59+
# Add tests to CTest
60+
add_test(NAME MbnTests COMMAND MbnTests)
61+
62+
# Link macOS frameworks (CoreFoundation and Security)
63+
if (APPLE)
64+
target_link_libraries(MbnTests PRIVATE
65+
"-framework CoreFoundation"
66+
"-framework Security"
67+
"-framework SystemConfiguration"
68+
)
69+
endif()

c/Cargo.toml

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
[package]
2+
name = "mbn_c"
3+
version = "0.1.0"
4+
edition = "2021"
5+
6+
[lib]
7+
crate-type = ["staticlib"] # for .a static lib
8+
9+
10+
[dependencies]
11+
mbinary = {path = "../rust"}
12+
libc ="0.2.168"
13+
cbindgen = { version = "0.27.0" }
14+
15+
[build-dependencies]
16+
cbindgen = { version = "0.27.0", default-features = false }

c/build.rs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
use std::env;
2+
3+
fn main() {
4+
let crate_dir = env::var("CARGO_MANIFEST_DIR").unwrap();
5+
let config: cbindgen::Config = cbindgen::Config::from_file("cbindgen.toml").unwrap();
6+
7+
cbindgen::Builder::new()
8+
.with_crate(crate_dir)
9+
.with_config(config)
10+
.generate()
11+
.expect("Unable to generate bindings")
12+
.write_to_file("../target/mbinary.h");
13+
}

c/build.sh

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
#!/bin/bash
2+
3+
options() {
4+
echo "1 - Build"
5+
echo "2 - Run"
6+
echo "3 - Build & Test"
7+
echo "4 - Build & Run"
8+
}
9+
10+
build() {
11+
# cd ../
12+
cargo build
13+
14+
# cd mbn_c
15+
# Build artifacts
16+
cmake -DCMAKE_EXPORT_COMPILE_COMMANDS=ON -B build
17+
18+
# Build
19+
cmake --build ./build
20+
21+
}
22+
23+
run() {
24+
# Run binary
25+
./bin/mbn
26+
}
27+
28+
tester() {
29+
if cd build; then
30+
ctest --verbose
31+
fi
32+
}
33+
34+
# Main
35+
while true; do
36+
options
37+
read -r option
38+
39+
case $option in
40+
1)
41+
build
42+
break
43+
;;
44+
2)
45+
run
46+
break
47+
;;
48+
3)
49+
build
50+
tester
51+
break
52+
;;
53+
4)
54+
build
55+
run
56+
break
57+
;;
58+
*) echo "Choose a different one." ;;
59+
esac
60+
done

c/cbindgen.toml

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
language = "C"
2+
pragma_once = true
3+
line_length = 100
4+
tab_width = 4
5+
sys_includes = ["stdio.h"]
6+
7+
# Affects enum typedefs
8+
cpp_compat = true
9+
10+
[export]
11+
include =["RecordHeader","RType", "Mbp1Msg", "OhlcvMsg", "TradesMsg", "BboMsg", "TbboMsg", "BidAskPair", "RecordData","CRecordEnum" , "Side", "Action"]
12+
13+
[export.rename]
14+
"FILE" = "FILE"
15+
16+
[enum]
17+
prefix_with_name = false
18+
19+
[parse]
20+
parse_deps = true
21+
include = ["mbinary"]
22+
extra_bindings = ["mbinary"]
23+
24+
# [defines]
25+
# cbindgen = true
26+

c/src/decode.rs

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
use mbinary::decode::RecordDecoder;
2+
use std::ffi::CStr;
3+
use std::fs::File;
4+
use std::io::{BufReader, Cursor, Read};
5+
use std::ptr;
6+
use std::slice;
7+
8+
use crate::records::CRecordEnum;
9+
10+
/// C-compatible wrapper around RecordDecoder
11+
pub struct CRecordDecoder {
12+
decoder: RecordDecoder<Box<dyn Read>>,
13+
}
14+
15+
/// Create a new `CRecordDecoder` with an in-memory buffer as the source.
16+
#[no_mangle]
17+
pub extern "C" fn create_buffer_decoder(
18+
source: *const u8,
19+
source_size: usize,
20+
) -> *mut CRecordDecoder {
21+
if source.is_null() || source_size == 0 {
22+
return ptr::null_mut();
23+
}
24+
25+
// Convert the raw pointer and size to a Vec<u8>
26+
let source_slice = unsafe { slice::from_raw_parts(source, source_size) };
27+
let source_buffer = Cursor::new(source_slice.to_vec());
28+
29+
let decoder = CRecordDecoder {
30+
decoder: RecordDecoder::new(Box::new(source_buffer)),
31+
};
32+
33+
Box::into_raw(Box::new(decoder))
34+
}
35+
36+
/// Create a new `CRecordDecoder` with a file as the source.
37+
#[no_mangle]
38+
pub extern "C" fn create_file_decoder(file_path: *const libc::c_char) -> *mut CRecordDecoder {
39+
if file_path.is_null() {
40+
return ptr::null_mut();
41+
}
42+
43+
// Convert C string to Rust Path
44+
let c_str = unsafe { CStr::from_ptr(file_path) };
45+
let path = match c_str.to_str() {
46+
Ok(path) => path,
47+
Err(_) => return ptr::null_mut(),
48+
};
49+
50+
let decoder = match create_decoder_from_file(path) {
51+
Ok(f) => f,
52+
Err(_) => return ptr::null_mut(),
53+
};
54+
55+
let c_decoder = CRecordDecoder { decoder };
56+
57+
Box::into_raw(Box::new(c_decoder))
58+
}
59+
60+
// Helper function, not exposed to C directly.
61+
fn create_decoder_from_file(
62+
file_path: &str,
63+
) -> Result<RecordDecoder<Box<dyn Read>>, std::io::Error> {
64+
let file = File::open(file_path)?;
65+
let buf_reader = BufReader::new(file);
66+
let boxed_reader: Box<dyn Read> = Box::new(buf_reader);
67+
Ok(RecordDecoder::new(boxed_reader))
68+
}
69+
70+
/// Iteratively decodes records, returning false when all records are decoded.
71+
#[no_mangle]
72+
pub extern "C" fn decoder_iter(decoder: *mut CRecordDecoder, output: *mut CRecordEnum) -> bool {
73+
if decoder.is_null() || output.is_null() {
74+
return false;
75+
}
76+
77+
let decoder = unsafe { &mut *decoder };
78+
let mut iterator = decoder.decoder.decode_iterator();
79+
80+
match iterator.next() {
81+
Some(Ok(record_enum)) => {
82+
let c_record: CRecordEnum = record_enum.into();
83+
unsafe { ptr::write(output, c_record) };
84+
true
85+
}
86+
_ => false,
87+
}
88+
}
89+
90+
/// Destroy the `CRecordDecoder`
91+
#[no_mangle]
92+
pub extern "C" fn destroy_record_decoder(decoder: *mut CRecordDecoder) {
93+
if decoder.is_null() {
94+
return;
95+
}
96+
97+
unsafe {
98+
let _ = Box::from_raw(decoder);
99+
}
100+
}

0 commit comments

Comments
 (0)