libcall 0.0.2 → 0.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +158 -65
- data/lib/libcall/argument_processor.rb +104 -0
- data/lib/libcall/callback_handler.rb +52 -0
- data/lib/libcall/caller.rb +14 -59
- data/lib/libcall/cli.rb +23 -9
- data/lib/libcall/fiddley.rb +140 -0
- data/lib/libcall/library_finder.rb +39 -3
- data/lib/libcall/output_reader.rb +43 -0
- data/lib/libcall/parser.rb +49 -0
- data/lib/libcall/type_map.rb +73 -3
- data/lib/libcall/version.rb +1 -1
- data/lib/libcall.rb +4 -0
- metadata +5 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 6d3e1c356b60aca8356cd319a9c31619850cb4cbee3275d94f04bc0f3d18b422
|
|
4
|
+
data.tar.gz: 75ebf8ecc71c6d38f34c4b1f1b6be809e95cc2d5f24cba21755cbf3455ccd9fb
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: fcebbe2dd033a7392ee4bc5b5efed51e5352f6cc1a1dee9aa33751e9833703a6574853265698851548c1b1cda17fad437f450f6058b2a664b7b599c049b44e48
|
|
7
|
+
data.tar.gz: f3f36070e3aec368a843d72851c88bc25e86a8f04bc23e4950e92e0eda46a23a8980d25598c697069c093f3083b0476d2f6bd1d082ec2dc46e64dd1af661fe6e
|
data/README.md
CHANGED
|
@@ -12,50 +12,20 @@ Call C functions in shared libraries from the command line.
|
|
|
12
12
|
gem install libcall
|
|
13
13
|
```
|
|
14
14
|
|
|
15
|
-
**Windows**: Supports DLLs (e.g., `msvcrt.dll`, `kernel32.dll`). Searches in System32, PATH, and MSYS2/MinGW directories. For building custom DLLs, RubyInstaller with DevKit is recommended.
|
|
16
|
-
|
|
17
|
-
## Usage
|
|
18
|
-
|
|
19
|
-
```sh
|
|
20
|
-
libcall [OPTIONS] <LIBRARY> <FUNCTION> (TYPE VALUE)...
|
|
21
|
-
```
|
|
22
|
-
|
|
23
15
|
### Quick Examples
|
|
24
16
|
|
|
25
17
|
```sh
|
|
26
|
-
#
|
|
27
|
-
libcall -lm -r f64 sqrt double 16
|
|
28
|
-
# => 4.0
|
|
29
|
-
|
|
30
|
-
# libc strlen
|
|
31
|
-
libcall -lc strlen string "hello" -r usize
|
|
32
|
-
# => 5
|
|
18
|
+
libcall -lm sqrt double 16 -r double # => 4.0
|
|
33
19
|
```
|
|
34
20
|
|
|
35
|
-
### Argument Syntax
|
|
36
|
-
|
|
37
|
-
Pass arguments as TYPE VALUE pairs (single-token suffix style has been removed):
|
|
38
|
-
|
|
39
|
-
- Examples: `int 10`, `double -3.14`, `string "hello"`
|
|
40
|
-
- Negative values are safe (not treated as options): `int -23`
|
|
41
|
-
|
|
42
|
-
Pointers and null:
|
|
43
|
-
|
|
44
|
-
- Use `ptr` (or `pointer`) to pass raw addresses as integers
|
|
45
|
-
- Use `null`, `nil`, `NULL`, or `0` to pass a null pointer
|
|
46
|
-
|
|
47
21
|
```sh
|
|
48
|
-
|
|
49
|
-
libcall -ltest str_length ptr null -r i32
|
|
50
|
-
# => 0
|
|
22
|
+
libcall -lc strlen string "hello" -r usize # => 5
|
|
51
23
|
```
|
|
52
24
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
- Use `--` to stop option parsing if a value starts with `-`
|
|
25
|
+
## Usage
|
|
56
26
|
|
|
57
27
|
```sh
|
|
58
|
-
libcall
|
|
28
|
+
libcall [OPTIONS] <LIBRARY> <FUNCTION> (TYPE VALUE)...
|
|
59
29
|
```
|
|
60
30
|
|
|
61
31
|
### Options
|
|
@@ -76,48 +46,119 @@ Library search:
|
|
|
76
46
|
|
|
77
47
|
### More Examples
|
|
78
48
|
|
|
49
|
+
Output parameter with libm
|
|
50
|
+
|
|
51
|
+
```sh
|
|
52
|
+
libcall -lm modf double -3.14 out:double -r f64
|
|
53
|
+
# Result: -0.14000000000000012
|
|
54
|
+
# Output parameters:
|
|
55
|
+
# [1] double = -3.0
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
JSON output
|
|
59
|
+
|
|
79
60
|
```sh
|
|
80
|
-
# JSON output
|
|
81
61
|
libcall --json -lm sqrt double 9.0 -r f64
|
|
62
|
+
# {
|
|
63
|
+
# "library": "/lib/x86_64-linux-gnu/libm.so",
|
|
64
|
+
# "function": "sqrt",
|
|
65
|
+
# "return_type": "double",
|
|
66
|
+
# "result": 3.0
|
|
67
|
+
# }
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
Dry run
|
|
82
71
|
|
|
83
|
-
|
|
72
|
+
```sh
|
|
84
73
|
libcall --dry-run -lc getpid -r int
|
|
74
|
+
# Library: /lib/x86_64-linux-gnu/libc.so
|
|
75
|
+
# Function: getpid
|
|
76
|
+
# Return: int
|
|
77
|
+
```
|
|
85
78
|
|
|
86
|
-
|
|
87
|
-
libcall -lm modf double -3.14 out:double -r f64
|
|
79
|
+
## Type Reference
|
|
88
80
|
|
|
89
|
-
|
|
90
|
-
libcall -lm fabs double -5.5 -r f64
|
|
91
|
-
# => 5.5
|
|
81
|
+
libcall supports multiple naming conventions for types, making it easy to work with C libraries.
|
|
92
82
|
|
|
93
|
-
|
|
94
|
-
libcall msvcrt.dll sqrt double 16.0 -r f64
|
|
95
|
-
# => 4.0
|
|
83
|
+
### Integer Types
|
|
96
84
|
|
|
97
|
-
|
|
98
|
-
|
|
85
|
+
| Short (Rust-like) | C Standard | C99/stdint.h | Size |
|
|
86
|
+
| ----------------- | -------------------------- | ---------------------- | ------------------ |
|
|
87
|
+
| `i8` / `u8` | `char` / `uchar` | `int8_t` / `uint8_t` | 1 byte |
|
|
88
|
+
| `i16` / `u16` | `short` / `ushort` | `int16_t` / `uint16_t` | 2 bytes |
|
|
89
|
+
| `i32` / `u32` | `int` / `uint` | `int32_t` / `uint32_t` | 4 bytes |
|
|
90
|
+
| `i64` / `u64` | `long_long` / `ulong_long` | `int64_t` / `uint64_t` | 8 bytes |
|
|
91
|
+
| `isize` / `usize` | `long` / `ulong` | `ssize_t` / `size_t` | platform-dependent |
|
|
92
|
+
|
|
93
|
+
**Alternative names**: You can use any of these:
|
|
94
|
+
|
|
95
|
+
- C-style: `char`, `short`, `int`, `long`, `unsigned_int`, etc.
|
|
96
|
+
- stdint-style: `int8`, `int16`, `int32`, `int64`, `uint8`, `uint16`, etc.
|
|
97
|
+
- With `_t` suffix: `int8_t`, `uint8_t`, `int32_t`, `size_t`, etc.
|
|
98
|
+
|
|
99
|
+
### Floating Point Types
|
|
100
|
+
|
|
101
|
+
| Short | C Standard | Alternative | Size |
|
|
102
|
+
| ----- | ---------- | ----------- | ------- |
|
|
103
|
+
| `f32` | `float` | `float32` | 4 bytes |
|
|
104
|
+
| `f64` | `double` | `float64` | 8 bytes |
|
|
105
|
+
|
|
106
|
+
### Pointer Types
|
|
107
|
+
|
|
108
|
+
| Type | Description | Usage |
|
|
109
|
+
| --------------------------- | ------------------------ | ------------------------------ |
|
|
110
|
+
| `ptr` / `pointer` / `voidp` | Generic pointer (void\*) | For arbitrary memory addresses |
|
|
111
|
+
| `string` / `cstr` / `str` | C string (char\*) | For passing/returning strings |
|
|
112
|
+
|
|
113
|
+
**Null pointer values**: Use `null`, `NULL`, `nil`, or `0` to pass a null pointer.
|
|
114
|
+
|
|
115
|
+
### Special Types
|
|
116
|
+
|
|
117
|
+
| Type | Description | Alternative names |
|
|
118
|
+
| ------------------------ | --------------------------- | -------------------- |
|
|
119
|
+
| `void` | No value (return type only) | — |
|
|
120
|
+
| `size_t` | Platform size type | `usize` (unsigned) |
|
|
121
|
+
| `ssize_t` | Signed size type | `isize` (signed) |
|
|
122
|
+
| `intptr_t` / `uintptr_t` | Pointer-sized integer | `intptr` / `uintptr` |
|
|
123
|
+
| `ptrdiff_t` | Pointer difference type | — |
|
|
124
|
+
| `bool` | Boolean (as int) | — |
|
|
125
|
+
|
|
126
|
+
### Output Parameters
|
|
127
|
+
|
|
128
|
+
Prefix any type with `out:` to create an output parameter:
|
|
129
|
+
|
|
130
|
+
```sh
|
|
131
|
+
out:int # Output integer pointer (int*)
|
|
132
|
+
out:double # Output double pointer (double*)
|
|
133
|
+
out:string # Output string pointer (char**)
|
|
99
134
|
```
|
|
100
135
|
|
|
101
|
-
|
|
136
|
+
### Array Types
|
|
137
|
+
|
|
138
|
+
| Syntax | Description | Example |
|
|
139
|
+
| ------------- | ----------------------------- | -------------------- |
|
|
140
|
+
| `TYPE[]` | Input array | `int[] 1,2,3,4,5` |
|
|
141
|
+
| `out:TYPE[N]` | Output array of N elements | `out:int[10]` |
|
|
142
|
+
| `out:TYPE[N]` | Output array with initializer | `out:int[4] 4,3,2,1` |
|
|
102
143
|
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
|
106
|
-
|
|
|
107
|
-
| `
|
|
108
|
-
| `
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
- `
|
|
120
|
-
- `
|
|
144
|
+
### Callback Types
|
|
145
|
+
|
|
146
|
+
| Keyword | Description | Example |
|
|
147
|
+
| ---------- | --------------------------- | -------------------------------- |
|
|
148
|
+
| `func` | Function pointer (callback) | `func 'int(int a,int b){ a+b }'` |
|
|
149
|
+
| `callback` | Alias for `func` | Same as above |
|
|
150
|
+
|
|
151
|
+
### Argument Syntax
|
|
152
|
+
|
|
153
|
+
Pass arguments as TYPE VALUE pairs:
|
|
154
|
+
|
|
155
|
+
```sh
|
|
156
|
+
libcall -lm sqrt double 16.0 -r f64
|
|
157
|
+
libcall -lc strlen string "hello" -r usize
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
- Null pointers: Use `null`, `NULL`, `nil`, or `0`
|
|
161
|
+
- Negative numbers work as expected (e.g., `double -3.14`)
|
|
121
162
|
|
|
122
163
|
## pkg-config Support
|
|
123
164
|
|
|
@@ -160,6 +201,42 @@ libcall -lc getrandom out:uchar[16] size_t 16 uint 0 -r long
|
|
|
160
201
|
libcall -lSystem arc4random_buf out:uchar[16] size_t 16 -r void
|
|
161
202
|
```
|
|
162
203
|
|
|
204
|
+
## Callbacks (experimental)
|
|
205
|
+
|
|
206
|
+
Pass a C function pointer via a Ruby callback. Use `func` or `callback` with a quoted spec:
|
|
207
|
+
|
|
208
|
+
- Syntax: `func 'RET(TYPE name, TYPE name, ...){ ruby_code }'` (alias: `callback ...`)
|
|
209
|
+
- Inside the block, helper methods from `Libcall::Fiddley::DSL` are available:
|
|
210
|
+
- `int(ptr)`, `double(ptr)`, `cstr(ptr)` read values from pointers
|
|
211
|
+
- `read(:type, ptr)` reads any supported type; `ptr(addr)` makes a pointer
|
|
212
|
+
|
|
213
|
+
Quick examples
|
|
214
|
+
|
|
215
|
+
```sh
|
|
216
|
+
# Fixture function: int32_t apply_i32(int32_t, int32_t, int32_t (*)(int32_t,int32_t))
|
|
217
|
+
libcall -ltest -L test/fixtures/libtest/build apply_i32 \
|
|
218
|
+
int 3 int 5 \
|
|
219
|
+
func 'int(int a,int b){ a + b }' \
|
|
220
|
+
-r i32
|
|
221
|
+
# => 8
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
```sh
|
|
225
|
+
# libc qsort: sort 4 ints ascending; use out:int[4] with an initializer so the result prints
|
|
226
|
+
libcall -lc qsort \
|
|
227
|
+
out:int[4] 4,2,3,1 \
|
|
228
|
+
size_t 4 \
|
|
229
|
+
size_t 4 \
|
|
230
|
+
callback 'int(void* a, void* b){ int(a) <=> int(b) }' \
|
|
231
|
+
-r void
|
|
232
|
+
# Output parameters:
|
|
233
|
+
# [0] int[4] = [1, 2, 3, 4]
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
Notes
|
|
237
|
+
|
|
238
|
+
- Match the C signature exactly (types and arity). Blocks run in-process; exceptions abort the call.
|
|
239
|
+
|
|
163
240
|
## Warning
|
|
164
241
|
|
|
165
242
|
FFI calls are inherently unsafe. You must:
|
|
@@ -171,6 +248,22 @@ FFI calls are inherently unsafe. You must:
|
|
|
171
248
|
|
|
172
249
|
Incorrect usage can crash your program.
|
|
173
250
|
|
|
251
|
+
## Windows Support
|
|
252
|
+
|
|
253
|
+
Supports DLLs (e.g., `msvcrt.dll`, `kernel32.dll`). Searches in System32, PATH, and MSYS2/MinGW directories. For building custom DLLs, RubyInstaller with DevKit is recommended.
|
|
254
|
+
|
|
255
|
+
### Windows Examples
|
|
256
|
+
|
|
257
|
+
```powershell
|
|
258
|
+
# Calling C runtime functions
|
|
259
|
+
libcall msvcrt.dll sqrt double 16.0 -r f64 # => 4.0
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
```powershell
|
|
263
|
+
# Accessing environment variables
|
|
264
|
+
libcall msvcrt.dll getenv string "PATH" -r cstr
|
|
265
|
+
```
|
|
266
|
+
|
|
174
267
|
## Development
|
|
175
268
|
|
|
176
269
|
```sh
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Libcall
|
|
4
|
+
# Processes argument pairs for FFI calls
|
|
5
|
+
class ArgumentProcessor
|
|
6
|
+
# Value object to hold processed arguments
|
|
7
|
+
ProcessedArguments = Struct.new(
|
|
8
|
+
:arg_types,
|
|
9
|
+
:arg_values,
|
|
10
|
+
:out_refs,
|
|
11
|
+
:closures,
|
|
12
|
+
keyword_init: true
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
def initialize(arg_pairs)
|
|
16
|
+
@arg_pairs = arg_pairs
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def process
|
|
20
|
+
arg_types = []
|
|
21
|
+
arg_values = []
|
|
22
|
+
out_refs = []
|
|
23
|
+
closures = []
|
|
24
|
+
|
|
25
|
+
@arg_pairs.each_with_index do |(type_sym, value), idx|
|
|
26
|
+
if type_sym.is_a?(Array)
|
|
27
|
+
process_complex_type(type_sym, value, idx, arg_types, arg_values, out_refs)
|
|
28
|
+
elsif type_sym == :callback
|
|
29
|
+
process_callback(value, arg_types, arg_values, closures)
|
|
30
|
+
else
|
|
31
|
+
arg_types << TypeMap.to_fiddle_type(type_sym)
|
|
32
|
+
arg_values << value
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
ProcessedArguments.new(
|
|
37
|
+
arg_types: arg_types,
|
|
38
|
+
arg_values: arg_values,
|
|
39
|
+
out_refs: out_refs,
|
|
40
|
+
closures: closures
|
|
41
|
+
)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
private
|
|
45
|
+
|
|
46
|
+
def process_complex_type(type_sym, value, idx, arg_types, arg_values, out_refs)
|
|
47
|
+
case type_sym.first
|
|
48
|
+
when :out
|
|
49
|
+
process_output_pointer(type_sym, idx, arg_types, arg_values, out_refs)
|
|
50
|
+
when :array
|
|
51
|
+
process_input_array(type_sym, value, arg_types, arg_values)
|
|
52
|
+
when :out_array
|
|
53
|
+
process_output_array(type_sym, value, idx, arg_types, arg_values, out_refs)
|
|
54
|
+
else
|
|
55
|
+
raise Error, "Unknown array/output form: #{type_sym.inspect}"
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def process_output_pointer(type_sym, idx, arg_types, arg_values, out_refs)
|
|
60
|
+
inner = type_sym[1]
|
|
61
|
+
ptr = TypeMap.allocate_output_pointer(inner)
|
|
62
|
+
out_refs << { index: idx, kind: :out, type: inner, ptr: ptr }
|
|
63
|
+
arg_types << TypeMap.to_fiddle_type(type_sym)
|
|
64
|
+
arg_values << ptr.to_i
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def process_input_array(type_sym, value, arg_types, arg_values)
|
|
68
|
+
base = type_sym[1]
|
|
69
|
+
values = Array(value)
|
|
70
|
+
ptr = TypeMap.allocate_array(base, values.length)
|
|
71
|
+
TypeMap.write_array(ptr, base, values)
|
|
72
|
+
arg_types << TypeMap.to_fiddle_type(type_sym)
|
|
73
|
+
arg_values << ptr.to_i
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def process_output_array(type_sym, value, idx, arg_types, arg_values, out_refs)
|
|
77
|
+
base = type_sym[1]
|
|
78
|
+
count = type_sym[2]
|
|
79
|
+
ptr = TypeMap.allocate_array(base, count)
|
|
80
|
+
|
|
81
|
+
# Optional initializer values
|
|
82
|
+
if value
|
|
83
|
+
vals = Array(value)
|
|
84
|
+
unless vals.length == count
|
|
85
|
+
raise Error,
|
|
86
|
+
"Initializer length #{vals.length} does not match out array size #{count}"
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
TypeMap.write_array(ptr, base, vals)
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
out_refs << { index: idx, kind: :out_array, base: base, count: count, ptr: ptr }
|
|
93
|
+
arg_types << TypeMap.to_fiddle_type(type_sym)
|
|
94
|
+
arg_values << ptr.to_i
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def process_callback(value, arg_types, arg_values, closures)
|
|
98
|
+
closure = CallbackHandler.create(value)
|
|
99
|
+
closures << closure # keep alive during call
|
|
100
|
+
arg_types << TypeMap.to_fiddle_type(:callback)
|
|
101
|
+
arg_values << closure
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
end
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'fiddle'
|
|
4
|
+
|
|
5
|
+
module Libcall
|
|
6
|
+
# Handles callback function pointer creation for FFI calls
|
|
7
|
+
class CallbackHandler
|
|
8
|
+
attr_reader :spec
|
|
9
|
+
|
|
10
|
+
def self.create(spec)
|
|
11
|
+
new(spec).create_closure
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def initialize(spec)
|
|
15
|
+
@spec = spec
|
|
16
|
+
validate_spec!
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def create_closure
|
|
20
|
+
ret_ty = TypeMap.to_fiddle_type(@spec[:ret])
|
|
21
|
+
arg_tys = @spec[:args].map { |a| TypeMap.to_fiddle_type(a) }
|
|
22
|
+
ruby_proc = build_proc
|
|
23
|
+
|
|
24
|
+
Fiddle::Closure::BlockCaller.new(ret_ty, arg_tys) do |*cb_args|
|
|
25
|
+
cooked_args = cook_arguments(cb_args)
|
|
26
|
+
ruby_proc.call(*cooked_args)
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
private
|
|
31
|
+
|
|
32
|
+
def validate_spec!
|
|
33
|
+
return if @spec.is_a?(Hash) && @spec[:kind] == :callback
|
|
34
|
+
|
|
35
|
+
raise Error, 'Invalid callback value; expected func signature and block'
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def build_proc
|
|
39
|
+
ctx = Object.new.extend(Fiddley::DSL)
|
|
40
|
+
ctx.instance_eval("proc #{@spec[:block]}", __FILE__, __LINE__)
|
|
41
|
+
rescue SyntaxError => e
|
|
42
|
+
raise Error, "Invalid Ruby block for callback: #{e.message}"
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def cook_arguments(cb_args)
|
|
46
|
+
cb_args.each_with_index.map do |v, i|
|
|
47
|
+
at = @spec[:args][i]
|
|
48
|
+
at == :voidp ? Fiddle::Pointer.new(v) : v
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
data/lib/libcall/caller.rb
CHANGED
|
@@ -15,53 +15,17 @@ module Libcall
|
|
|
15
15
|
end
|
|
16
16
|
|
|
17
17
|
def call
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
out_refs = []
|
|
18
|
+
processor = ArgumentProcessor.new(arg_pairs)
|
|
19
|
+
processed = processor.process
|
|
21
20
|
|
|
22
|
-
|
|
23
|
-
arg_types << TypeMap.to_fiddle_type(type_sym)
|
|
24
|
-
|
|
25
|
-
if type_sym.is_a?(Array)
|
|
26
|
-
case type_sym.first
|
|
27
|
-
when :out
|
|
28
|
-
inner = type_sym[1]
|
|
29
|
-
ptr = TypeMap.allocate_output_pointer(inner)
|
|
30
|
-
out_refs << { index: idx, kind: :out, type: inner, ptr: ptr }
|
|
31
|
-
arg_values << ptr.to_i
|
|
32
|
-
when :array
|
|
33
|
-
base = type_sym[1]
|
|
34
|
-
values = Array(value)
|
|
35
|
-
ptr = TypeMap.allocate_array(base, values.length)
|
|
36
|
-
TypeMap.write_array(ptr, base, values)
|
|
37
|
-
arg_values << ptr.to_i
|
|
38
|
-
when :out_array
|
|
39
|
-
base = type_sym[1]
|
|
40
|
-
count = type_sym[2]
|
|
41
|
-
ptr = TypeMap.allocate_array(base, count)
|
|
42
|
-
out_refs << { index: idx, kind: :out_array, base: base, count: count, ptr: ptr }
|
|
43
|
-
arg_values << ptr.to_i
|
|
44
|
-
else
|
|
45
|
-
raise Error, "Unknown array/output form: #{type_sym.inspect}"
|
|
46
|
-
end
|
|
47
|
-
else
|
|
48
|
-
arg_values << value
|
|
49
|
-
end
|
|
50
|
-
end
|
|
51
|
-
|
|
52
|
-
ret_type = TypeMap.to_fiddle_type(return_type)
|
|
53
|
-
|
|
54
|
-
handle = Fiddle.dlopen(lib_path)
|
|
55
|
-
func_ptr = handle[func_name]
|
|
56
|
-
func = Fiddle::Function.new(func_ptr, arg_types, ret_type)
|
|
57
|
-
|
|
58
|
-
raw_result = func.call(*arg_values)
|
|
21
|
+
raw_result = execute_function(processed)
|
|
59
22
|
formatted_result = format_result(raw_result, return_type)
|
|
60
23
|
|
|
61
|
-
|
|
24
|
+
output_reader = OutputReader.new(processed.out_refs)
|
|
25
|
+
if output_reader.empty?
|
|
62
26
|
formatted_result
|
|
63
27
|
else
|
|
64
|
-
{ result: formatted_result, outputs:
|
|
28
|
+
{ result: formatted_result, outputs: output_reader.read }
|
|
65
29
|
end
|
|
66
30
|
rescue Fiddle::DLError => e
|
|
67
31
|
raise Error, "Failed to load library or function: #{e.message}"
|
|
@@ -69,6 +33,14 @@ module Libcall
|
|
|
69
33
|
|
|
70
34
|
private
|
|
71
35
|
|
|
36
|
+
def execute_function(processed)
|
|
37
|
+
handle = Fiddle.dlopen(lib_path)
|
|
38
|
+
func_ptr = handle[func_name]
|
|
39
|
+
ret_type = TypeMap.to_fiddle_type(return_type)
|
|
40
|
+
func = Fiddle::Function.new(func_ptr, processed.arg_types, ret_type)
|
|
41
|
+
func.call(*processed.arg_values)
|
|
42
|
+
end
|
|
43
|
+
|
|
72
44
|
def format_result(result, type)
|
|
73
45
|
case type
|
|
74
46
|
when :void
|
|
@@ -90,22 +62,5 @@ module Libcall
|
|
|
90
62
|
result
|
|
91
63
|
end
|
|
92
64
|
end
|
|
93
|
-
|
|
94
|
-
def read_output_values(out_refs)
|
|
95
|
-
out_refs.map do |ref|
|
|
96
|
-
case ref[:kind]
|
|
97
|
-
when :out
|
|
98
|
-
value = TypeMap.read_output_pointer(ref[:ptr], ref[:type])
|
|
99
|
-
{ index: ref[:index], type: ref[:type].to_s, value: value }
|
|
100
|
-
when :out_array
|
|
101
|
-
base = ref[:base]
|
|
102
|
-
count = ref[:count]
|
|
103
|
-
values = TypeMap.read_array(ref[:ptr], base, count)
|
|
104
|
-
{ index: ref[:index], type: "#{base}[#{count}]", value: values }
|
|
105
|
-
else
|
|
106
|
-
raise Error, "Unknown out reference kind: #{ref[:kind]}"
|
|
107
|
-
end
|
|
108
|
-
end
|
|
109
|
-
end
|
|
110
65
|
end
|
|
111
66
|
end
|
data/lib/libcall/cli.rb
CHANGED
|
@@ -85,6 +85,11 @@ module Libcall
|
|
|
85
85
|
end
|
|
86
86
|
|
|
87
87
|
<<~BANNER
|
|
88
|
+
|
|
89
|
+
Program: libcall (Call C functions from the command line)
|
|
90
|
+
Version: #{Libcall::VERSION}
|
|
91
|
+
Source: https://github.com/kojix2/libcall
|
|
92
|
+
|
|
88
93
|
Usage: libcall [OPTIONS] <LIBRARY> <FUNCTION> (TYPE VALUE)...
|
|
89
94
|
|
|
90
95
|
Call C functions in shared libraries from the command line.
|
|
@@ -95,14 +100,14 @@ module Libcall
|
|
|
95
100
|
#{examples.lines.map { |line| line.chomp }.join("\n ")}
|
|
96
101
|
|
|
97
102
|
Options:
|
|
98
|
-
-l, --lib LIBRARY
|
|
99
|
-
-L, --lib-path PATH
|
|
100
|
-
-r, --ret TYPE
|
|
101
|
-
--dry-run
|
|
102
|
-
--json
|
|
103
|
-
--verbose
|
|
104
|
-
-h, --help
|
|
105
|
-
-v, --version
|
|
103
|
+
-l, --lib LIBRARY Library name to search for (e.g., -lm for libm)
|
|
104
|
+
-L, --lib-path PATH Add directory to library search path
|
|
105
|
+
-r, --ret TYPE Return type (default: void)
|
|
106
|
+
--dry-run Show what would be executed without calling
|
|
107
|
+
--json Output result in JSON format
|
|
108
|
+
--verbose Show detailed information
|
|
109
|
+
-h, --help Show this help message
|
|
110
|
+
-v, --version Show version information
|
|
106
111
|
BANNER
|
|
107
112
|
end
|
|
108
113
|
|
|
@@ -156,8 +161,17 @@ module Libcall
|
|
|
156
161
|
type_sym = Parser.parse_type(type_tok)
|
|
157
162
|
|
|
158
163
|
# TYPE that represents an output pointer/array does not require a value
|
|
164
|
+
# For out:TYPE[N], allow an optional comma-separated initializer list right after the type.
|
|
159
165
|
if type_sym.is_a?(Array) && %i[out out_array].include?(type_sym.first)
|
|
160
|
-
|
|
166
|
+
if type_sym.first == :out_array && i < argv.length && argv[i].include?(',')
|
|
167
|
+
init_tok = argv[i]
|
|
168
|
+
i += 1
|
|
169
|
+
base = type_sym[1]
|
|
170
|
+
values = Parser.coerce_value([:array, base], init_tok)
|
|
171
|
+
arg_pairs << [type_sym, values]
|
|
172
|
+
else
|
|
173
|
+
arg_pairs << [type_sym, nil]
|
|
174
|
+
end
|
|
161
175
|
next
|
|
162
176
|
end
|
|
163
177
|
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# A tiny helper layer inspired by fiddley (BSD-2-Clause) to offer
|
|
4
|
+
# convenient, DSL-friendly utilities on top of Ruby's Fiddle.
|
|
5
|
+
# This is intentionally small and tailored for libcall use-cases.
|
|
6
|
+
|
|
7
|
+
require 'fiddle'
|
|
8
|
+
|
|
9
|
+
module Libcall
|
|
10
|
+
module Fiddley
|
|
11
|
+
# Small helper methods intended for use inside callback blocks
|
|
12
|
+
module DSL
|
|
13
|
+
module_function
|
|
14
|
+
|
|
15
|
+
def ptr(x)
|
|
16
|
+
Fiddle::Pointer.new(Integer(x))
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def read(type, p)
|
|
20
|
+
t = type.is_a?(Symbol) ? type : type.to_s
|
|
21
|
+
t_sym = Libcall::TypeMap.lookup(t) || (type.is_a?(Symbol) ? type : nil)
|
|
22
|
+
raise Libcall::Error, "unknown read type: #{type}" unless t_sym
|
|
23
|
+
|
|
24
|
+
pp = p.is_a?(Fiddle::Pointer) ? p : Fiddle::Pointer.new(Integer(p))
|
|
25
|
+
Libcall::TypeMap.read_output_pointer(pp, t_sym)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# Typed short-hands
|
|
29
|
+
def char(p) = read(:char, p)
|
|
30
|
+
def uchar(p) = read(:uchar, p)
|
|
31
|
+
def short(p) = read(:short, p)
|
|
32
|
+
def ushort(p) = read(:ushort, p)
|
|
33
|
+
def int(p) = read(:int, p)
|
|
34
|
+
def uint(p) = read(:uint, p)
|
|
35
|
+
def long(p) = read(:long, p)
|
|
36
|
+
def ulong(p) = read(:ulong, p)
|
|
37
|
+
def long_long(p) = read(:long_long, p)
|
|
38
|
+
def ulong_long(p) = read(:ulong_long, p)
|
|
39
|
+
def float(p) = read(:float, p)
|
|
40
|
+
def double(p) = read(:double, p)
|
|
41
|
+
def cstr(p) = read(:string, p)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
module Utils
|
|
45
|
+
module_function
|
|
46
|
+
|
|
47
|
+
# Return size in bytes for a given type symbol.
|
|
48
|
+
|
|
49
|
+
def sizeof(type)
|
|
50
|
+
Libcall::TypeMap.sizeof(type)
|
|
51
|
+
rescue StandardError => e
|
|
52
|
+
raise Libcall::Error, "unknown type for sizeof: #{type} (#{e.message})"
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# Convert a type symbol to a Fiddle type constant.
|
|
56
|
+
def to_fiddle_type(type)
|
|
57
|
+
Libcall::TypeMap.to_fiddle_type(type)
|
|
58
|
+
rescue StandardError => e
|
|
59
|
+
raise Libcall::Error, "unknown type for to_fiddle_type: #{type} (#{e.message})"
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# Pack template for array values of given base type.
|
|
63
|
+
def array_pack_template(type)
|
|
64
|
+
Libcall::TypeMap.pack_template(type)
|
|
65
|
+
rescue StandardError => e
|
|
66
|
+
raise Libcall::Error, "Unsupported array base type: #{type} (#{e.message})"
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# Convert Ruby array of numbers to a binary string for the given type
|
|
70
|
+
def array2str(type, array)
|
|
71
|
+
array.pack("#{array_pack_template(type)}*")
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
# Convert binary string to Ruby array of the given type
|
|
75
|
+
def str2array(type, str)
|
|
76
|
+
str.unpack("#{array_pack_template(type)}*")
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
# Minimal memory buffer wrapper to make building args/arrays convenient
|
|
81
|
+
class MemoryPointer
|
|
82
|
+
attr_reader :size
|
|
83
|
+
|
|
84
|
+
def initialize(type, count = 1)
|
|
85
|
+
@type = type
|
|
86
|
+
@size = Utils.sizeof(type) * count
|
|
87
|
+
@ptr = Fiddle::Pointer.malloc(@size)
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def to_ptr
|
|
91
|
+
@ptr
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def address
|
|
95
|
+
@ptr.to_i
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def write_array(type, values)
|
|
99
|
+
data = Utils.array2str(type, Array(values))
|
|
100
|
+
@ptr[0, data.bytesize] = data
|
|
101
|
+
self
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def read_array(type, count)
|
|
105
|
+
bytes = Utils.sizeof(type) * count
|
|
106
|
+
Utils.str2array(type, @ptr[0, bytes])
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def put_bytes(offset, str)
|
|
110
|
+
@ptr[offset, str.bytesize] = str
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
def write_bytes(str)
|
|
114
|
+
put_bytes(0, str)
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
def get_bytes(offset, len)
|
|
118
|
+
@ptr[offset, len]
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
def read_bytes(len)
|
|
122
|
+
get_bytes(0, len)
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
# Return Fiddle::Pointer stored at this pointer (read void*)
|
|
126
|
+
def read_pointer
|
|
127
|
+
to_ptr.ptr
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
# Wrap Fiddle::Closure::BlockCaller with friendlier type mapping
|
|
132
|
+
class Function < Fiddle::Closure::BlockCaller
|
|
133
|
+
def initialize(ret, params, &blk)
|
|
134
|
+
r = Utils.to_fiddle_type(ret)
|
|
135
|
+
p = Array(params).map { |t| Utils.to_fiddle_type(t) }
|
|
136
|
+
super(r, p, &blk)
|
|
137
|
+
end
|
|
138
|
+
end
|
|
139
|
+
end
|
|
140
|
+
end
|
|
@@ -19,6 +19,9 @@ module Libcall
|
|
|
19
19
|
|
|
20
20
|
# Find library by name (e.g., "m" -> "/lib/x86_64-linux-gnu/libm.so.6")
|
|
21
21
|
def find(lib_name)
|
|
22
|
+
# Normalize "-lfoo" style if passed as is (harmless if not present)
|
|
23
|
+
lib_name = lib_name.sub(/\A-l/, '')
|
|
24
|
+
|
|
22
25
|
# If it's a path, return as-is
|
|
23
26
|
return File.expand_path(lib_name) if path_like?(lib_name)
|
|
24
27
|
|
|
@@ -52,6 +55,30 @@ module Libcall
|
|
|
52
55
|
resolved = resolve_by_name_in_paths(lib_name, search_paths)
|
|
53
56
|
return resolved if resolved
|
|
54
57
|
|
|
58
|
+
# On macOS, try common library naming conventions for dyld shared cache
|
|
59
|
+
# (e.g., libSystem.B.dylib exists in cache but not on filesystem)
|
|
60
|
+
if Platform.darwin?
|
|
61
|
+
prefixes = lib_name.start_with?('lib') ? [''] : ['lib', '']
|
|
62
|
+
extensions = Platform.library_extensions
|
|
63
|
+
|
|
64
|
+
prefixes.product(extensions).each do |prefix, ext|
|
|
65
|
+
candidates = [
|
|
66
|
+
"#{prefix}#{lib_name}#{ext}",
|
|
67
|
+
"#{prefix}#{lib_name}.B#{ext}", # Common pattern: libSystem.B.dylib
|
|
68
|
+
"#{prefix}#{lib_name}.A#{ext}" # Also try .A variant
|
|
69
|
+
]
|
|
70
|
+
|
|
71
|
+
candidates.each do |candidate|
|
|
72
|
+
# Test if dyld can resolve this name
|
|
73
|
+
|
|
74
|
+
Fiddle.dlopen(candidate)
|
|
75
|
+
return candidate # Return the name itself, let Fiddle resolve it
|
|
76
|
+
rescue Fiddle::DLError
|
|
77
|
+
next
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
|
|
55
82
|
raise Error, "Library not found: #{lib_name} (searched in: #{search_paths.join(', ')})"
|
|
56
83
|
end
|
|
57
84
|
|
|
@@ -134,11 +161,20 @@ module Libcall
|
|
|
134
161
|
full_path = File.join(path, name)
|
|
135
162
|
return File.expand_path(full_path) if File.file?(full_path)
|
|
136
163
|
|
|
137
|
-
# Check for versioned libraries
|
|
164
|
+
# Check for versioned libraries:
|
|
165
|
+
# - Linux: libm.so.6 => "#{name}.*"
|
|
166
|
+
# - macOS: libSystem.B.dylib, libcrypto.3.dylib => "#{prefix}#{lib_name}.*#{ext}"
|
|
138
167
|
next if ext.empty?
|
|
139
168
|
|
|
140
|
-
|
|
141
|
-
|
|
169
|
+
patterns = [
|
|
170
|
+
File.join(path, "#{name}.*"),
|
|
171
|
+
File.join(path, "#{prefix}#{lib_name}.*#{ext}")
|
|
172
|
+
]
|
|
173
|
+
|
|
174
|
+
patterns.each do |pattern|
|
|
175
|
+
matches = Dir.glob(pattern).select { |f| File.file?(f) }
|
|
176
|
+
return File.expand_path(matches.first) unless matches.empty?
|
|
177
|
+
end
|
|
142
178
|
end
|
|
143
179
|
|
|
144
180
|
nil
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Libcall
|
|
4
|
+
# Handles reading output parameters after FFI calls
|
|
5
|
+
class OutputReader
|
|
6
|
+
def initialize(out_refs)
|
|
7
|
+
@out_refs = out_refs
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def read
|
|
11
|
+
@out_refs.map do |ref|
|
|
12
|
+
case ref[:kind]
|
|
13
|
+
when :out
|
|
14
|
+
read_single_output(ref)
|
|
15
|
+
when :out_array
|
|
16
|
+
read_array_output(ref)
|
|
17
|
+
else
|
|
18
|
+
raise Error, "Unknown out reference kind: #{ref[:kind]}"
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def empty?
|
|
24
|
+
@out_refs.empty?
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
private
|
|
28
|
+
|
|
29
|
+
def read_single_output(ref)
|
|
30
|
+
value = TypeMap.read_output_pointer(ref[:ptr], ref[:type])
|
|
31
|
+
{ index: ref[:index], type: ref[:type].to_s, value: value }
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def read_array_output(ref)
|
|
35
|
+
values = TypeMap.read_array(ref[:ptr], ref[:base], ref[:count])
|
|
36
|
+
{
|
|
37
|
+
index: ref[:index],
|
|
38
|
+
type: "#{ref[:base]}[#{ref[:count]}]",
|
|
39
|
+
value: values
|
|
40
|
+
}
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
data/lib/libcall/parser.rb
CHANGED
|
@@ -7,6 +7,9 @@ module Libcall
|
|
|
7
7
|
class Parser
|
|
8
8
|
# Pair-only API helpers
|
|
9
9
|
def self.parse_type(type_str)
|
|
10
|
+
# Callback function pointer: func/callback 'ret(arg,...) { |...| ... }'
|
|
11
|
+
return :callback if %w[func callback].include?(type_str)
|
|
12
|
+
|
|
10
13
|
# Output array spec: out:TYPE[N]
|
|
11
14
|
if type_str.start_with?('out:') && type_str.match(/^out:(.+)\[(\d+)\]$/)
|
|
12
15
|
base = Regexp.last_match(1)
|
|
@@ -51,6 +54,52 @@ module Libcall
|
|
|
51
54
|
end
|
|
52
55
|
|
|
53
56
|
def self.coerce_value(type_sym, token)
|
|
57
|
+
# Callback value: signature + Ruby block
|
|
58
|
+
if type_sym == :callback
|
|
59
|
+
src = strip_quotes(token.to_s)
|
|
60
|
+
m = src.match(/\A\s*([^(\s]+)\s*\(([^)]*)\)\s*(\{.*\})\s*\z/m)
|
|
61
|
+
raise Error, "Invalid callback spec: #{src}" unless m
|
|
62
|
+
|
|
63
|
+
ret_s, args_s, block_src = m.captures.map(&:strip)
|
|
64
|
+
if block_src =~ /\{\s*\|/
|
|
65
|
+
raise Error,
|
|
66
|
+
'Explicit block parameters are not supported; name your C args and omit block params (e.g., int(int a,int b){a+b})'
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
ret_sym = TypeMap.lookup(ret_s)
|
|
70
|
+
raise Error, "Unknown callback return type: #{ret_s}" unless ret_sym
|
|
71
|
+
|
|
72
|
+
pairs = if args_s.empty?
|
|
73
|
+
[]
|
|
74
|
+
else
|
|
75
|
+
args_s.split(',').map do |raw|
|
|
76
|
+
# Normalize pointer syntax: "void *a", "void * a", "void*a" → "void* a"
|
|
77
|
+
normalized = raw.strip.gsub(/\s*\*\s*/, '* ').strip
|
|
78
|
+
|
|
79
|
+
if (mm = normalized.match(/\A(.+?)\s+([A-Za-z_][A-Za-z0-9_]*)\z/))
|
|
80
|
+
type_part = mm[1].strip
|
|
81
|
+
name_part = mm[2]
|
|
82
|
+
else
|
|
83
|
+
type_part = normalized
|
|
84
|
+
name_part = nil
|
|
85
|
+
end
|
|
86
|
+
sym = TypeMap.lookup(type_part)
|
|
87
|
+
raise Error, "Unknown callback arg type: #{raw.strip}" unless sym
|
|
88
|
+
|
|
89
|
+
[sym, name_part]
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
arg_syms = pairs.map(&:first)
|
|
94
|
+
names_with_nils = pairs.map(&:last)
|
|
95
|
+
if !names_with_nils.empty? && names_with_nils.all?
|
|
96
|
+
block_src = block_src.sub(/\{\s*/) do
|
|
97
|
+
"{|#{names_with_nils.join(',')}| "
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
return { kind: :callback, ret: ret_sym, args: arg_syms, block: block_src }
|
|
102
|
+
end
|
|
54
103
|
# Input array values: comma-separated
|
|
55
104
|
if type_sym.is_a?(Array) && type_sym.first == :array
|
|
56
105
|
base = type_sym[1]
|
data/lib/libcall/type_map.rb
CHANGED
|
@@ -27,6 +27,7 @@ module Libcall
|
|
|
27
27
|
'void' => :void,
|
|
28
28
|
# Common C type names
|
|
29
29
|
'char' => :char,
|
|
30
|
+
'uchar' => :uchar,
|
|
30
31
|
'short' => :short,
|
|
31
32
|
'ushort' => :ushort,
|
|
32
33
|
'int' => :int,
|
|
@@ -35,6 +36,21 @@ module Libcall
|
|
|
35
36
|
'ulong' => :ulong,
|
|
36
37
|
'float' => :float,
|
|
37
38
|
'double' => :double,
|
|
39
|
+
# C-style pointer aliases
|
|
40
|
+
'void*' => :voidp,
|
|
41
|
+
'const void*' => :voidp,
|
|
42
|
+
'const_void*' => :voidp,
|
|
43
|
+
'const_voidp' => :voidp,
|
|
44
|
+
# Underscored variants
|
|
45
|
+
'unsigned_char' => :uchar,
|
|
46
|
+
'unsigned_short' => :ushort,
|
|
47
|
+
'unsigned_int' => :uint,
|
|
48
|
+
'unsigned_long' => :ulong,
|
|
49
|
+
'long_long' => :long_long,
|
|
50
|
+
'unsigned_long_long' => :ulong_long,
|
|
51
|
+
# Short aliases
|
|
52
|
+
'unsigned' => :uint,
|
|
53
|
+
'signed' => :int,
|
|
38
54
|
# Extended type names (stdint-like)
|
|
39
55
|
'int8' => :char,
|
|
40
56
|
'uint8' => :uchar,
|
|
@@ -46,6 +62,15 @@ module Libcall
|
|
|
46
62
|
'uint64' => :ulong_long,
|
|
47
63
|
'float32' => :float,
|
|
48
64
|
'float64' => :double,
|
|
65
|
+
# C99/C11 standard types with _t suffix
|
|
66
|
+
'int8_t' => :char,
|
|
67
|
+
'uint8_t' => :uchar,
|
|
68
|
+
'int16_t' => :short,
|
|
69
|
+
'uint16_t' => :ushort,
|
|
70
|
+
'int32_t' => :int,
|
|
71
|
+
'uint32_t' => :uint,
|
|
72
|
+
'int64_t' => :long_long,
|
|
73
|
+
'uint64_t' => :ulong_long,
|
|
49
74
|
# Size and pointer-sized integers
|
|
50
75
|
'size_t' => :ulong,
|
|
51
76
|
'ssize_t' => :long,
|
|
@@ -96,18 +121,32 @@ module Libcall
|
|
|
96
121
|
return Fiddle::TYPE_VOIDP if %i[out array out_array].include?(tag)
|
|
97
122
|
end
|
|
98
123
|
|
|
124
|
+
# Callback function pointers are passed as void*
|
|
125
|
+
return Fiddle::TYPE_VOIDP if type_sym == :callback
|
|
126
|
+
|
|
99
127
|
case type_sym
|
|
128
|
+
# Void type
|
|
100
129
|
when :void then Fiddle::TYPE_VOID
|
|
130
|
+
# Integer types (8-bit)
|
|
101
131
|
when :char then Fiddle::TYPE_CHAR
|
|
102
132
|
when :uchar then Fiddle::TYPE_UCHAR
|
|
133
|
+
# Integer types (16-bit)
|
|
103
134
|
when :short then Fiddle::TYPE_SHORT
|
|
104
135
|
when :ushort then Fiddle::TYPE_USHORT
|
|
136
|
+
# Integer types (32-bit)
|
|
105
137
|
when :int, :uint then Fiddle::TYPE_INT
|
|
138
|
+
# Integer types (platform-dependent)
|
|
106
139
|
when :long, :ulong then Fiddle::TYPE_LONG
|
|
140
|
+
# Integer types (64-bit)
|
|
107
141
|
when :long_long, :ulong_long then Fiddle::TYPE_LONG_LONG
|
|
142
|
+
# Floating point types
|
|
108
143
|
when :float then Fiddle::TYPE_FLOAT
|
|
109
144
|
when :double then Fiddle::TYPE_DOUBLE
|
|
110
|
-
|
|
145
|
+
# Pointer types
|
|
146
|
+
when :voidp, :pointer then Fiddle::TYPE_VOIDP
|
|
147
|
+
when :string then Fiddle::TYPE_VOIDP
|
|
148
|
+
# Special types
|
|
149
|
+
when :size_t then Fiddle::TYPE_SIZE_T
|
|
111
150
|
else
|
|
112
151
|
raise Error, "Unknown Fiddle type: #{type_sym}"
|
|
113
152
|
end
|
|
@@ -116,14 +155,23 @@ module Libcall
|
|
|
116
155
|
# Get the size in bytes for a type symbol
|
|
117
156
|
def self.sizeof(type_sym)
|
|
118
157
|
case type_sym
|
|
158
|
+
# Integer types (8-bit)
|
|
119
159
|
when :char, :uchar then Fiddle::SIZEOF_CHAR
|
|
160
|
+
# Integer types (16-bit)
|
|
120
161
|
when :short, :ushort then Fiddle::SIZEOF_SHORT
|
|
162
|
+
# Integer types (32-bit)
|
|
121
163
|
when :int, :uint then Fiddle::SIZEOF_INT
|
|
164
|
+
# Integer types (platform-dependent)
|
|
122
165
|
when :long, :ulong then Fiddle::SIZEOF_LONG
|
|
166
|
+
# Integer types (64-bit)
|
|
123
167
|
when :long_long, :ulong_long then Fiddle::SIZEOF_LONG_LONG
|
|
168
|
+
# Floating point types
|
|
124
169
|
when :float then Fiddle::SIZEOF_FLOAT
|
|
125
170
|
when :double then Fiddle::SIZEOF_DOUBLE
|
|
126
|
-
|
|
171
|
+
# Pointer types
|
|
172
|
+
when :voidp, :pointer, :string then Fiddle::SIZEOF_VOIDP
|
|
173
|
+
# Special types
|
|
174
|
+
when :size_t then Fiddle::SIZEOF_SIZE_T
|
|
127
175
|
else
|
|
128
176
|
raise Error, "Cannot get size for type: #{type_sym}"
|
|
129
177
|
end
|
|
@@ -140,18 +188,26 @@ module Libcall
|
|
|
140
188
|
# Read value from output pointer
|
|
141
189
|
def self.read_output_pointer(ptr, type_sym)
|
|
142
190
|
case type_sym
|
|
191
|
+
# Integer types (8-bit)
|
|
143
192
|
when :char then ptr[0, Fiddle::SIZEOF_CHAR].unpack1('c')
|
|
144
193
|
when :uchar then ptr[0, Fiddle::SIZEOF_CHAR].unpack1('C')
|
|
194
|
+
# Integer types (16-bit)
|
|
145
195
|
when :short then ptr[0, Fiddle::SIZEOF_SHORT].unpack1('s')
|
|
146
196
|
when :ushort then ptr[0, Fiddle::SIZEOF_SHORT].unpack1('S')
|
|
197
|
+
# Integer types (32-bit)
|
|
147
198
|
when :int then ptr[0, Fiddle::SIZEOF_INT].unpack1('i')
|
|
148
199
|
when :uint then ptr[0, Fiddle::SIZEOF_INT].unpack1('I')
|
|
200
|
+
# Integer types (platform-dependent)
|
|
149
201
|
when :long then ptr[0, Fiddle::SIZEOF_LONG].unpack1('l!')
|
|
150
202
|
when :ulong then ptr[0, Fiddle::SIZEOF_LONG].unpack1('L!')
|
|
203
|
+
# Integer types (64-bit)
|
|
151
204
|
when :long_long then ptr[0, Fiddle::SIZEOF_LONG_LONG].unpack1('q')
|
|
152
205
|
when :ulong_long then ptr[0, Fiddle::SIZEOF_LONG_LONG].unpack1('Q')
|
|
206
|
+
# Floating point types
|
|
153
207
|
when :float then ptr[0, Fiddle::SIZEOF_FLOAT].unpack1('f')
|
|
154
208
|
when :double then ptr[0, Fiddle::SIZEOF_DOUBLE].unpack1('d')
|
|
209
|
+
# Pointer types
|
|
210
|
+
when :voidp then format('0x%x', ptr[0, Fiddle::SIZEOF_VOIDP].unpack1('J'))
|
|
155
211
|
when :string
|
|
156
212
|
addr = ptr[0, Fiddle::SIZEOF_VOIDP].unpack1('J')
|
|
157
213
|
return '(null)' if addr.zero?
|
|
@@ -161,12 +217,16 @@ module Libcall
|
|
|
161
217
|
rescue StandardError
|
|
162
218
|
format('0x%x', addr)
|
|
163
219
|
end
|
|
164
|
-
when :voidp then format('0x%x', ptr[0, Fiddle::SIZEOF_VOIDP].unpack1('J'))
|
|
165
220
|
else
|
|
166
221
|
raise Error, "Cannot read output value for type: #{type_sym}"
|
|
167
222
|
end
|
|
168
223
|
end
|
|
169
224
|
|
|
225
|
+
# Read a single scalar value at address (helper for callbacks)
|
|
226
|
+
def self.read_scalar(ptr, type_sym)
|
|
227
|
+
read_output_pointer(ptr, type_sym)
|
|
228
|
+
end
|
|
229
|
+
|
|
170
230
|
# Allocate memory for an array of base type and count elements
|
|
171
231
|
def self.allocate_array(base_type, count)
|
|
172
232
|
Fiddle::Pointer.malloc(sizeof(base_type) * count)
|
|
@@ -189,18 +249,28 @@ module Libcall
|
|
|
189
249
|
|
|
190
250
|
def self.pack_template(base_type)
|
|
191
251
|
case base_type
|
|
252
|
+
# Integer types (8-bit)
|
|
192
253
|
when :char then 'c'
|
|
193
254
|
when :uchar then 'C'
|
|
255
|
+
# Integer types (16-bit)
|
|
194
256
|
when :short then 's'
|
|
195
257
|
when :ushort then 'S'
|
|
258
|
+
# Integer types (32-bit)
|
|
196
259
|
when :int then 'i'
|
|
197
260
|
when :uint then 'I'
|
|
261
|
+
# Integer types (platform-dependent)
|
|
198
262
|
when :long then 'l!'
|
|
199
263
|
when :ulong then 'L!'
|
|
264
|
+
# Integer types (64-bit)
|
|
200
265
|
when :long_long then 'q'
|
|
201
266
|
when :ulong_long then 'Q'
|
|
267
|
+
# Floating point types
|
|
202
268
|
when :float then 'f'
|
|
203
269
|
when :double then 'd'
|
|
270
|
+
# Pointer types
|
|
271
|
+
when :pointer, :voidp then 'J'
|
|
272
|
+
# Special types
|
|
273
|
+
when :size_t then (Fiddle::SIZEOF_VOIDP == Fiddle::SIZEOF_LONG ? 'L!' : 'Q')
|
|
204
274
|
else
|
|
205
275
|
raise Error, "Unsupported array base type: #{base_type}"
|
|
206
276
|
end
|
data/lib/libcall/version.rb
CHANGED
data/lib/libcall.rb
CHANGED
|
@@ -3,8 +3,12 @@
|
|
|
3
3
|
require_relative 'libcall/version'
|
|
4
4
|
require_relative 'libcall/parser'
|
|
5
5
|
require_relative 'libcall/library_finder'
|
|
6
|
+
require_relative 'libcall/callback_handler'
|
|
7
|
+
require_relative 'libcall/output_reader'
|
|
8
|
+
require_relative 'libcall/argument_processor'
|
|
6
9
|
require_relative 'libcall/caller'
|
|
7
10
|
require_relative 'libcall/cli'
|
|
11
|
+
require_relative 'libcall/fiddley'
|
|
8
12
|
|
|
9
13
|
module Libcall
|
|
10
14
|
class Error < StandardError; end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: libcall
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.0.
|
|
4
|
+
version: 0.0.4
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- kojix2
|
|
@@ -47,9 +47,13 @@ files:
|
|
|
47
47
|
- README.md
|
|
48
48
|
- exe/libcall
|
|
49
49
|
- lib/libcall.rb
|
|
50
|
+
- lib/libcall/argument_processor.rb
|
|
51
|
+
- lib/libcall/callback_handler.rb
|
|
50
52
|
- lib/libcall/caller.rb
|
|
51
53
|
- lib/libcall/cli.rb
|
|
54
|
+
- lib/libcall/fiddley.rb
|
|
52
55
|
- lib/libcall/library_finder.rb
|
|
56
|
+
- lib/libcall/output_reader.rb
|
|
53
57
|
- lib/libcall/parser.rb
|
|
54
58
|
- lib/libcall/platform.rb
|
|
55
59
|
- lib/libcall/type_map.rb
|