libcall 0.0.3 → 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 +95 -64
- data/lib/libcall/argument_processor.rb +104 -0
- data/lib/libcall/callback_handler.rb +52 -0
- data/lib/libcall/caller.rb +14 -96
- data/lib/libcall/cli.rb +13 -8
- data/lib/libcall/fiddley.rb +7 -23
- data/lib/libcall/library_finder.rb +39 -3
- data/lib/libcall/output_reader.rb +43 -0
- data/lib/libcall/parser.rb +33 -13
- data/lib/libcall/type_map.rb +40 -3
- data/lib/libcall/version.rb +1 -1
- data/lib/libcall.rb +3 -0
- metadata +5 -2
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,14 +12,6 @@ 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
|
|
@@ -30,30 +22,10 @@ libcall -lm sqrt double 16 -r double # => 4.0
|
|
|
30
22
|
libcall -lc strlen string "hello" -r usize # => 5
|
|
31
23
|
```
|
|
32
24
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
Pass arguments as TYPE VALUE pairs (single-token suffix style has been removed):
|
|
36
|
-
|
|
37
|
-
- Examples: `int 10`, `double -3.14`, `string "hello"`
|
|
38
|
-
- Negative values are safe (not treated as options): `int -23`
|
|
39
|
-
|
|
40
|
-
Pointers and null:
|
|
41
|
-
|
|
42
|
-
- Use `ptr` (or `pointer`) to pass raw addresses as integers
|
|
43
|
-
- Use `null`, `nil`, `NULL`, or `0` to pass a null pointer
|
|
44
|
-
|
|
45
|
-
```sh
|
|
46
|
-
# Pass a null pointer to a function taking const char*
|
|
47
|
-
libcall -ltest str_length ptr null -r i32
|
|
48
|
-
# => 0
|
|
49
|
-
```
|
|
50
|
-
|
|
51
|
-
End of options `--`:
|
|
52
|
-
|
|
53
|
-
- Use `--` to stop option parsing if a value starts with `-`
|
|
25
|
+
## Usage
|
|
54
26
|
|
|
55
27
|
```sh
|
|
56
|
-
libcall
|
|
28
|
+
libcall [OPTIONS] <LIBRARY> <FUNCTION> (TYPE VALUE)...
|
|
57
29
|
```
|
|
58
30
|
|
|
59
31
|
### Options
|
|
@@ -74,12 +46,6 @@ Library search:
|
|
|
74
46
|
|
|
75
47
|
### More Examples
|
|
76
48
|
|
|
77
|
-
TYPE/VALUE pairs with `-r` before function
|
|
78
|
-
|
|
79
|
-
```sh
|
|
80
|
-
libcall -lm -r double fabs double -5.5 # => 5.5
|
|
81
|
-
```
|
|
82
|
-
|
|
83
49
|
Output parameter with libm
|
|
84
50
|
|
|
85
51
|
```sh
|
|
@@ -110,40 +76,89 @@ libcall --dry-run -lc getpid -r int
|
|
|
110
76
|
# Return: int
|
|
111
77
|
```
|
|
112
78
|
|
|
113
|
-
|
|
79
|
+
## Type Reference
|
|
114
80
|
|
|
115
|
-
|
|
116
|
-
libcall msvcrt.dll sqrt double 16.0 -r f64 # => 4.0
|
|
117
|
-
```
|
|
81
|
+
libcall supports multiple naming conventions for types, making it easy to work with C libraries.
|
|
118
82
|
|
|
119
|
-
|
|
83
|
+
### Integer Types
|
|
120
84
|
|
|
121
|
-
|
|
122
|
-
|
|
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**)
|
|
123
134
|
```
|
|
124
135
|
|
|
125
|
-
|
|
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` |
|
|
143
|
+
|
|
144
|
+
### Callback Types
|
|
126
145
|
|
|
127
|
-
|
|
|
128
|
-
|
|
|
129
|
-
| `
|
|
130
|
-
| `
|
|
131
|
-
| `i16` | `short` (≈ `int16_t`) | -32768 to 32767 |
|
|
132
|
-
| `u16` | `unsigned short` (≈ `uint16_t`) | 0 to 65535 |
|
|
133
|
-
| `i32` | `int` (≈ `int32_t`) | typical 32-bit int |
|
|
134
|
-
| `u32` | `unsigned int` (≈ `uint32_t`) | unsigned 32-bit |
|
|
135
|
-
| `i64` | `long long` (≈ `int64_t`) | 64-bit |
|
|
136
|
-
| `u64` | `unsigned long long` (≈ `uint64_t`) | 64-bit |
|
|
137
|
-
| `f32` | `float` | single precision |
|
|
138
|
-
| `f64` | `double` | double precision |
|
|
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 |
|
|
139
150
|
|
|
140
|
-
|
|
151
|
+
### Argument Syntax
|
|
152
|
+
|
|
153
|
+
Pass arguments as TYPE VALUE pairs:
|
|
141
154
|
|
|
142
|
-
|
|
143
|
-
-
|
|
144
|
-
-
|
|
155
|
+
```sh
|
|
156
|
+
libcall -lm sqrt double 16.0 -r f64
|
|
157
|
+
libcall -lc strlen string "hello" -r usize
|
|
158
|
+
```
|
|
145
159
|
|
|
146
|
-
|
|
160
|
+
- Null pointers: Use `null`, `NULL`, `nil`, or `0`
|
|
161
|
+
- Negative numbers work as expected (e.g., `double -3.14`)
|
|
147
162
|
|
|
148
163
|
## pkg-config Support
|
|
149
164
|
|
|
@@ -190,7 +205,7 @@ libcall -lSystem arc4random_buf out:uchar[16] size_t 16 -r void
|
|
|
190
205
|
|
|
191
206
|
Pass a C function pointer via a Ruby callback. Use `func` or `callback` with a quoted spec:
|
|
192
207
|
|
|
193
|
-
- Syntax: `func 'RET(
|
|
208
|
+
- Syntax: `func 'RET(TYPE name, TYPE name, ...){ ruby_code }'` (alias: `callback ...`)
|
|
194
209
|
- Inside the block, helper methods from `Libcall::Fiddley::DSL` are available:
|
|
195
210
|
- `int(ptr)`, `double(ptr)`, `cstr(ptr)` read values from pointers
|
|
196
211
|
- `read(:type, ptr)` reads any supported type; `ptr(addr)` makes a pointer
|
|
@@ -201,7 +216,7 @@ Quick examples
|
|
|
201
216
|
# Fixture function: int32_t apply_i32(int32_t, int32_t, int32_t (*)(int32_t,int32_t))
|
|
202
217
|
libcall -ltest -L test/fixtures/libtest/build apply_i32 \
|
|
203
218
|
int 3 int 5 \
|
|
204
|
-
func 'int(int,int){
|
|
219
|
+
func 'int(int a,int b){ a + b }' \
|
|
205
220
|
-r i32
|
|
206
221
|
# => 8
|
|
207
222
|
```
|
|
@@ -212,7 +227,7 @@ libcall -lc qsort \
|
|
|
212
227
|
out:int[4] 4,2,3,1 \
|
|
213
228
|
size_t 4 \
|
|
214
229
|
size_t 4 \
|
|
215
|
-
callback 'int(void
|
|
230
|
+
callback 'int(void* a, void* b){ int(a) <=> int(b) }' \
|
|
216
231
|
-r void
|
|
217
232
|
# Output parameters:
|
|
218
233
|
# [0] int[4] = [1, 2, 3, 4]
|
|
@@ -233,6 +248,22 @@ FFI calls are inherently unsafe. You must:
|
|
|
233
248
|
|
|
234
249
|
Incorrect usage can crash your program.
|
|
235
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
|
+
|
|
236
267
|
## Development
|
|
237
268
|
|
|
238
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,90 +15,17 @@ module Libcall
|
|
|
15
15
|
end
|
|
16
16
|
|
|
17
17
|
def call
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
out_refs = []
|
|
21
|
-
closures = []
|
|
18
|
+
processor = ArgumentProcessor.new(arg_pairs)
|
|
19
|
+
processed = processor.process
|
|
22
20
|
|
|
23
|
-
|
|
24
|
-
arg_types << TypeMap.to_fiddle_type(type_sym)
|
|
25
|
-
|
|
26
|
-
if type_sym.is_a?(Array)
|
|
27
|
-
case type_sym.first
|
|
28
|
-
when :out
|
|
29
|
-
inner = type_sym[1]
|
|
30
|
-
ptr = TypeMap.allocate_output_pointer(inner)
|
|
31
|
-
out_refs << { index: idx, kind: :out, type: inner, ptr: ptr }
|
|
32
|
-
arg_values << ptr.to_i
|
|
33
|
-
when :array
|
|
34
|
-
base = type_sym[1]
|
|
35
|
-
values = Array(value)
|
|
36
|
-
ptr = TypeMap.allocate_array(base, values.length)
|
|
37
|
-
TypeMap.write_array(ptr, base, values)
|
|
38
|
-
arg_values << ptr.to_i
|
|
39
|
-
when :out_array
|
|
40
|
-
base = type_sym[1]
|
|
41
|
-
count = type_sym[2]
|
|
42
|
-
ptr = TypeMap.allocate_array(base, count)
|
|
43
|
-
# Optional initializer values
|
|
44
|
-
if value
|
|
45
|
-
vals = Array(value)
|
|
46
|
-
raise Error, "Initializer length #{vals.length} does not match out array size #{count}" unless vals.length == count
|
|
47
|
-
TypeMap.write_array(ptr, base, vals)
|
|
48
|
-
end
|
|
49
|
-
out_refs << { index: idx, kind: :out_array, base: base, count: count, ptr: ptr }
|
|
50
|
-
arg_values << ptr.to_i
|
|
51
|
-
else
|
|
52
|
-
raise Error, "Unknown array/output form: #{type_sym.inspect}"
|
|
53
|
-
end
|
|
54
|
-
elsif type_sym == :callback
|
|
55
|
-
spec = value
|
|
56
|
-
unless spec.is_a?(Hash) && spec[:kind] == :callback
|
|
57
|
-
raise Error, 'Invalid callback value; expected func signature and block'
|
|
58
|
-
end
|
|
59
|
-
|
|
60
|
-
ret_ty = TypeMap.to_fiddle_type(spec[:ret])
|
|
61
|
-
arg_tys = spec[:args].map { |a| TypeMap.to_fiddle_type(a) }
|
|
62
|
-
# Build Ruby proc from block source, e.g., "{|a,b| a+b}"
|
|
63
|
-
# Evaluate proc in a helper context so DSL methods are available
|
|
64
|
-
ctx = Object.new.extend(Libcall::Fiddley::DSL)
|
|
65
|
-
begin
|
|
66
|
-
ruby_proc = ctx.instance_eval("proc #{spec[:block]}", __FILE__, __LINE__)
|
|
67
|
-
rescue SyntaxError => e
|
|
68
|
-
raise Error, "Invalid Ruby block for callback: #{e.message}"
|
|
69
|
-
end
|
|
70
|
-
closure = Fiddle::Closure::BlockCaller.new(ret_ty, arg_tys) do |*cb_args|
|
|
71
|
-
# Convert pointer-typed args to Fiddle::Pointer for convenience
|
|
72
|
-
cooked = cb_args.each_with_index.map do |v, i|
|
|
73
|
-
at = spec[:args][i]
|
|
74
|
-
if at == :voidp
|
|
75
|
-
Fiddle::Pointer.new(v)
|
|
76
|
-
else
|
|
77
|
-
v
|
|
78
|
-
end
|
|
79
|
-
end
|
|
80
|
-
ruby_proc.call(*cooked)
|
|
81
|
-
end
|
|
82
|
-
closures << closure # keep alive during call
|
|
83
|
-
arg_values << closure
|
|
84
|
-
else
|
|
85
|
-
arg_values << value
|
|
86
|
-
end
|
|
87
|
-
end
|
|
88
|
-
|
|
89
|
-
ret_type = TypeMap.to_fiddle_type(return_type)
|
|
90
|
-
|
|
91
|
-
handle = Fiddle.dlopen(lib_path)
|
|
92
|
-
func_ptr = handle[func_name]
|
|
93
|
-
func = Fiddle::Function.new(func_ptr, arg_types, ret_type)
|
|
94
|
-
|
|
95
|
-
raw_result = func.call(*arg_values)
|
|
21
|
+
raw_result = execute_function(processed)
|
|
96
22
|
formatted_result = format_result(raw_result, return_type)
|
|
97
23
|
|
|
98
|
-
|
|
24
|
+
output_reader = OutputReader.new(processed.out_refs)
|
|
25
|
+
if output_reader.empty?
|
|
99
26
|
formatted_result
|
|
100
27
|
else
|
|
101
|
-
{ result: formatted_result, outputs:
|
|
28
|
+
{ result: formatted_result, outputs: output_reader.read }
|
|
102
29
|
end
|
|
103
30
|
rescue Fiddle::DLError => e
|
|
104
31
|
raise Error, "Failed to load library or function: #{e.message}"
|
|
@@ -106,6 +33,14 @@ module Libcall
|
|
|
106
33
|
|
|
107
34
|
private
|
|
108
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
|
+
|
|
109
44
|
def format_result(result, type)
|
|
110
45
|
case type
|
|
111
46
|
when :void
|
|
@@ -127,22 +62,5 @@ module Libcall
|
|
|
127
62
|
result
|
|
128
63
|
end
|
|
129
64
|
end
|
|
130
|
-
|
|
131
|
-
def read_output_values(out_refs)
|
|
132
|
-
out_refs.map do |ref|
|
|
133
|
-
case ref[:kind]
|
|
134
|
-
when :out
|
|
135
|
-
value = TypeMap.read_output_pointer(ref[:ptr], ref[:type])
|
|
136
|
-
{ index: ref[:index], type: ref[:type].to_s, value: value }
|
|
137
|
-
when :out_array
|
|
138
|
-
base = ref[:base]
|
|
139
|
-
count = ref[:count]
|
|
140
|
-
values = TypeMap.read_array(ref[:ptr], base, count)
|
|
141
|
-
{ index: ref[:index], type: "#{base}[#{count}]", value: values }
|
|
142
|
-
else
|
|
143
|
-
raise Error, "Unknown out reference kind: #{ref[:kind]}"
|
|
144
|
-
end
|
|
145
|
-
end
|
|
146
|
-
end
|
|
147
65
|
end
|
|
148
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
|
|
data/lib/libcall/fiddley.rb
CHANGED
|
@@ -44,42 +44,26 @@ module Libcall
|
|
|
44
44
|
module Utils
|
|
45
45
|
module_function
|
|
46
46
|
|
|
47
|
-
# Native size_t pack template and size
|
|
48
|
-
SIZET_PACK = (Fiddle::SIZEOF_VOIDP == Fiddle::SIZEOF_LONG ? 'L!' : 'Q')
|
|
49
|
-
|
|
50
47
|
# Return size in bytes for a given type symbol.
|
|
51
|
-
# Falls back to pointer size for :pointer and :voidp.
|
|
52
|
-
def sizeof(type)
|
|
53
|
-
return Fiddle::SIZEOF_SIZE_T if type == :size_t
|
|
54
|
-
return Fiddle::SIZEOF_VOIDP if %i[pointer voidp].include?(type)
|
|
55
48
|
|
|
56
|
-
|
|
49
|
+
def sizeof(type)
|
|
57
50
|
Libcall::TypeMap.sizeof(type)
|
|
58
|
-
rescue StandardError
|
|
59
|
-
raise Libcall::Error, "unknown type for sizeof: #{type}"
|
|
51
|
+
rescue StandardError => e
|
|
52
|
+
raise Libcall::Error, "unknown type for sizeof: #{type} (#{e.message})"
|
|
60
53
|
end
|
|
61
54
|
|
|
62
55
|
# Convert a type symbol to a Fiddle type constant.
|
|
63
56
|
def to_fiddle_type(type)
|
|
64
|
-
return Fiddle::TYPE_SIZE_T if type == :size_t
|
|
65
|
-
return Fiddle::TYPE_VOIDP if %i[pointer voidp].include?(type)
|
|
66
|
-
|
|
67
57
|
Libcall::TypeMap.to_fiddle_type(type)
|
|
68
|
-
rescue StandardError
|
|
69
|
-
raise Libcall::Error, "unknown type for to_fiddle_type: #{type}"
|
|
58
|
+
rescue StandardError => e
|
|
59
|
+
raise Libcall::Error, "unknown type for to_fiddle_type: #{type} (#{e.message})"
|
|
70
60
|
end
|
|
71
61
|
|
|
72
62
|
# Pack template for array values of given base type.
|
|
73
63
|
def array_pack_template(type)
|
|
74
|
-
return SIZET_PACK if type == :size_t
|
|
75
|
-
|
|
76
|
-
# Use TypeMap's packing for standard types
|
|
77
64
|
Libcall::TypeMap.pack_template(type)
|
|
78
|
-
rescue StandardError
|
|
79
|
-
|
|
80
|
-
return 'J' if %i[pointer voidp].include?(type)
|
|
81
|
-
|
|
82
|
-
raise Libcall::Error, "Unsupported array base type: #{type}"
|
|
65
|
+
rescue StandardError => e
|
|
66
|
+
raise Libcall::Error, "Unsupported array base type: #{type} (#{e.message})"
|
|
83
67
|
end
|
|
84
68
|
|
|
85
69
|
# Convert Ruby array of numbers to a binary string for the given type
|
|
@@ -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
|
@@ -60,23 +60,43 @@ module Libcall
|
|
|
60
60
|
m = src.match(/\A\s*([^(\s]+)\s*\(([^)]*)\)\s*(\{.*\})\s*\z/m)
|
|
61
61
|
raise Error, "Invalid callback spec: #{src}" unless m
|
|
62
62
|
|
|
63
|
-
ret_s = m
|
|
64
|
-
|
|
65
|
-
|
|
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
|
|
66
68
|
|
|
67
69
|
ret_sym = TypeMap.lookup(ret_s)
|
|
68
70
|
raise Error, "Unknown callback return type: #{ret_s}" unless ret_sym
|
|
69
71
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
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
|
|
80
100
|
|
|
81
101
|
return { kind: :callback, ret: ret_sym, args: arg_syms, block: block_src }
|
|
82
102
|
end
|
data/lib/libcall/type_map.rb
CHANGED
|
@@ -125,17 +125,28 @@ module Libcall
|
|
|
125
125
|
return Fiddle::TYPE_VOIDP if type_sym == :callback
|
|
126
126
|
|
|
127
127
|
case type_sym
|
|
128
|
+
# Void type
|
|
128
129
|
when :void then Fiddle::TYPE_VOID
|
|
130
|
+
# Integer types (8-bit)
|
|
129
131
|
when :char then Fiddle::TYPE_CHAR
|
|
130
132
|
when :uchar then Fiddle::TYPE_UCHAR
|
|
133
|
+
# Integer types (16-bit)
|
|
131
134
|
when :short then Fiddle::TYPE_SHORT
|
|
132
135
|
when :ushort then Fiddle::TYPE_USHORT
|
|
136
|
+
# Integer types (32-bit)
|
|
133
137
|
when :int, :uint then Fiddle::TYPE_INT
|
|
138
|
+
# Integer types (platform-dependent)
|
|
134
139
|
when :long, :ulong then Fiddle::TYPE_LONG
|
|
140
|
+
# Integer types (64-bit)
|
|
135
141
|
when :long_long, :ulong_long then Fiddle::TYPE_LONG_LONG
|
|
142
|
+
# Floating point types
|
|
136
143
|
when :float then Fiddle::TYPE_FLOAT
|
|
137
144
|
when :double then Fiddle::TYPE_DOUBLE
|
|
138
|
-
|
|
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
|
|
139
150
|
else
|
|
140
151
|
raise Error, "Unknown Fiddle type: #{type_sym}"
|
|
141
152
|
end
|
|
@@ -144,14 +155,23 @@ module Libcall
|
|
|
144
155
|
# Get the size in bytes for a type symbol
|
|
145
156
|
def self.sizeof(type_sym)
|
|
146
157
|
case type_sym
|
|
158
|
+
# Integer types (8-bit)
|
|
147
159
|
when :char, :uchar then Fiddle::SIZEOF_CHAR
|
|
160
|
+
# Integer types (16-bit)
|
|
148
161
|
when :short, :ushort then Fiddle::SIZEOF_SHORT
|
|
162
|
+
# Integer types (32-bit)
|
|
149
163
|
when :int, :uint then Fiddle::SIZEOF_INT
|
|
164
|
+
# Integer types (platform-dependent)
|
|
150
165
|
when :long, :ulong then Fiddle::SIZEOF_LONG
|
|
166
|
+
# Integer types (64-bit)
|
|
151
167
|
when :long_long, :ulong_long then Fiddle::SIZEOF_LONG_LONG
|
|
168
|
+
# Floating point types
|
|
152
169
|
when :float then Fiddle::SIZEOF_FLOAT
|
|
153
170
|
when :double then Fiddle::SIZEOF_DOUBLE
|
|
154
|
-
|
|
171
|
+
# Pointer types
|
|
172
|
+
when :voidp, :pointer, :string then Fiddle::SIZEOF_VOIDP
|
|
173
|
+
# Special types
|
|
174
|
+
when :size_t then Fiddle::SIZEOF_SIZE_T
|
|
155
175
|
else
|
|
156
176
|
raise Error, "Cannot get size for type: #{type_sym}"
|
|
157
177
|
end
|
|
@@ -168,18 +188,26 @@ module Libcall
|
|
|
168
188
|
# Read value from output pointer
|
|
169
189
|
def self.read_output_pointer(ptr, type_sym)
|
|
170
190
|
case type_sym
|
|
191
|
+
# Integer types (8-bit)
|
|
171
192
|
when :char then ptr[0, Fiddle::SIZEOF_CHAR].unpack1('c')
|
|
172
193
|
when :uchar then ptr[0, Fiddle::SIZEOF_CHAR].unpack1('C')
|
|
194
|
+
# Integer types (16-bit)
|
|
173
195
|
when :short then ptr[0, Fiddle::SIZEOF_SHORT].unpack1('s')
|
|
174
196
|
when :ushort then ptr[0, Fiddle::SIZEOF_SHORT].unpack1('S')
|
|
197
|
+
# Integer types (32-bit)
|
|
175
198
|
when :int then ptr[0, Fiddle::SIZEOF_INT].unpack1('i')
|
|
176
199
|
when :uint then ptr[0, Fiddle::SIZEOF_INT].unpack1('I')
|
|
200
|
+
# Integer types (platform-dependent)
|
|
177
201
|
when :long then ptr[0, Fiddle::SIZEOF_LONG].unpack1('l!')
|
|
178
202
|
when :ulong then ptr[0, Fiddle::SIZEOF_LONG].unpack1('L!')
|
|
203
|
+
# Integer types (64-bit)
|
|
179
204
|
when :long_long then ptr[0, Fiddle::SIZEOF_LONG_LONG].unpack1('q')
|
|
180
205
|
when :ulong_long then ptr[0, Fiddle::SIZEOF_LONG_LONG].unpack1('Q')
|
|
206
|
+
# Floating point types
|
|
181
207
|
when :float then ptr[0, Fiddle::SIZEOF_FLOAT].unpack1('f')
|
|
182
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'))
|
|
183
211
|
when :string
|
|
184
212
|
addr = ptr[0, Fiddle::SIZEOF_VOIDP].unpack1('J')
|
|
185
213
|
return '(null)' if addr.zero?
|
|
@@ -189,7 +217,6 @@ module Libcall
|
|
|
189
217
|
rescue StandardError
|
|
190
218
|
format('0x%x', addr)
|
|
191
219
|
end
|
|
192
|
-
when :voidp then format('0x%x', ptr[0, Fiddle::SIZEOF_VOIDP].unpack1('J'))
|
|
193
220
|
else
|
|
194
221
|
raise Error, "Cannot read output value for type: #{type_sym}"
|
|
195
222
|
end
|
|
@@ -222,18 +249,28 @@ module Libcall
|
|
|
222
249
|
|
|
223
250
|
def self.pack_template(base_type)
|
|
224
251
|
case base_type
|
|
252
|
+
# Integer types (8-bit)
|
|
225
253
|
when :char then 'c'
|
|
226
254
|
when :uchar then 'C'
|
|
255
|
+
# Integer types (16-bit)
|
|
227
256
|
when :short then 's'
|
|
228
257
|
when :ushort then 'S'
|
|
258
|
+
# Integer types (32-bit)
|
|
229
259
|
when :int then 'i'
|
|
230
260
|
when :uint then 'I'
|
|
261
|
+
# Integer types (platform-dependent)
|
|
231
262
|
when :long then 'l!'
|
|
232
263
|
when :ulong then 'L!'
|
|
264
|
+
# Integer types (64-bit)
|
|
233
265
|
when :long_long then 'q'
|
|
234
266
|
when :ulong_long then 'Q'
|
|
267
|
+
# Floating point types
|
|
235
268
|
when :float then 'f'
|
|
236
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')
|
|
237
274
|
else
|
|
238
275
|
raise Error, "Unsupported array base type: #{base_type}"
|
|
239
276
|
end
|
data/lib/libcall/version.rb
CHANGED
data/lib/libcall.rb
CHANGED
|
@@ -3,6 +3,9 @@
|
|
|
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'
|
|
8
11
|
require_relative 'libcall/fiddley'
|
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,10 +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
|
|
52
54
|
- lib/libcall/fiddley.rb
|
|
53
55
|
- lib/libcall/library_finder.rb
|
|
56
|
+
- lib/libcall/output_reader.rb
|
|
54
57
|
- lib/libcall/parser.rb
|
|
55
58
|
- lib/libcall/platform.rb
|
|
56
59
|
- lib/libcall/type_map.rb
|
|
@@ -73,7 +76,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
73
76
|
- !ruby/object:Gem::Version
|
|
74
77
|
version: '0'
|
|
75
78
|
requirements: []
|
|
76
|
-
rubygems_version: 3.
|
|
79
|
+
rubygems_version: 3.6.9
|
|
77
80
|
specification_version: 4
|
|
78
81
|
summary: Call functions in shared libraries directly from the CLI
|
|
79
82
|
test_files: []
|