Compare commits

..

1 Commits

Author SHA1 Message Date
8effb1ff7d 0.2.4: fix length defines 2025-08-17 15:52:13 +01:00
9 changed files with 159 additions and 221 deletions

3
.gitignore vendored
View File

@@ -1,5 +1,2 @@
# Cargo
target/
# Slop
.vscode/

36
.vscode/launch.json vendored Normal file
View File

@@ -0,0 +1,36 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"type": "lldb",
"request": "launch",
"name": "Debug executable 'bin2hpp'",
"cargo": {
"args": ["build", "--bin=bin2hpp", "--package=bin2hpp"],
"filter": {
"name": "bin2hpp",
"kind": "bin"
}
},
"args": ["-i", "/home/adam/my_library.dll"],
"cwd": "${workspaceFolder}"
},
{
"type": "lldb",
"request": "launch",
"name": "Debug unit tests in executable 'bin2hpp'",
"cargo": {
"args": ["test", "--no-run", "--bin=bin2hpp", "--package=bin2hpp"],
"filter": {
"name": "bin2hpp",
"kind": "bin"
}
},
"args": [],
"cwd": "${workspaceFolder}"
}
]
}

2
Cargo.lock generated
View File

@@ -4,7 +4,7 @@ version = 4
[[package]]
name = "bin2hpp"
version = "0.3.0"
version = "0.2.4"
dependencies = [
"thiserror",
]

View File

@@ -1,6 +1,6 @@
[package]
name = "bin2hpp"
version = "0.3.0"
version = "0.2.4"
authors = ["Adam Macdonald"]
edition = "2024"
license = "GPL-3.0-only"

View File

@@ -6,55 +6,25 @@ CLI tool for converting files into header files which one can use to directly em
## Building
`cargo build`, or `cargo [build | run | install] --profile optimised` to build an optimised binary.
The performance (but potentially not the executable size) can be further optimised by telling the compiler to use a more modern instruction set: `RUSTFLAGS="-C target-cpu=native"`.
1. `cargo build`
## Usage
### Basic usage
`$ bin2hpp -i my_library.dll -o c_header.h` will generate a `c_header.h` file in your current working directory containing something similar to the following:
`$ bin2hpp -i ~/my_library.dll --overwrite` will generate an `my_library_dll.h` file in your current working directory containing something similar to the following:
```c
// Generated by bin2hpp 0.3.0
#ifdef MY_LIBRARY_DLL_INIT
// Generated by bin2hpp 0.2.4
#ifndef MY_LIBRARY_DLL_H
#define MY_LIBRARY_DLL_H
#define MY_LIBRARY_DLL_LEN 9728
const unsigned char MY_LIBRARY_DLL[MY_LIBRARY_DLL_LEN] = {77,90,144,0,3,0,0,0,4, ...};
const unsigned char MY_LIBRARY_DLL[MY_LIBRARY_DLL_LEN] = {0x4d,0x5a,0x90,0x0,0x3,0x0,0x0, ...};
#else
#define MY_LIBRARY_DLL_LEN 9728
extern const unsigned char MY_LIBRARY_DLL[MY_LIBRARY_DLL_LEN];
#endif
```
Note how in **C mode** one must `#define MY_LIBRARY_DLL_INIT`, or whatever the corresponding implementation macro is for your generated header file, in exactly _one_ C source file (`*.c`, not in a `*.h` file).
Example:
`main.c`
```c
// In any other C source files other than the first: do not redefine
#define MY_LIBRARY_DLL_INIT
#include "c_header.h"
int main() {
return MY_LIBRARY_DLL_LEN;
}
```
Alternatively, in **C++ mode**: `bin2hpp -i my_library.dll -o cpp_header.hpp --cpp --constexpr --stdarray --i8`
```cpp
// Generated by bin2hpp 0.3.0
#if !defined(CPP_HEADER_HPP)
#define CPP_HEADER_HPP
#include <cstdint>
#include <array>
#define MY_LIBRARY_DLL_LEN 9728
inline constexpr std::array<std::int8_t,MY_LIBRARY_DLL_LEN> MY_LIBRARY_DLL = {77,90,-112,0,3,0,0,0,4, ...};
#endif
```
### Note about CLI arguments
Command line arguments are not positional. The input file path argument is the

View File

@@ -22,8 +22,7 @@ pub struct Config {
pub(crate) constness: Constness,
pub(crate) symbol_name: String,
pub(crate) namespace: Option<String>,
pub(crate) impl_macro: String,
pub(crate) header_guard: String,
pub(crate) guard_name: String,
pub(crate) container_type: ContainerType,
}
@@ -57,7 +56,18 @@ pub fn parse_config_from_args(args: &CliArgs) -> Result<Config, ProgramConfigErr
}
};
let symbol_name = match &args.symbol_name {
Ok(Config {
input_file_path: args.input_file_path.clone(),
output_file_path: output_path.clone(),
overwrite: args.overwrite,
line_ending: if args.line_ending == LineEnding::Unix {
LF_LINE_ENDING.into()
} else {
CRLF_LINE_ENDING.into()
},
language: args.language,
constness: args.constness,
symbol_name: match &args.symbol_name {
Some(s) => {
if s.chars().all(|c| c.is_ascii_alphanumeric() || c == '_') {
s.to_string()
@@ -84,39 +94,7 @@ pub fn parse_config_from_args(args: &CliArgs) -> Result<Config, ProgramConfigErr
format!("{}{}", stem.to_ascii_uppercase(), ext.to_ascii_uppercase())
}
};
let header_guard = match output_path.file_stem() {
Some(stem) => {
format!(
"{}_{}",
stem.to_string_lossy().to_uppercase(),
if let Some(ext) = output_path.extension() {
ext.to_string_lossy().to_uppercase()
} else {
"".into()
}
)
}
None => {
return Err(ProgramConfigError::InvalidPath(
output_path.to_string_lossy().to_string(),
));
}
};
Ok(Config {
input_file_path: args.input_file_path.clone(),
output_file_path: output_path.clone(),
overwrite: args.overwrite,
line_ending: if args.line_ending == LineEnding::Unix {
LF_LINE_ENDING.into()
} else {
CRLF_LINE_ENDING.into()
},
language: args.language,
constness: args.constness,
symbol_name: symbol_name.clone(),
namespace: match &args.namespace {
Some(ns) => {
if ns.chars().all(|c| c.is_ascii_alphanumeric() || c == '_') {
@@ -127,7 +105,7 @@ pub fn parse_config_from_args(args: &CliArgs) -> Result<Config, ProgramConfigErr
}
_ => None,
},
impl_macro: match &args.impl_macro_name {
guard_name: match &args.guard_name {
Some(guard) => {
if guard.chars().all(|c| c.is_ascii_alphanumeric() || c == '_') {
guard.clone()
@@ -136,10 +114,29 @@ pub fn parse_config_from_args(args: &CliArgs) -> Result<Config, ProgramConfigErr
}
}
_ => {
format!("{}_INIT", symbol_name.to_uppercase())
let mut stem: String = match output_path.file_stem() {
Some(s) => s.to_string_lossy().to_ascii_uppercase(),
None => {
return Err(ProgramConfigError::InvalidPath(
args.input_file_path.to_string_lossy().to_string(),
));
}
};
stem.retain(|c| c.is_ascii_alphanumeric() || c == '_');
let mut ext: String = match output_path.extension() {
Some(s) => s.to_string_lossy().to_ascii_uppercase(),
None => {
return Err(ProgramConfigError::InvalidPath(
args.input_file_path.to_string_lossy().to_string(),
));
}
};
ext.retain(|c| c.is_ascii_alphanumeric() || c == '_');
format!("{}_{}", stem, ext)
}
},
header_guard: header_guard,
container_type: args.container_type,
})
}

View File

@@ -20,26 +20,6 @@ pub enum ByteType {
I8,
}
impl ByteType {
pub fn to_c_type(&self) -> &'static str {
match self {
ByteType::UnsignedChar => "unsigned char",
ByteType::Char => "char",
ByteType::U8 => "uint8_t",
ByteType::I8 => "int8_t",
}
}
pub fn to_cpp_type(&self) -> &'static str {
match self {
ByteType::UnsignedChar => self.to_c_type(),
ByteType::Char => self.to_c_type(),
ByteType::U8 => "std::uint8_t",
ByteType::I8 => "std::int8_t",
}
}
}
#[derive(Debug, PartialEq, Clone, Copy)]
pub enum ContainerType {
CArray(ByteType),
@@ -81,7 +61,7 @@ pub struct CliArgs {
pub(crate) constness: Constness,
pub(crate) symbol_name: Option<String>,
pub(crate) namespace: Option<String>,
pub(crate) impl_macro_name: Option<String>,
pub(crate) guard_name: Option<String>,
pub(crate) container_type: ContainerType,
}
@@ -123,10 +103,10 @@ pub fn parse_cli_args(args: &[String]) -> Result<CliArgs, CliParseError> {
None => None,
};
let impl_macro_name = match args.iter().position(|s| *s == "--impl") {
let guard_name = match args.iter().position(|s| *s == "--guard") {
Some(idx) => match args.get(idx + 1) {
Some(s) => Some(s),
None => return Err(CliParseError::MissingOperand("--impl")),
None => return Err(CliParseError::MissingOperand("--guard")),
},
None => None,
};
@@ -237,7 +217,7 @@ pub fn parse_cli_args(args: &[String]) -> Result<CliArgs, CliParseError> {
constness: constness,
symbol_name: symbol_name.cloned(),
namespace: namespace.cloned(),
impl_macro_name: impl_macro_name.cloned(),
guard_name: guard_name.cloned(),
container_type: container_type,
});
}

View File

@@ -28,8 +28,8 @@ OUTPUT OPTIONS:
--symname NAME Symbol name
--namespace NAME Namespace in which the symbol will exist (C++ mode)
--impl NAME Preprocessor macro to use for the implementation guard
in C mode (default: derive from symbol name)
--guard NAME Preprocessor macro to use for the header guard (#ifndef ...)
(default: derive from output filename)
CONTAINER TYPES:

View File

@@ -29,21 +29,20 @@ pub fn header(config: &Config, in_data: &[u8]) -> Result<String, HeaderGeneratio
// Header guard
if config.language == Language::Cpp {
// C++ style
write!(
buf,
"#if !defined({}){}",
config.header_guard, config.line_ending
config.guard_name, config.line_ending
)
.unwrap();
write!(buf, "#define {}{}", config.header_guard, config.line_ending).unwrap();
} else {
// C style
write!(buf, "#ifdef {}{}", config.impl_macro, config.line_ending).unwrap();
write!(buf, "#ifndef {}{}", config.guard_name, config.line_ending).unwrap();
}
write!(buf, "#define {}{}", config.guard_name, config.line_ending).unwrap();
// Includes & main content
with_includes(config, &mut buf);
with_contents(config, in_data, &mut buf)?;
// End header guard
@@ -58,21 +57,6 @@ pub fn with_contents(
in_data: &[u8],
buf: &mut String,
) -> Result<(), HeaderGenerationError> {
with_includes(config, buf);
let length: usize = match config.container_type {
ContainerType::CString => in_data.len() + 1, // +1 for NUL terminator
_ => in_data.len(),
};
write!(
buf,
"{}{}",
length_define(config, length),
config.line_ending
)
.unwrap();
if config.language == Language::Cpp {
// C++ mode
@@ -87,18 +71,9 @@ pub fn with_contents(
// C mode
with_definition(config, in_data, buf)?;
write!(buf, "#else{}", config.line_ending).unwrap();
with_includes(config, buf);
write!(
buf,
"{}{}",
length_define(config, length),
config.line_ending
)
.unwrap();
with_declaration(config, buf);
with_declaration(config, in_data, buf);
}
Ok(())
@@ -177,16 +152,29 @@ pub fn with_definition(
in_data: &[u8],
buf: &mut String,
) -> Result<(), HeaderGenerationError> {
let length: usize = match config.container_type {
ContainerType::CString => in_data.len() + 1, // +1 for NUL terminator
_ => in_data.len(),
};
let modifiers = definition_modifiers(config);
write!(
buf,
"{}{}",
length_define(config, length),
config.line_ending
)
.unwrap();
write!(
buf,
"{}{} = {};{}",
modifiers,
data_type_and_symbol(config),
data_type_and_symbol(config, in_data),
match config.container_type {
ContainerType::CArray(_) => with_array_initialiser(config, in_data),
ContainerType::StdArray(_) => with_array_initialiser(config, in_data),
ContainerType::CArray(_) => with_array_initialiser(in_data),
ContainerType::StdArray(_) => with_array_initialiser(in_data),
_ => with_string_initialiser(config, in_data)?,
},
config.line_ending
@@ -196,47 +184,25 @@ pub fn with_definition(
Ok(())
}
pub fn with_declaration(config: &Config, buf: &mut String) {
pub fn with_declaration(config: &Config, in_data: &[u8], buf: &mut String) {
let modifiers = declaration_modifiers(config);
write!(
buf,
"{}{};{}",
modifiers,
data_type_and_symbol(config),
data_type_and_symbol(config, in_data),
config.line_ending
)
.unwrap();
}
pub fn with_array_initialiser(config: &Config, in_data: &[u8]) -> String {
pub fn with_array_initialiser(in_data: &[u8]) -> String {
let mut out_str: String = String::with_capacity(in_data.len() * 6 + 16);
let signed_literals = match config.container_type {
ContainerType::CArray(byte_type) => match byte_type {
ByteType::Char => true,
ByteType::I8 => true,
_ => false,
},
ContainerType::StdArray(byte_type) => match byte_type {
ByteType::Char => true,
ByteType::I8 => true,
_ => false,
},
_ => false,
};
write!(out_str, "{{").unwrap();
if signed_literals {
for b in in_data {
let b = i8::from_ne_bytes([*b; 1]);
write!(out_str, "{},", b).unwrap();
}
} else {
for b in in_data {
write!(out_str, "{},", b).unwrap();
}
write!(out_str, "{:#x},", b).unwrap();
}
let _ = out_str.pop();
write!(out_str, "}}").unwrap();
@@ -280,49 +246,41 @@ pub fn length_symbol(config: &Config) -> String {
out_str
}
pub fn data_type_and_symbol(config: &Config) -> String {
let mut out_str = String::with_capacity(config.symbol_name.len() + 20);
pub fn data_type_and_symbol(config: &Config, in_data: &[u8]) -> String {
let u8_type = match config.language {
Language::C => "uint8_t",
Language::Cpp => "std::uint8_t",
};
let i8_type = match config.language {
Language::C => "int8_t",
Language::Cpp => "std::int8_t",
};
let length_sym = length_symbol(config);
match config.container_type {
ContainerType::CArray(byte_type) => write!(
out_str,
"{} {}[{}]",
if config.language == Language::Cpp {
byte_type.to_cpp_type()
} else {
byte_type.to_c_type()
ContainerType::CArray(byte_type) => match byte_type {
ByteType::UnsignedChar => {
format!("unsigned char {}[{}]", config.symbol_name, length_sym)
}
ByteType::Char => format!("char {}[{}]", config.symbol_name, length_sym),
_ => format!("{} {}[{}]", u8_type, config.symbol_name, length_sym),
},
config.symbol_name,
length_sym
)
.unwrap(),
ContainerType::StdArray(byte_type) => write!(
out_str,
ContainerType::StdArray(byte_type) => {
format!(
"std::array<{},{}> {}",
if config.language == Language::Cpp {
byte_type.to_cpp_type()
} else {
byte_type.to_c_type()
match byte_type {
ByteType::UnsignedChar => "unsigned char",
ByteType::Char => "char",
ByteType::U8 => u8_type,
ByteType::I8 => i8_type,
},
length_sym,
in_data.len(),
config.symbol_name
)
.unwrap(),
ContainerType::CString => write!(
out_str,
"{} {}[{}]",
ByteType::Char.to_c_type(),
config.symbol_name,
length_sym
)
.unwrap(),
ContainerType::StdString => write!(out_str, "std::string {}", config.symbol_name).unwrap(),
ContainerType::StdStringView => {
write!(out_str, "std::string_view {}", config.symbol_name).unwrap()
}
};
out_str
ContainerType::CString => format!("char {}[{}]", config.symbol_name, length_sym),
ContainerType::StdString => format!("std::string {}", config.symbol_name),
ContainerType::StdStringView => format!("std::string_view {}", config.symbol_name),
}
}