cs-binding-generator 1.0.1.dev21__tar.gz → 1.0.1.dev35__tar.gz
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.
- {cs_binding_generator-1.0.1.dev21 → cs_binding_generator-1.0.1.dev35}/.gitignore +3 -0
- cs_binding_generator-1.0.1.dev35/COPILOT_CONTEXT.md +149 -0
- {cs_binding_generator-1.0.1.dev21 → cs_binding_generator-1.0.1.dev35}/PKG-INFO +1 -1
- {cs_binding_generator-1.0.1.dev21 → cs_binding_generator-1.0.1.dev35}/cs_binding_generator/_version.py +3 -3
- {cs_binding_generator-1.0.1.dev21 → cs_binding_generator-1.0.1.dev35}/cs_binding_generator/code_generators.py +79 -7
- {cs_binding_generator-1.0.1.dev21 → cs_binding_generator-1.0.1.dev35}/cs_binding_generator/config.py +51 -34
- {cs_binding_generator-1.0.1.dev21 → cs_binding_generator-1.0.1.dev35}/cs_binding_generator/generator.py +47 -2
- {cs_binding_generator-1.0.1.dev21 → cs_binding_generator-1.0.1.dev35}/cs_binding_generator/main.py +22 -33
- {cs_binding_generator-1.0.1.dev21 → cs_binding_generator-1.0.1.dev35}/cs_binding_generator/type_mapper.py +69 -3
- {cs_binding_generator-1.0.1.dev21 → cs_binding_generator-1.0.1.dev35}/cs_binding_generator.egg-info/PKG-INFO +1 -1
- {cs_binding_generator-1.0.1.dev21 → cs_binding_generator-1.0.1.dev35}/cs_binding_generator.egg-info/SOURCES.txt +3 -0
- {cs_binding_generator-1.0.1.dev21 → cs_binding_generator-1.0.1.dev35}/tests/test_code_generators.py +2 -0
- cs_binding_generator-1.0.1.dev35/tests/test_defines.py +247 -0
- {cs_binding_generator-1.0.1.dev21 → cs_binding_generator-1.0.1.dev35}/tests/test_edge_cases.py +25 -25
- cs_binding_generator-1.0.1.dev35/tests/test_flag_enums.py +280 -0
- {cs_binding_generator-1.0.1.dev21 → cs_binding_generator-1.0.1.dev35}/tests/test_generator.py +111 -0
- {cs_binding_generator-1.0.1.dev21 → cs_binding_generator-1.0.1.dev35}/tests/test_multi_file_deduplication.py +5 -5
- cs_binding_generator-1.0.1.dev35/tests/test_opaque_typedef_underlying.py +32 -0
- {cs_binding_generator-1.0.1.dev21 → cs_binding_generator-1.0.1.dev35}/tests/test_removal.py +34 -41
- {cs_binding_generator-1.0.1.dev21 → cs_binding_generator-1.0.1.dev35}/tests/test_renaming.py +33 -33
- {cs_binding_generator-1.0.1.dev21 → cs_binding_generator-1.0.1.dev35}/tests/test_type_mapper.py +70 -2
- {cs_binding_generator-1.0.1.dev21 → cs_binding_generator-1.0.1.dev35}/tests/test_xml_config.py +79 -79
- cs_binding_generator-1.0.1.dev21/COPILOT_CONTEXT.md +0 -439
- {cs_binding_generator-1.0.1.dev21 → cs_binding_generator-1.0.1.dev35}/.coverage +0 -0
- {cs_binding_generator-1.0.1.dev21 → cs_binding_generator-1.0.1.dev35}/.flake8 +0 -0
- {cs_binding_generator-1.0.1.dev21 → cs_binding_generator-1.0.1.dev35}/.github/workflows/publish.yml +0 -0
- {cs_binding_generator-1.0.1.dev21 → cs_binding_generator-1.0.1.dev35}/LICENSE +0 -0
- {cs_binding_generator-1.0.1.dev21 → cs_binding_generator-1.0.1.dev35}/README.md +0 -0
- {cs_binding_generator-1.0.1.dev21 → cs_binding_generator-1.0.1.dev35}/cs_binding_generator/__init__.py +0 -0
- {cs_binding_generator-1.0.1.dev21 → cs_binding_generator-1.0.1.dev35}/cs_binding_generator/constants.py +0 -0
- {cs_binding_generator-1.0.1.dev21 → cs_binding_generator-1.0.1.dev35}/cs_binding_generator.egg-info/dependency_links.txt +0 -0
- {cs_binding_generator-1.0.1.dev21 → cs_binding_generator-1.0.1.dev35}/cs_binding_generator.egg-info/entry_points.txt +0 -0
- {cs_binding_generator-1.0.1.dev21 → cs_binding_generator-1.0.1.dev35}/cs_binding_generator.egg-info/requires.txt +0 -0
- {cs_binding_generator-1.0.1.dev21 → cs_binding_generator-1.0.1.dev35}/cs_binding_generator.egg-info/top_level.txt +0 -0
- {cs_binding_generator-1.0.1.dev21 → cs_binding_generator-1.0.1.dev35}/docs/ARCHITECTURE.md +0 -0
- {cs_binding_generator-1.0.1.dev21 → cs_binding_generator-1.0.1.dev35}/docs/INCLUDE_DIRECTORIES.md +0 -0
- {cs_binding_generator-1.0.1.dev21 → cs_binding_generator-1.0.1.dev35}/docs/MULTI_FILE_OUTPUT.md +0 -0
- {cs_binding_generator-1.0.1.dev21 → cs_binding_generator-1.0.1.dev35}/docs/RENAMING_EXAMPLE.xml +0 -0
- {cs_binding_generator-1.0.1.dev21 → cs_binding_generator-1.0.1.dev35}/docs/TROUBLESHOOTING.md +0 -0
- {cs_binding_generator-1.0.1.dev21 → cs_binding_generator-1.0.1.dev35}/docs/XML_CONFIG.md +0 -0
- {cs_binding_generator-1.0.1.dev21 → cs_binding_generator-1.0.1.dev35}/enter_devenv.sh +0 -0
- {cs_binding_generator-1.0.1.dev21 → cs_binding_generator-1.0.1.dev35}/pyproject.toml +0 -0
- {cs_binding_generator-1.0.1.dev21 → cs_binding_generator-1.0.1.dev35}/run_tests.sh +0 -0
- {cs_binding_generator-1.0.1.dev21 → cs_binding_generator-1.0.1.dev35}/setup.cfg +0 -0
- {cs_binding_generator-1.0.1.dev21 → cs_binding_generator-1.0.1.dev35}/test_dotnet/FreeTypeTest/FreeTypeTest.csproj +0 -0
- {cs_binding_generator-1.0.1.dev21 → cs_binding_generator-1.0.1.dev35}/test_dotnet/FreeTypeTest/bindings.cs +0 -0
- {cs_binding_generator-1.0.1.dev21 → cs_binding_generator-1.0.1.dev35}/test_dotnet/FreeTypeTest/cs-bindings.xml +0 -0
- {cs_binding_generator-1.0.1.dev21 → cs_binding_generator-1.0.1.dev35}/test_dotnet/FreeTypeTest/freetype.cs +0 -0
- {cs_binding_generator-1.0.1.dev21 → cs_binding_generator-1.0.1.dev35}/test_dotnet/LibtcodTest/LibtcodTest.csproj +0 -0
- {cs_binding_generator-1.0.1.dev21 → cs_binding_generator-1.0.1.dev35}/test_dotnet/LibtcodTest/SDL3.cs +0 -0
- {cs_binding_generator-1.0.1.dev21 → cs_binding_generator-1.0.1.dev35}/test_dotnet/LibtcodTest/bindings.cs +0 -0
- {cs_binding_generator-1.0.1.dev21 → cs_binding_generator-1.0.1.dev35}/test_dotnet/LibtcodTest/cs-bindings.xml +0 -0
- {cs_binding_generator-1.0.1.dev21 → cs_binding_generator-1.0.1.dev35}/test_dotnet/LibtcodTest/libtcod.cs +0 -0
- {cs_binding_generator-1.0.1.dev21 → cs_binding_generator-1.0.1.dev35}/test_dotnet/SDL3Test/SDL3.cs +0 -0
- {cs_binding_generator-1.0.1.dev21 → cs_binding_generator-1.0.1.dev35}/test_dotnet/SDL3Test/SDL3Test.csproj +0 -0
- {cs_binding_generator-1.0.1.dev21 → cs_binding_generator-1.0.1.dev35}/test_dotnet/SDL3Test/SDL3Test.sln +0 -0
- {cs_binding_generator-1.0.1.dev21 → cs_binding_generator-1.0.1.dev35}/test_dotnet/SDL3Test/bindings.cs +0 -0
- {cs_binding_generator-1.0.1.dev21 → cs_binding_generator-1.0.1.dev35}/test_dotnet/SDL3Test/cs-bindings.xml +0 -0
- {cs_binding_generator-1.0.1.dev21 → cs_binding_generator-1.0.1.dev35}/tests/__init__.py +0 -0
- {cs_binding_generator-1.0.1.dev21 → cs_binding_generator-1.0.1.dev35}/tests/conftest.py +0 -0
- {cs_binding_generator-1.0.1.dev21 → cs_binding_generator-1.0.1.dev35}/tests/fixtures.py +0 -0
- {cs_binding_generator-1.0.1.dev21 → cs_binding_generator-1.0.1.dev35}/tests/test_cli.py +0 -0
- {cs_binding_generator-1.0.1.dev21 → cs_binding_generator-1.0.1.dev35}/tests/test_cli_extended.py +0 -0
- {cs_binding_generator-1.0.1.dev21 → cs_binding_generator-1.0.1.dev35}/tests/test_error_handling.py +0 -0
- {cs_binding_generator-1.0.1.dev21 → cs_binding_generator-1.0.1.dev35}/tests/test_type_mapping_extended.py +0 -0
- {cs_binding_generator-1.0.1.dev21 → cs_binding_generator-1.0.1.dev35}/tests/test_variadic_functions.py +0 -0
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
# CsBindingGenerator - AI Assistant Context
|
|
2
|
+
|
|
3
|
+
**PURPOSE**: Essential context for AI assistants. For change history, see `git log`.
|
|
4
|
+
|
|
5
|
+
## Project Overview
|
|
6
|
+
|
|
7
|
+
Generates C# P/Invoke bindings from C headers using libclang. Modern LibraryImport attributes, multi-file output, regex renames.
|
|
8
|
+
|
|
9
|
+
## Architecture
|
|
10
|
+
|
|
11
|
+
```
|
|
12
|
+
C Headers → libclang → Generator → TypeMapper → CodeGenerator → C# Output
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
- **Generator** (`generator.py`): Parses C headers, manages deduplication, filters system headers
|
|
16
|
+
- **TypeMapper** (`type_mapper.py`): Maps C→C# types, applies renames, context-aware (is_struct_field, is_return_type)
|
|
17
|
+
- **CodeGenerator** (`code_generators.py`): Generates C# code, handles anonymous unions/structs, applies attributes
|
|
18
|
+
- **Config** (`config.py`): Parses XML configuration
|
|
19
|
+
- **Main** (`main.py`): CLI entry point (minimal args - most config in XML)
|
|
20
|
+
|
|
21
|
+
## Critical Concepts
|
|
22
|
+
|
|
23
|
+
### Deduplication (Multi-File Mode - Always Enabled)
|
|
24
|
+
|
|
25
|
+
Global deduplication by name only:
|
|
26
|
+
- Functions: First library wins by `cursor.spelling`
|
|
27
|
+
- Structs/Unions: First library wins by `(name, file, line)`
|
|
28
|
+
|
|
29
|
+
**CRITICAL**: Library order in XML matters - foundation libraries first.
|
|
30
|
+
|
|
31
|
+
### Type Mapping Context Awareness
|
|
32
|
+
|
|
33
|
+
- **`is_struct_field=True`**: Maps C `bool` → C# `byte` (avoids CS8500 managed type errors)
|
|
34
|
+
- **`is_return_type=True`**: Affects pointer type mapping
|
|
35
|
+
|
|
36
|
+
### Anonymous Unions/Structs
|
|
37
|
+
|
|
38
|
+
- Detected by "anonymous" in `cursor.spelling`
|
|
39
|
+
- NOT generated as standalone types
|
|
40
|
+
- Members flattened into parent struct
|
|
41
|
+
- Use `cursor.type.get_offset(field_name)` for parent-relative offsets (returns bits ÷ 8 = bytes)
|
|
42
|
+
|
|
43
|
+
### Variadic Functions
|
|
44
|
+
|
|
45
|
+
- **Default**: `...` parameter omitted (works with calling conventions, may cause stack issues)
|
|
46
|
+
- **`--use-variadic` flag**: Uses DllImport + `__arglist` (non-AOT compatible)
|
|
47
|
+
|
|
48
|
+
### Rename Rules
|
|
49
|
+
|
|
50
|
+
Stored as list: `[(pattern, replacement, is_regex), ...]`
|
|
51
|
+
- Applied in order, first match wins
|
|
52
|
+
- User writes `$1`, `$2` (converted to `\1`, `\2` internally)
|
|
53
|
+
- Uses `re.fullmatch()` for precise matching
|
|
54
|
+
- Post-processing uses word boundaries
|
|
55
|
+
|
|
56
|
+
**Conflict resolution**: Place specific rules before general ones:
|
|
57
|
+
```xml
|
|
58
|
+
<rename from="SDL_strcasecmp" to="SDL_strcasecmp"/> <!-- Keep -->
|
|
59
|
+
<rename from="SDL_(.*)" to="$1" regex="true"/> <!-- Strip rest -->
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
### System Header Filtering
|
|
63
|
+
|
|
64
|
+
`_is_system_header()` filters:
|
|
65
|
+
- `/usr/include/` direct files (no subdirectory)
|
|
66
|
+
- `/usr/include/{sys,bits,etc}/` subdirectories
|
|
67
|
+
- C standard library headers (c_std_headers set)
|
|
68
|
+
- `/usr/include/c++` paths
|
|
69
|
+
|
|
70
|
+
## XML Configuration
|
|
71
|
+
|
|
72
|
+
```xml
|
|
73
|
+
<bindings visibility="internal">
|
|
74
|
+
<include_directory path="/custom/path"/>
|
|
75
|
+
<rename from="PREFIX_(.*)" to="$1" regex="true"/>
|
|
76
|
+
<remove pattern="DEPRECATED_.*" regex="true"/>
|
|
77
|
+
<constants name="Flags" pattern="FLAG_.*" type="uint" flags="true"/>
|
|
78
|
+
|
|
79
|
+
<library name="libname" namespace="Namespace" class="ClassName">
|
|
80
|
+
<using namespace="System.Runtime.CompilerServices"/>
|
|
81
|
+
<include file="/path/to/header.h"/>
|
|
82
|
+
</library>
|
|
83
|
+
</bindings>
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
**Key attributes**:
|
|
87
|
+
- `<bindings>`: `visibility="public|internal"`
|
|
88
|
+
- `<rename>`: `from`, `to`, `regex="true|false"`
|
|
89
|
+
- `<remove>`: `pattern`, `regex="true|false"`
|
|
90
|
+
- `<constants>`: `name`, `pattern`, `type`, `flags="true|false"`
|
|
91
|
+
- `<library>`: `name`, `namespace`, `class` (default: "NativeMethods")
|
|
92
|
+
- `<using>`: `namespace`
|
|
93
|
+
- `<include>`: `file`
|
|
94
|
+
|
|
95
|
+
**Note**: Don't specify `/usr/include` - clang auto-detects it.
|
|
96
|
+
|
|
97
|
+
## CLI Arguments (Limited by Design)
|
|
98
|
+
|
|
99
|
+
- `-C/--config`: XML config file (default: cs-bindings.xml)
|
|
100
|
+
- `-o/--output`: Output directory (default: current)
|
|
101
|
+
- `--ignore-missing`: Continue if headers missing
|
|
102
|
+
- `--use-variadic`: Generate variadic functions with __arglist
|
|
103
|
+
- `--clang-path`: Path to libclang
|
|
104
|
+
|
|
105
|
+
## Known Edge Cases
|
|
106
|
+
|
|
107
|
+
- **Duplicate symbols**: Ensure library order in XML (foundation first) for global deduplication
|
|
108
|
+
- **Bool in structs**: Type mapper automatically converts bool→byte when `is_struct_field=True` to avoid managed type errors
|
|
109
|
+
- **System header leakage**: `_is_system_header()` must filter direct `/usr/include/` files and subdirectories
|
|
110
|
+
- **Anonymous unions**: Must detect "anonymous" in `cursor.spelling` and flatten members with parent-relative offsets
|
|
111
|
+
- **Incomplete arrays**: Handle `TypeKind.INCOMPLETEARRAY` → `nuint` for char*, `T*` for others
|
|
112
|
+
|
|
113
|
+
## libclang Key Concepts
|
|
114
|
+
|
|
115
|
+
- `cursor.spelling`: Entity name
|
|
116
|
+
- `cursor.location.file`: Source file
|
|
117
|
+
- `cursor.is_definition()`: True if definition (not declaration)
|
|
118
|
+
- `cursor.type.get_offset(field)`: Offset in **bits** (÷ 8 for bytes)
|
|
119
|
+
- `TypeKind.INCOMPLETEARRAY`: Array parameters like `items[]`
|
|
120
|
+
|
|
121
|
+
## Testing
|
|
122
|
+
|
|
123
|
+
Run: `source enter_devenv.sh && ./run_tests.sh`
|
|
124
|
+
- pytest 9.0.2, Python 3.13.11
|
|
125
|
+
- Current: 179 tests
|
|
126
|
+
- Test with real C code using `temp_header_file` fixture
|
|
127
|
+
- Verify struct offsets in bytes
|
|
128
|
+
|
|
129
|
+
## Development Workflow
|
|
130
|
+
|
|
131
|
+
1. Source env: `source enter_devenv.sh`
|
|
132
|
+
2. Make changes
|
|
133
|
+
3. Run: `./run_tests.sh`
|
|
134
|
+
4. Test projects in `test_dotnet/`: SDL3Test, LibtcodTest, FreeTypeTest
|
|
135
|
+
5. Each has `regenerate_bindings.sh` + `dotnet build`
|
|
136
|
+
|
|
137
|
+
## User Interaction Rules
|
|
138
|
+
|
|
139
|
+
1. Never assume pre-existing bugs
|
|
140
|
+
2. Don't try random fixes - ask if uncertain
|
|
141
|
+
3. Read error messages carefully
|
|
142
|
+
4. Check actual implementation (e.g., argparse in main.py) before documenting
|
|
143
|
+
5. Update this file only for architectural insights, not change history
|
|
144
|
+
|
|
145
|
+
---
|
|
146
|
+
|
|
147
|
+
**For change history: `git log`**
|
|
148
|
+
|
|
149
|
+
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: cs_binding_generator
|
|
3
|
-
Version: 1.0.1.
|
|
3
|
+
Version: 1.0.1.dev35
|
|
4
4
|
Summary: Generate C# bindings from C headers using libclang with modern LibraryImport
|
|
5
5
|
Author-email: Robin 'Ruadeil' Degen <mail@ruadeil.lgbt>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -28,7 +28,7 @@ version_tuple: VERSION_TUPLE
|
|
|
28
28
|
commit_id: COMMIT_ID
|
|
29
29
|
__commit_id__: COMMIT_ID
|
|
30
30
|
|
|
31
|
-
__version__ = version = '1.0.1.
|
|
32
|
-
__version_tuple__ = version_tuple = (1, 0, 1, '
|
|
31
|
+
__version__ = version = '1.0.1.dev35'
|
|
32
|
+
__version_tuple__ = version_tuple = (1, 0, 1, 'dev35')
|
|
33
33
|
|
|
34
|
-
__commit_id__ = commit_id = '
|
|
34
|
+
__commit_id__ = commit_id = 'g0db8f9507'
|
|
@@ -187,11 +187,16 @@ class CodeGenerator:
|
|
|
187
187
|
# Add helper function for struct return types (skip for variadic functions)
|
|
188
188
|
if is_struct_return and not is_variadic:
|
|
189
189
|
# Build parameters for helper (same as original)
|
|
190
|
+
# Escape parameter names for the function call
|
|
191
|
+
param_names = [self._escape_keyword(arg.spelling) if arg.spelling else f"param{i}"
|
|
192
|
+
for i, arg in enumerate(cursor.get_arguments())]
|
|
193
|
+
params_call_str = ", ".join(param_names) if param_names else ""
|
|
194
|
+
|
|
190
195
|
code += f"""
|
|
191
196
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
192
197
|
{self.visibility} static unsafe {struct_return_type} {func_name}Struct({params_str})
|
|
193
198
|
{{
|
|
194
|
-
var ptr = {func_name}({
|
|
199
|
+
var ptr = {func_name}({params_call_str});
|
|
195
200
|
return Marshal.PtrToStructure<{struct_return_type}>((nint)ptr);
|
|
196
201
|
}}
|
|
197
202
|
"""
|
|
@@ -237,10 +242,69 @@ class CodeGenerator:
|
|
|
237
242
|
# Collect fields with their offsets
|
|
238
243
|
fields = []
|
|
239
244
|
for field in cursor.get_children():
|
|
245
|
+
# Handle anonymous unions/structs by flattening their members into the parent struct
|
|
246
|
+
if field.kind == CursorKind.UNION_DECL and field.spelling and "anonymous" in field.spelling.lower() and field.is_definition():
|
|
247
|
+
# This is an anonymous union - flatten its members into the struct
|
|
248
|
+
for union_member in field.get_children():
|
|
249
|
+
if union_member.kind == CursorKind.FIELD_DECL:
|
|
250
|
+
member_name = union_member.spelling
|
|
251
|
+
if not member_name:
|
|
252
|
+
continue
|
|
253
|
+
|
|
254
|
+
# Escape C# keywords
|
|
255
|
+
member_name = self._escape_keyword(member_name)
|
|
256
|
+
|
|
257
|
+
# Get the offset using parent struct's type.get_offset()
|
|
258
|
+
# This gives us the actual offset in the parent struct, not relative to the union
|
|
259
|
+
try:
|
|
260
|
+
offset_bits = cursor.type.get_offset(union_member.spelling)
|
|
261
|
+
offset_bytes = offset_bits // 8
|
|
262
|
+
except Exception:
|
|
263
|
+
# Fallback to field offset (will be wrong for anonymous unions)
|
|
264
|
+
offset_bits = union_member.get_field_offsetof()
|
|
265
|
+
offset_bytes = offset_bits // 8
|
|
266
|
+
|
|
267
|
+
# Map the type
|
|
268
|
+
member_type = self.type_mapper.map_type(union_member.type, is_struct_field=True)
|
|
269
|
+
if not member_type or "unnamed" in member_type or "::" in member_type:
|
|
270
|
+
continue
|
|
271
|
+
|
|
272
|
+
fields.append(f" [FieldOffset({offset_bytes})]\n {self.visibility} {member_type} {member_name};")
|
|
273
|
+
continue
|
|
274
|
+
|
|
275
|
+
# Handle anonymous structs by flattening their members
|
|
276
|
+
if field.kind == CursorKind.STRUCT_DECL and field.spelling and "anonymous" in field.spelling.lower() and field.is_definition():
|
|
277
|
+
# This is an anonymous struct - flatten its members into the parent struct
|
|
278
|
+
for struct_member in field.get_children():
|
|
279
|
+
if struct_member.kind == CursorKind.FIELD_DECL:
|
|
280
|
+
member_name = struct_member.spelling
|
|
281
|
+
if not member_name:
|
|
282
|
+
continue
|
|
283
|
+
|
|
284
|
+
# Escape C# keywords
|
|
285
|
+
member_name = self._escape_keyword(member_name)
|
|
286
|
+
|
|
287
|
+
# Get the offset using parent struct's type.get_offset()
|
|
288
|
+
try:
|
|
289
|
+
offset_bits = cursor.type.get_offset(struct_member.spelling)
|
|
290
|
+
offset_bytes = offset_bits // 8
|
|
291
|
+
except Exception:
|
|
292
|
+
# Fallback to field offset
|
|
293
|
+
offset_bits = struct_member.get_field_offsetof()
|
|
294
|
+
offset_bytes = offset_bits // 8
|
|
295
|
+
|
|
296
|
+
# Map the type
|
|
297
|
+
member_type = self.type_mapper.map_type(struct_member.type, is_struct_field=True)
|
|
298
|
+
if not member_type or "unnamed" in member_type or "::" in member_type:
|
|
299
|
+
continue
|
|
300
|
+
|
|
301
|
+
fields.append(f" [FieldOffset({offset_bytes})]\n {self.visibility} {member_type} {member_name};")
|
|
302
|
+
continue
|
|
303
|
+
|
|
240
304
|
if field.kind == CursorKind.FIELD_DECL:
|
|
241
305
|
field_name = field.spelling
|
|
242
306
|
|
|
243
|
-
# Skip unnamed fields (anonymous unions/structs)
|
|
307
|
+
# Skip unnamed fields (anonymous unions/structs without definitions are already handled above)
|
|
244
308
|
if not field_name:
|
|
245
309
|
continue
|
|
246
310
|
|
|
@@ -255,7 +319,7 @@ class CodeGenerator:
|
|
|
255
319
|
if field.type.kind == TypeKind.CONSTANTARRAY:
|
|
256
320
|
element_type = field.type.get_array_element_type()
|
|
257
321
|
array_size = field.type.get_array_size()
|
|
258
|
-
element_csharp = self.type_mapper.map_type(element_type)
|
|
322
|
+
element_csharp = self.type_mapper.map_type(element_type, is_struct_field=True)
|
|
259
323
|
|
|
260
324
|
# Skip if element type cannot be mapped
|
|
261
325
|
if not element_csharp:
|
|
@@ -265,6 +329,7 @@ class CodeGenerator:
|
|
|
265
329
|
element_size = element_type.get_size() # size in bytes
|
|
266
330
|
|
|
267
331
|
# Check if element type is a primitive type (can use fixed keyword)
|
|
332
|
+
# Note: bool is excluded because fixed bool arrays make structs managed
|
|
268
333
|
primitive_types = {
|
|
269
334
|
"byte",
|
|
270
335
|
"sbyte",
|
|
@@ -276,7 +341,6 @@ class CodeGenerator:
|
|
|
276
341
|
"ulong",
|
|
277
342
|
"float",
|
|
278
343
|
"double",
|
|
279
|
-
"bool",
|
|
280
344
|
"char",
|
|
281
345
|
}
|
|
282
346
|
|
|
@@ -355,7 +419,7 @@ class CodeGenerator:
|
|
|
355
419
|
if field.type.kind == TypeKind.CONSTANTARRAY:
|
|
356
420
|
element_type = field.type.get_array_element_type()
|
|
357
421
|
array_size = field.type.get_array_size()
|
|
358
|
-
element_csharp = self.type_mapper.map_type(element_type)
|
|
422
|
+
element_csharp = self.type_mapper.map_type(element_type, is_struct_field=True)
|
|
359
423
|
|
|
360
424
|
# Skip if element type cannot be mapped
|
|
361
425
|
if not element_csharp:
|
|
@@ -365,6 +429,7 @@ class CodeGenerator:
|
|
|
365
429
|
element_size = element_type.get_size()
|
|
366
430
|
|
|
367
431
|
# Check if element type is a primitive type (can use fixed keyword)
|
|
432
|
+
# Note: bool is excluded because fixed bool arrays make structs managed
|
|
368
433
|
primitive_types = {
|
|
369
434
|
"byte",
|
|
370
435
|
"sbyte",
|
|
@@ -376,7 +441,6 @@ class CodeGenerator:
|
|
|
376
441
|
"ulong",
|
|
377
442
|
"float",
|
|
378
443
|
"double",
|
|
379
|
-
"bool",
|
|
380
444
|
"char",
|
|
381
445
|
}
|
|
382
446
|
|
|
@@ -555,9 +619,14 @@ class CodeGenerator:
|
|
|
555
619
|
if csharp_type and csharp_type != "int":
|
|
556
620
|
inheritance_clause = f" : {csharp_type}"
|
|
557
621
|
|
|
622
|
+
# Check if this enum should have [Flags] attribute
|
|
623
|
+
flags_attribute = ""
|
|
624
|
+
if self.type_mapper.is_flag_enum(enum_name):
|
|
625
|
+
flags_attribute = "[Flags]\n"
|
|
626
|
+
|
|
558
627
|
values_str = "\n".join(values)
|
|
559
628
|
|
|
560
|
-
code = f"""{self.visibility} enum {enum_name}{inheritance_clause}
|
|
629
|
+
code = f"""{flags_attribute}{self.visibility} enum {enum_name}{inheritance_clause}
|
|
561
630
|
{{
|
|
562
631
|
{values_str}
|
|
563
632
|
}}
|
|
@@ -640,6 +709,7 @@ class OutputBuilder:
|
|
|
640
709
|
argv_copy[0] = "cs_binding_generator"
|
|
641
710
|
command_line = " ".join(argv_copy)
|
|
642
711
|
|
|
712
|
+
parts.append("// <auto-generated />")
|
|
643
713
|
parts.append("//")
|
|
644
714
|
parts.append("// This file was automatically generated by cs-binding-generator")
|
|
645
715
|
parts.append("// https://github.com/cs-binding-generator/cs-binding-generator")
|
|
@@ -648,6 +718,8 @@ class OutputBuilder:
|
|
|
648
718
|
parts.append("// Do not modify this file directly")
|
|
649
719
|
parts.append("//")
|
|
650
720
|
parts.append("")
|
|
721
|
+
parts.append("#nullable enable")
|
|
722
|
+
parts.append("")
|
|
651
723
|
|
|
652
724
|
# Usings (non-global)
|
|
653
725
|
from .constants import REQUIRED_USINGS
|
{cs_binding_generator-1.0.1.dev21 → cs_binding_generator-1.0.1.dev35}/cs_binding_generator/config.py
RENAMED
|
@@ -3,10 +3,27 @@ XML configuration file parsing for C# bindings generator
|
|
|
3
3
|
"""
|
|
4
4
|
|
|
5
5
|
import xml.etree.ElementTree as ET
|
|
6
|
+
from dataclasses import dataclass, field
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@dataclass
|
|
10
|
+
class BindingConfig:
|
|
11
|
+
"""Configuration for C# bindings generation"""
|
|
12
|
+
header_library_pairs: list[tuple[str, str]] = field(default_factory=list)
|
|
13
|
+
include_dirs: list[str] = field(default_factory=list)
|
|
14
|
+
renames: list[tuple[str, str, bool]] = field(default_factory=list)
|
|
15
|
+
removals: list[tuple[str, bool]] = field(default_factory=list)
|
|
16
|
+
flag_enums: list[tuple[str, bool]] = field(default_factory=list)
|
|
17
|
+
library_class_names: dict[str, str] = field(default_factory=dict)
|
|
18
|
+
library_namespaces: dict[str, str] = field(default_factory=dict)
|
|
19
|
+
library_using_statements: dict[str, list[str]] = field(default_factory=dict)
|
|
20
|
+
visibility: str = "public"
|
|
21
|
+
global_constants: list[tuple[str, str, str, bool]] = field(default_factory=list)
|
|
22
|
+
global_defines: list[tuple[str, str | None]] = field(default_factory=list)
|
|
6
23
|
|
|
7
24
|
|
|
8
25
|
def parse_config_file(config_path):
|
|
9
|
-
"""Parse XML configuration file and return
|
|
26
|
+
"""Parse XML configuration file and return BindingConfig object"""
|
|
10
27
|
try:
|
|
11
28
|
tree = ET.parse(config_path)
|
|
12
29
|
root = tree.getroot()
|
|
@@ -14,20 +31,13 @@ def parse_config_file(config_path):
|
|
|
14
31
|
if root.tag != "bindings":
|
|
15
32
|
raise ValueError(f"Expected root element 'bindings', got '{root.tag}'")
|
|
16
33
|
|
|
17
|
-
|
|
18
|
-
include_dirs = []
|
|
19
|
-
renames = [] # Changed to list of (from, to, is_regex) tuples
|
|
20
|
-
removals = [] # List of (pattern, is_regex) tuples
|
|
21
|
-
library_class_names = {} # Map library name to class name
|
|
22
|
-
library_namespaces = {} # Map library name to namespace
|
|
23
|
-
library_using_statements = {} # Map library name to list of using statements
|
|
24
|
-
library_constants = {} # Map library name to list of (name, pattern, type) tuples
|
|
34
|
+
config = BindingConfig()
|
|
25
35
|
|
|
26
36
|
# Get global visibility setting (default to "public")
|
|
27
|
-
visibility = root.get("visibility", "public").strip().lower()
|
|
28
|
-
if visibility not in ("public", "internal"):
|
|
37
|
+
config.visibility = root.get("visibility", "public").strip().lower()
|
|
38
|
+
if config.visibility not in ("public", "internal"):
|
|
29
39
|
import sys
|
|
30
|
-
print(f"Error: Invalid visibility value '{visibility}'. Must be 'public' or 'internal'.", file=sys.stderr)
|
|
40
|
+
print(f"Error: Invalid visibility value '{config.visibility}'. Must be 'public' or 'internal'.", file=sys.stderr)
|
|
31
41
|
sys.exit(1)
|
|
32
42
|
|
|
33
43
|
# Get global include directories
|
|
@@ -35,7 +45,7 @@ def parse_config_file(config_path):
|
|
|
35
45
|
path = include_dir.get("path")
|
|
36
46
|
if not path:
|
|
37
47
|
raise ValueError("Include directory element missing 'path' attribute")
|
|
38
|
-
include_dirs.append(path.strip())
|
|
48
|
+
config.include_dirs.append(path.strip())
|
|
39
49
|
|
|
40
50
|
# Get global renames (support both simple and regex)
|
|
41
51
|
for rename in root.findall("rename"):
|
|
@@ -44,7 +54,7 @@ def parse_config_file(config_path):
|
|
|
44
54
|
if not from_name or not to_name:
|
|
45
55
|
raise ValueError("Rename element missing 'from' or 'to' attribute")
|
|
46
56
|
is_regex = rename.get("regex", "false").lower() == "true"
|
|
47
|
-
renames.append((from_name.strip(), to_name.strip(), is_regex))
|
|
57
|
+
config.renames.append((from_name.strip(), to_name.strip(), is_regex))
|
|
48
58
|
|
|
49
59
|
# Get global removals (support both simple and regex)
|
|
50
60
|
for remove in root.findall("remove"):
|
|
@@ -52,12 +62,29 @@ def parse_config_file(config_path):
|
|
|
52
62
|
if not pattern:
|
|
53
63
|
raise ValueError("Remove element missing 'pattern' attribute")
|
|
54
64
|
is_regex = remove.get("regex", "false").lower() == "true"
|
|
55
|
-
removals.append((pattern.strip(), is_regex))
|
|
65
|
+
config.removals.append((pattern.strip(), is_regex))
|
|
66
|
+
|
|
67
|
+
# Get global flag enums (enum patterns that should have [Flags] attribute)
|
|
68
|
+
for flag_enum in root.findall("flags"):
|
|
69
|
+
pattern = flag_enum.get("pattern")
|
|
70
|
+
if not pattern:
|
|
71
|
+
raise ValueError("Flags element missing 'pattern' attribute")
|
|
72
|
+
is_regex = flag_enum.get("regex", "false").lower() == "true"
|
|
73
|
+
config.flag_enums.append((pattern.strip(), is_regex))
|
|
74
|
+
|
|
75
|
+
# Get global compiler defines
|
|
76
|
+
for define in root.findall("define"):
|
|
77
|
+
name = define.get("name")
|
|
78
|
+
if not name:
|
|
79
|
+
raise ValueError("Define element missing 'name' attribute")
|
|
80
|
+
value = define.get("value") # Optional, can be None
|
|
81
|
+
if value is not None:
|
|
82
|
+
value = value.strip()
|
|
83
|
+
config.global_defines.append((name.strip(), value))
|
|
56
84
|
|
|
57
85
|
# Get global constants (macros to extract)
|
|
58
86
|
# These are stored as a list of (name, pattern, type, is_flags) tuples
|
|
59
87
|
# They will be applied to all libraries during processing
|
|
60
|
-
global_constants = []
|
|
61
88
|
for const in root.findall("constants"):
|
|
62
89
|
const_name = const.get("name")
|
|
63
90
|
const_pattern = const.get("pattern")
|
|
@@ -69,7 +96,7 @@ def parse_config_file(config_path):
|
|
|
69
96
|
if not const_pattern:
|
|
70
97
|
raise ValueError("Constants element missing 'pattern' attribute")
|
|
71
98
|
|
|
72
|
-
global_constants.append((const_name.strip(), const_pattern.strip(), const_type.strip(), const_flags))
|
|
99
|
+
config.global_constants.append((const_name.strip(), const_pattern.strip(), const_type.strip(), const_flags))
|
|
73
100
|
|
|
74
101
|
for library in root.findall("library"):
|
|
75
102
|
library_name = library.get("name")
|
|
@@ -78,12 +105,12 @@ def parse_config_file(config_path):
|
|
|
78
105
|
|
|
79
106
|
# Get class name (default to NativeMethods if not specified)
|
|
80
107
|
class_name = library.get("class", "NativeMethods")
|
|
81
|
-
library_class_names[library_name.strip()] = class_name.strip()
|
|
108
|
+
config.library_class_names[library_name.strip()] = class_name.strip()
|
|
82
109
|
|
|
83
110
|
# Get namespace from library attribute
|
|
84
111
|
library_namespace = library.get("namespace")
|
|
85
112
|
if library_namespace is not None:
|
|
86
|
-
library_namespaces[library_name.strip()] = library_namespace.strip()
|
|
113
|
+
config.library_namespaces[library_name.strip()] = library_namespace.strip()
|
|
87
114
|
|
|
88
115
|
# Get using statements
|
|
89
116
|
using_statements = []
|
|
@@ -92,33 +119,23 @@ def parse_config_file(config_path):
|
|
|
92
119
|
if using_namespace:
|
|
93
120
|
using_statements.append(using_namespace.strip())
|
|
94
121
|
if using_statements:
|
|
95
|
-
library_using_statements[library_name.strip()] = using_statements
|
|
122
|
+
config.library_using_statements[library_name.strip()] = using_statements
|
|
96
123
|
|
|
97
124
|
# Get library-specific include directories
|
|
98
125
|
for include_dir in library.findall("include_directory"):
|
|
99
126
|
path = include_dir.get("path")
|
|
100
127
|
if not path:
|
|
101
128
|
raise ValueError(f"Include directory element in library '{library_name}' missing 'path' attribute")
|
|
102
|
-
include_dirs.append(path.strip())
|
|
129
|
+
config.include_dirs.append(path.strip())
|
|
103
130
|
|
|
104
131
|
# Get include files
|
|
105
132
|
for include in library.findall("include"):
|
|
106
133
|
header_path = include.get("file")
|
|
107
134
|
if not header_path:
|
|
108
135
|
raise ValueError(f"Include element in library '{library_name}' missing 'file' attribute")
|
|
109
|
-
header_library_pairs.append((header_path.strip(), library_name.strip()))
|
|
110
|
-
|
|
111
|
-
return
|
|
112
|
-
header_library_pairs,
|
|
113
|
-
include_dirs,
|
|
114
|
-
renames,
|
|
115
|
-
removals,
|
|
116
|
-
library_class_names,
|
|
117
|
-
library_namespaces,
|
|
118
|
-
library_using_statements,
|
|
119
|
-
visibility,
|
|
120
|
-
global_constants,
|
|
121
|
-
)
|
|
136
|
+
config.header_library_pairs.append((header_path.strip(), library_name.strip()))
|
|
137
|
+
|
|
138
|
+
return config
|
|
122
139
|
|
|
123
140
|
except ET.ParseError as e:
|
|
124
141
|
raise ValueError(f"XML parsing error: {e}")
|
|
@@ -265,6 +265,9 @@ class CSharpBindingsGenerator:
|
|
|
265
265
|
|
|
266
266
|
elif cursor.kind == CursorKind.STRUCT_DECL:
|
|
267
267
|
if cursor.is_definition():
|
|
268
|
+
# Skip anonymous structs - they are handled inline by their parent struct
|
|
269
|
+
if cursor.spelling and "anonymous" in cursor.spelling.lower():
|
|
270
|
+
return
|
|
268
271
|
# Only generate code for non-system headers
|
|
269
272
|
if cursor.location.file:
|
|
270
273
|
file_path = str(Path(cursor.location.file.name).resolve())
|
|
@@ -292,6 +295,9 @@ class CSharpBindingsGenerator:
|
|
|
292
295
|
|
|
293
296
|
elif cursor.kind == CursorKind.UNION_DECL:
|
|
294
297
|
if cursor.is_definition():
|
|
298
|
+
# Skip anonymous unions - they are handled inline by their parent struct
|
|
299
|
+
if cursor.spelling and "anonymous" in cursor.spelling.lower():
|
|
300
|
+
return
|
|
295
301
|
# Only generate code for non-system headers
|
|
296
302
|
if cursor.location.file:
|
|
297
303
|
file_path = str(Path(cursor.location.file.name).resolve())
|
|
@@ -377,6 +383,28 @@ class CSharpBindingsGenerator:
|
|
|
377
383
|
self.seen_structs.add((type_name, None, None))
|
|
378
384
|
# Register as opaque type for pointer handling
|
|
379
385
|
self.type_mapper.opaque_types.add(type_name)
|
|
386
|
+
# Also generate an opaque type for the underlying struct name
|
|
387
|
+
# e.g. when typedef struct _XDisplay Display; we also want _XDisplay
|
|
388
|
+
try:
|
|
389
|
+
underlying_spelling = str(child.type.spelling)
|
|
390
|
+
except Exception:
|
|
391
|
+
underlying_spelling = None
|
|
392
|
+
if underlying_spelling:
|
|
393
|
+
# strip 'struct ' prefix if present
|
|
394
|
+
u_name = underlying_spelling
|
|
395
|
+
for prefix in ["const ", "volatile ", "struct ", "union ", "class "]:
|
|
396
|
+
if u_name.startswith(prefix):
|
|
397
|
+
u_name = u_name[len(prefix) :]
|
|
398
|
+
break
|
|
399
|
+
if u_name and u_name != type_name:
|
|
400
|
+
u_struct_key = (u_name, str(cursor.location.file), cursor.location.line)
|
|
401
|
+
if u_struct_key not in self.seen_structs:
|
|
402
|
+
u_code = self.code_generator.generate_opaque_type(u_name)
|
|
403
|
+
if u_code:
|
|
404
|
+
self._add_to_library_collection(self.generated_structs, self.current_library, u_code)
|
|
405
|
+
self.seen_structs.add(u_struct_key)
|
|
406
|
+
self.seen_structs.add((u_name, None, None))
|
|
407
|
+
self.type_mapper.opaque_types.add(u_name)
|
|
380
408
|
elif child.kind == CursorKind.STRUCT_DECL and not child.is_definition() and child.spelling:
|
|
381
409
|
# Direct forward declaration
|
|
382
410
|
struct_key = (child.spelling, str(cursor.location.file), cursor.location.line)
|
|
@@ -495,6 +523,7 @@ class CSharpBindingsGenerator:
|
|
|
495
523
|
library_using_statements: Optional[dict[str, list[str]]] = None,
|
|
496
524
|
visibility: str = "public",
|
|
497
525
|
global_constants: Optional[list[tuple[str, str, str, bool]]] = None,
|
|
526
|
+
global_defines: Optional[list[tuple[str, Optional[str]]]] = None,
|
|
498
527
|
) -> dict[str, str]:
|
|
499
528
|
"""Generate C# bindings from C header file(s)
|
|
500
529
|
|
|
@@ -503,12 +532,13 @@ class CSharpBindingsGenerator:
|
|
|
503
532
|
output: Output directory for generated files (required)
|
|
504
533
|
include_dirs: List of directories to search for included headers
|
|
505
534
|
ignore_missing: Continue processing even if some header files are not found
|
|
506
|
-
skip_variadic: Skip generating bindings for variadic functions
|
|
535
|
+
skip_variadic: Skip generating bindings for variadic functions (default: True)
|
|
507
536
|
library_class_names: Dict mapping library names to custom class names (defaults to NativeMethods)
|
|
508
537
|
library_namespaces: Dict mapping library names to custom namespaces
|
|
509
538
|
library_using_statements: Dict mapping library names to lists of using statements
|
|
510
539
|
visibility: Visibility modifier for generated code ("public" or "internal")
|
|
511
540
|
global_constants: List of (name, pattern, type) tuples for macro extraction, applied to all libraries
|
|
541
|
+
global_defines: List of (name, value) tuples for compiler defines, applied to all headers
|
|
512
542
|
"""
|
|
513
543
|
# Store visibility setting
|
|
514
544
|
self.visibility = visibility
|
|
@@ -528,6 +558,9 @@ class CSharpBindingsGenerator:
|
|
|
528
558
|
# Store global constants
|
|
529
559
|
self.global_constants = global_constants or []
|
|
530
560
|
|
|
561
|
+
# Store global defines
|
|
562
|
+
self.global_defines = global_defines or []
|
|
563
|
+
|
|
531
564
|
# Clear previous state
|
|
532
565
|
self._clear_state()
|
|
533
566
|
|
|
@@ -539,6 +572,13 @@ class CSharpBindingsGenerator:
|
|
|
539
572
|
for include_dir in include_dirs:
|
|
540
573
|
clang_args.append(f"-I{include_dir}")
|
|
541
574
|
|
|
575
|
+
# Add global compiler defines
|
|
576
|
+
for name, value in self.global_defines:
|
|
577
|
+
if value is None or value == "":
|
|
578
|
+
clang_args.append(f"-D{name}")
|
|
579
|
+
else:
|
|
580
|
+
clang_args.append(f"-D{name}={value}")
|
|
581
|
+
|
|
542
582
|
# Add system include paths so clang can find standard headers
|
|
543
583
|
# These paths are typical locations for system headers
|
|
544
584
|
import subprocess
|
|
@@ -671,8 +711,13 @@ class CSharpBindingsGenerator:
|
|
|
671
711
|
if underlying_type and underlying_type != "int":
|
|
672
712
|
inheritance_clause = f" : {underlying_type}"
|
|
673
713
|
|
|
714
|
+
# Check if this enum should have [Flags] attribute
|
|
715
|
+
flags_attribute = ""
|
|
716
|
+
if self.type_mapper.is_flag_enum(enum_name):
|
|
717
|
+
flags_attribute = "[Flags]\n"
|
|
718
|
+
|
|
674
719
|
values_str = "\n".join([f" {name} = {value}," for name, value in members])
|
|
675
|
-
code = f"""{self.visibility} enum {enum_name}{inheritance_clause}
|
|
720
|
+
code = f"""{flags_attribute}{self.visibility} enum {enum_name}{inheritance_clause}
|
|
676
721
|
{{
|
|
677
722
|
{values_str}
|
|
678
723
|
}}
|