cs-binding-generator 1.0.1.dev39__tar.gz → 1.0.1.dev40__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.dev39 → cs_binding_generator-1.0.1.dev40}/PKG-INFO +2 -1
- {cs_binding_generator-1.0.1.dev39 → cs_binding_generator-1.0.1.dev40}/README.md +1 -0
- {cs_binding_generator-1.0.1.dev39 → cs_binding_generator-1.0.1.dev40}/cs_binding_generator/_version.py +3 -3
- {cs_binding_generator-1.0.1.dev39 → cs_binding_generator-1.0.1.dev40}/cs_binding_generator/code_generators.py +46 -1
- {cs_binding_generator-1.0.1.dev39 → cs_binding_generator-1.0.1.dev40}/cs_binding_generator/config.py +8 -0
- {cs_binding_generator-1.0.1.dev39 → cs_binding_generator-1.0.1.dev40}/cs_binding_generator/generator.py +5 -2
- {cs_binding_generator-1.0.1.dev39 → cs_binding_generator-1.0.1.dev40}/cs_binding_generator/main.py +1 -0
- {cs_binding_generator-1.0.1.dev39 → cs_binding_generator-1.0.1.dev40}/cs_binding_generator.egg-info/PKG-INFO +2 -1
- {cs_binding_generator-1.0.1.dev39 → cs_binding_generator-1.0.1.dev40}/cs_binding_generator.egg-info/SOURCES.txt +1 -0
- {cs_binding_generator-1.0.1.dev39 → cs_binding_generator-1.0.1.dev40}/docs/XML_CONFIG.md +49 -0
- cs_binding_generator-1.0.1.dev40/tests/test_utf8_byte_overloads.py +161 -0
- {cs_binding_generator-1.0.1.dev39 → cs_binding_generator-1.0.1.dev40}/.coverage +0 -0
- {cs_binding_generator-1.0.1.dev39 → cs_binding_generator-1.0.1.dev40}/.flake8 +0 -0
- {cs_binding_generator-1.0.1.dev39 → cs_binding_generator-1.0.1.dev40}/.github/workflows/publish.yml +0 -0
- {cs_binding_generator-1.0.1.dev39 → cs_binding_generator-1.0.1.dev40}/.gitignore +0 -0
- {cs_binding_generator-1.0.1.dev39 → cs_binding_generator-1.0.1.dev40}/COPILOT_CONTEXT.md +0 -0
- {cs_binding_generator-1.0.1.dev39 → cs_binding_generator-1.0.1.dev40}/LICENSE +0 -0
- {cs_binding_generator-1.0.1.dev39 → cs_binding_generator-1.0.1.dev40}/cs_binding_generator/__init__.py +0 -0
- {cs_binding_generator-1.0.1.dev39 → cs_binding_generator-1.0.1.dev40}/cs_binding_generator/constants.py +0 -0
- {cs_binding_generator-1.0.1.dev39 → cs_binding_generator-1.0.1.dev40}/cs_binding_generator/type_mapper.py +0 -0
- {cs_binding_generator-1.0.1.dev39 → cs_binding_generator-1.0.1.dev40}/cs_binding_generator.egg-info/dependency_links.txt +0 -0
- {cs_binding_generator-1.0.1.dev39 → cs_binding_generator-1.0.1.dev40}/cs_binding_generator.egg-info/entry_points.txt +0 -0
- {cs_binding_generator-1.0.1.dev39 → cs_binding_generator-1.0.1.dev40}/cs_binding_generator.egg-info/requires.txt +0 -0
- {cs_binding_generator-1.0.1.dev39 → cs_binding_generator-1.0.1.dev40}/cs_binding_generator.egg-info/top_level.txt +0 -0
- {cs_binding_generator-1.0.1.dev39 → cs_binding_generator-1.0.1.dev40}/docs/ARCHITECTURE.md +0 -0
- {cs_binding_generator-1.0.1.dev39 → cs_binding_generator-1.0.1.dev40}/docs/INCLUDE_DIRECTORIES.md +0 -0
- {cs_binding_generator-1.0.1.dev39 → cs_binding_generator-1.0.1.dev40}/docs/MULTI_FILE_OUTPUT.md +0 -0
- {cs_binding_generator-1.0.1.dev39 → cs_binding_generator-1.0.1.dev40}/docs/RENAMING_EXAMPLE.xml +0 -0
- {cs_binding_generator-1.0.1.dev39 → cs_binding_generator-1.0.1.dev40}/docs/TROUBLESHOOTING.md +0 -0
- {cs_binding_generator-1.0.1.dev39 → cs_binding_generator-1.0.1.dev40}/enter_devenv.sh +0 -0
- {cs_binding_generator-1.0.1.dev39 → cs_binding_generator-1.0.1.dev40}/pyproject.toml +0 -0
- {cs_binding_generator-1.0.1.dev39 → cs_binding_generator-1.0.1.dev40}/run_tests.sh +0 -0
- {cs_binding_generator-1.0.1.dev39 → cs_binding_generator-1.0.1.dev40}/setup.cfg +0 -0
- {cs_binding_generator-1.0.1.dev39 → cs_binding_generator-1.0.1.dev40}/test_dotnet/FreeTypeTest/FreeTypeTest.csproj +0 -0
- {cs_binding_generator-1.0.1.dev39 → cs_binding_generator-1.0.1.dev40}/test_dotnet/FreeTypeTest/bindings.cs +0 -0
- {cs_binding_generator-1.0.1.dev39 → cs_binding_generator-1.0.1.dev40}/test_dotnet/FreeTypeTest/cs-bindings.xml +0 -0
- {cs_binding_generator-1.0.1.dev39 → cs_binding_generator-1.0.1.dev40}/test_dotnet/FreeTypeTest/freetype.cs +0 -0
- {cs_binding_generator-1.0.1.dev39 → cs_binding_generator-1.0.1.dev40}/test_dotnet/LibtcodTest/LibtcodTest.csproj +0 -0
- {cs_binding_generator-1.0.1.dev39 → cs_binding_generator-1.0.1.dev40}/test_dotnet/LibtcodTest/SDL3.cs +0 -0
- {cs_binding_generator-1.0.1.dev39 → cs_binding_generator-1.0.1.dev40}/test_dotnet/LibtcodTest/bindings.cs +0 -0
- {cs_binding_generator-1.0.1.dev39 → cs_binding_generator-1.0.1.dev40}/test_dotnet/LibtcodTest/cs-bindings.xml +0 -0
- {cs_binding_generator-1.0.1.dev39 → cs_binding_generator-1.0.1.dev40}/test_dotnet/LibtcodTest/libtcod.cs +0 -0
- {cs_binding_generator-1.0.1.dev39 → cs_binding_generator-1.0.1.dev40}/test_dotnet/SDL3Test/SDL3.cs +0 -0
- {cs_binding_generator-1.0.1.dev39 → cs_binding_generator-1.0.1.dev40}/test_dotnet/SDL3Test/SDL3Test.csproj +0 -0
- {cs_binding_generator-1.0.1.dev39 → cs_binding_generator-1.0.1.dev40}/test_dotnet/SDL3Test/SDL3Test.sln +0 -0
- {cs_binding_generator-1.0.1.dev39 → cs_binding_generator-1.0.1.dev40}/test_dotnet/SDL3Test/bindings.cs +0 -0
- {cs_binding_generator-1.0.1.dev39 → cs_binding_generator-1.0.1.dev40}/test_dotnet/SDL3Test/cs-bindings.xml +0 -0
- {cs_binding_generator-1.0.1.dev39 → cs_binding_generator-1.0.1.dev40}/tests/__init__.py +0 -0
- {cs_binding_generator-1.0.1.dev39 → cs_binding_generator-1.0.1.dev40}/tests/conftest.py +0 -0
- {cs_binding_generator-1.0.1.dev39 → cs_binding_generator-1.0.1.dev40}/tests/fixtures.py +0 -0
- {cs_binding_generator-1.0.1.dev39 → cs_binding_generator-1.0.1.dev40}/tests/test_cli.py +0 -0
- {cs_binding_generator-1.0.1.dev39 → cs_binding_generator-1.0.1.dev40}/tests/test_cli_extended.py +0 -0
- {cs_binding_generator-1.0.1.dev39 → cs_binding_generator-1.0.1.dev40}/tests/test_code_generators.py +0 -0
- {cs_binding_generator-1.0.1.dev39 → cs_binding_generator-1.0.1.dev40}/tests/test_defines.py +0 -0
- {cs_binding_generator-1.0.1.dev39 → cs_binding_generator-1.0.1.dev40}/tests/test_edge_cases.py +0 -0
- {cs_binding_generator-1.0.1.dev39 → cs_binding_generator-1.0.1.dev40}/tests/test_error_handling.py +0 -0
- {cs_binding_generator-1.0.1.dev39 → cs_binding_generator-1.0.1.dev40}/tests/test_flag_enums.py +0 -0
- {cs_binding_generator-1.0.1.dev39 → cs_binding_generator-1.0.1.dev40}/tests/test_generator.py +0 -0
- {cs_binding_generator-1.0.1.dev39 → cs_binding_generator-1.0.1.dev40}/tests/test_macro_constants.py +0 -0
- {cs_binding_generator-1.0.1.dev39 → cs_binding_generator-1.0.1.dev40}/tests/test_multi_file_deduplication.py +0 -0
- {cs_binding_generator-1.0.1.dev39 → cs_binding_generator-1.0.1.dev40}/tests/test_opaque_typedef_underlying.py +0 -0
- {cs_binding_generator-1.0.1.dev39 → cs_binding_generator-1.0.1.dev40}/tests/test_removal.py +0 -0
- {cs_binding_generator-1.0.1.dev39 → cs_binding_generator-1.0.1.dev40}/tests/test_renaming.py +0 -0
- {cs_binding_generator-1.0.1.dev39 → cs_binding_generator-1.0.1.dev40}/tests/test_type_mapper.py +0 -0
- {cs_binding_generator-1.0.1.dev39 → cs_binding_generator-1.0.1.dev40}/tests/test_type_mapping_extended.py +0 -0
- {cs_binding_generator-1.0.1.dev39 → cs_binding_generator-1.0.1.dev40}/tests/test_variadic_functions.py +0 -0
- {cs_binding_generator-1.0.1.dev39 → cs_binding_generator-1.0.1.dev40}/tests/test_xml_config.py +0 -0
|
@@ -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.dev40
|
|
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
|
|
@@ -50,6 +50,7 @@ The tool is configured primarily through XML configuration files, providing powe
|
|
|
50
50
|
- **Removal Support**: Filter out unwanted functions, types, or patterns
|
|
51
51
|
- **Compiler Defines**: Pass `-D` flags to libclang via `<define>` to enable optional API blocks and gate platform-specific code paths during parsing
|
|
52
52
|
- **Flag Enum Marking**: Tag auto-discovered C enums with `[Flags]` via `<flags>`, with exact-name or regex matching
|
|
53
|
+
- **UTF-8 Byte Overloads**: Opt in via `<utf8-byte-overloads/>` to emit a second `byte*`-param partial alongside every `string?`-param `[LibraryImport]`, so callers can hand pre-encoded UTF-8 buffers (u8 literals, pinned spans) to native code without re-encoding through a managed string
|
|
53
54
|
- **Macro Constants**: Extract C `#define` constants as C# enums (numeric mode) or as UTF-8 `ReadOnlySpan<byte>` members (string mode). Object-like and function-like macros are recursively expanded, and C-style casts in macro values are stripped before the numeric check, so chains like `SDL_BUTTON_MASK(SDL_BUTTON_LEFT)` and values like `((SDL_AudioDeviceID) 0xFFFFFFFFu)` resolve cleanly.
|
|
54
55
|
- **String Handling**: Provides both raw pointer and helper string methods for `char*` returns
|
|
55
56
|
- **Struct Generation**: Creates explicit layout structs with proper field offsets
|
|
@@ -21,6 +21,7 @@ The tool is configured primarily through XML configuration files, providing powe
|
|
|
21
21
|
- **Removal Support**: Filter out unwanted functions, types, or patterns
|
|
22
22
|
- **Compiler Defines**: Pass `-D` flags to libclang via `<define>` to enable optional API blocks and gate platform-specific code paths during parsing
|
|
23
23
|
- **Flag Enum Marking**: Tag auto-discovered C enums with `[Flags]` via `<flags>`, with exact-name or regex matching
|
|
24
|
+
- **UTF-8 Byte Overloads**: Opt in via `<utf8-byte-overloads/>` to emit a second `byte*`-param partial alongside every `string?`-param `[LibraryImport]`, so callers can hand pre-encoded UTF-8 buffers (u8 literals, pinned spans) to native code without re-encoding through a managed string
|
|
24
25
|
- **Macro Constants**: Extract C `#define` constants as C# enums (numeric mode) or as UTF-8 `ReadOnlySpan<byte>` members (string mode). Object-like and function-like macros are recursively expanded, and C-style casts in macro values are stripped before the numeric check, so chains like `SDL_BUTTON_MASK(SDL_BUTTON_LEFT)` and values like `((SDL_AudioDeviceID) 0xFFFFFFFFu)` resolve cleanly.
|
|
25
26
|
- **String Handling**: Provides both raw pointer and helper string methods for `char*` returns
|
|
26
27
|
- **Struct Generation**: Creates explicit layout structs with proper field offsets
|
|
@@ -18,7 +18,7 @@ version_tuple: tuple[int | str, ...]
|
|
|
18
18
|
commit_id: str | None
|
|
19
19
|
__commit_id__: str | None
|
|
20
20
|
|
|
21
|
-
__version__ = version = '1.0.1.
|
|
22
|
-
__version_tuple__ = version_tuple = (1, 0, 1, '
|
|
21
|
+
__version__ = version = '1.0.1.dev40'
|
|
22
|
+
__version_tuple__ = version_tuple = (1, 0, 1, 'dev40')
|
|
23
23
|
|
|
24
|
-
__commit_id__ = commit_id = '
|
|
24
|
+
__commit_id__ = commit_id = 'g7677f88ad'
|
|
@@ -12,12 +12,23 @@ from .type_mapper import TypeMapper
|
|
|
12
12
|
class CodeGenerator:
|
|
13
13
|
"""Generates C# code from libclang AST nodes"""
|
|
14
14
|
|
|
15
|
-
def __init__(
|
|
15
|
+
def __init__(
|
|
16
|
+
self,
|
|
17
|
+
type_mapper: TypeMapper,
|
|
18
|
+
visibility: str = "public",
|
|
19
|
+
skip_variadic: bool = False,
|
|
20
|
+
utf8_byte_overloads: bool = False,
|
|
21
|
+
):
|
|
16
22
|
self.type_mapper = type_mapper
|
|
17
23
|
self.anonymous_enum_counter = 0
|
|
18
24
|
self.visibility = visibility
|
|
19
25
|
self.skip_variadic = skip_variadic
|
|
20
26
|
self.has_variadic_functions = False
|
|
27
|
+
# When True, generate_function emits a parallel `byte*`-param overload alongside
|
|
28
|
+
# the primary `[LibraryImport]` for any function whose original signature contains
|
|
29
|
+
# a `string?` parameter. Lets callers pass pre-encoded UTF-8 buffers without the
|
|
30
|
+
# managed-string → UTF-8 re-encode round trip.
|
|
31
|
+
self.utf8_byte_overloads = utf8_byte_overloads
|
|
21
32
|
|
|
22
33
|
def generate_function(self, cursor, library_name: str) -> str:
|
|
23
34
|
"""Generate C# LibraryImport for a function"""
|
|
@@ -100,6 +111,40 @@ class CodeGenerator:
|
|
|
100
111
|
code = f""" [LibraryImport("{library_name}", EntryPoint = "{original_func_name}", StringMarshalling = StringMarshalling.Utf8)]
|
|
101
112
|
[UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
|
|
102
113
|
{return_marshal} {self.visibility} static partial {result_type} {func_name}({params_str});
|
|
114
|
+
"""
|
|
115
|
+
|
|
116
|
+
# Optional byte*-param overload. Emitted only when (a) the opt-in flag is set,
|
|
117
|
+
# (b) the function isn't variadic, and (c) at least one parameter was mapped to
|
|
118
|
+
# `string?`. The overload swaps every `string?` param for `byte*`, letting callers
|
|
119
|
+
# pass pre-encoded UTF-8 buffers (u8 literals, pinned spans) without re-encoding.
|
|
120
|
+
# The C# compiler picks between the two overloads by argument type at the call site.
|
|
121
|
+
if self.utf8_byte_overloads and not is_variadic_for_generation:
|
|
122
|
+
byte_params: list[str] = []
|
|
123
|
+
has_string_param = False
|
|
124
|
+
for i, arg in enumerate(cursor.get_arguments()):
|
|
125
|
+
arg_type = self.type_mapper.map_type(arg.type, is_return_type=False)
|
|
126
|
+
if arg_type is None:
|
|
127
|
+
# If the primary emit accepted these params, the secondary should too.
|
|
128
|
+
# Skip silently rather than emit a half-built overload.
|
|
129
|
+
byte_params = []
|
|
130
|
+
has_string_param = False
|
|
131
|
+
break
|
|
132
|
+
arg_name = arg.spelling or f"param{i}"
|
|
133
|
+
arg_name = self._escape_keyword(arg_name)
|
|
134
|
+
if arg_type == "string?":
|
|
135
|
+
has_string_param = True
|
|
136
|
+
byte_params.append(f"byte* {arg_name}")
|
|
137
|
+
elif arg_type == "bool":
|
|
138
|
+
byte_params.append(f"[MarshalAs(UnmanagedType.I1)] {arg_type} {arg_name}")
|
|
139
|
+
else:
|
|
140
|
+
byte_params.append(f"{arg_type} {arg_name}")
|
|
141
|
+
|
|
142
|
+
if has_string_param:
|
|
143
|
+
byte_params_str = ", ".join(byte_params)
|
|
144
|
+
code += f"""
|
|
145
|
+
[LibraryImport("{library_name}", EntryPoint = "{original_func_name}", StringMarshalling = StringMarshalling.Utf8)]
|
|
146
|
+
[UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
|
|
147
|
+
{return_marshal} {self.visibility} static partial {result_type} {func_name}({byte_params_str});
|
|
103
148
|
"""
|
|
104
149
|
|
|
105
150
|
# Add helper function for char* return types (skip for variadic functions)
|
{cs_binding_generator-1.0.1.dev39 → cs_binding_generator-1.0.1.dev40}/cs_binding_generator/config.py
RENAMED
|
@@ -20,6 +20,7 @@ class BindingConfig:
|
|
|
20
20
|
visibility: str = "public"
|
|
21
21
|
global_constants: list[tuple[str, str, str, bool]] = field(default_factory=list)
|
|
22
22
|
global_defines: list[tuple[str, str | None]] = field(default_factory=list)
|
|
23
|
+
utf8_byte_overloads: bool = False
|
|
23
24
|
|
|
24
25
|
|
|
25
26
|
def parse_config_file(config_path):
|
|
@@ -103,6 +104,13 @@ def parse_config_file(config_path):
|
|
|
103
104
|
((const_name or "").strip(), const_pattern.strip(), const_type, const_flags)
|
|
104
105
|
)
|
|
105
106
|
|
|
107
|
+
# Opt-in: for every function that has at least one `string?` parameter (mapped
|
|
108
|
+
# from a C char* arg), emit a parallel byte*-param overload alongside the primary
|
|
109
|
+
# P/Invoke. Lets callers pass pre-encoded UTF-8 buffers (u8 literals, pinned spans)
|
|
110
|
+
# without the round trip through Encoding.UTF8 → marshaller → native.
|
|
111
|
+
if root.find("utf8-byte-overloads") is not None:
|
|
112
|
+
config.utf8_byte_overloads = True
|
|
113
|
+
|
|
106
114
|
for library in root.findall("library"):
|
|
107
115
|
library_name = library.get("name")
|
|
108
116
|
if not library_name:
|
|
@@ -779,6 +779,7 @@ class CSharpBindingsGenerator:
|
|
|
779
779
|
visibility: str = "public",
|
|
780
780
|
global_constants: Optional[list[tuple[str, str, str, bool]]] = None,
|
|
781
781
|
global_defines: Optional[list[tuple[str, Optional[str]]]] = None,
|
|
782
|
+
utf8_byte_overloads: bool = False,
|
|
782
783
|
) -> dict[str, str]:
|
|
783
784
|
"""Generate C# bindings from C header file(s)
|
|
784
785
|
|
|
@@ -798,8 +799,10 @@ class CSharpBindingsGenerator:
|
|
|
798
799
|
# Store visibility setting
|
|
799
800
|
self.visibility = visibility
|
|
800
801
|
|
|
801
|
-
# Initialize code generator with visibility and
|
|
802
|
-
self.code_generator = CodeGenerator(
|
|
802
|
+
# Initialize code generator with visibility, skip_variadic, and byte-overload flag
|
|
803
|
+
self.code_generator = CodeGenerator(
|
|
804
|
+
self.type_mapper, visibility, skip_variadic, utf8_byte_overloads
|
|
805
|
+
)
|
|
803
806
|
|
|
804
807
|
# Store library class names
|
|
805
808
|
self.library_class_names = library_class_names or {}
|
|
@@ -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.dev40
|
|
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
|
|
@@ -50,6 +50,7 @@ The tool is configured primarily through XML configuration files, providing powe
|
|
|
50
50
|
- **Removal Support**: Filter out unwanted functions, types, or patterns
|
|
51
51
|
- **Compiler Defines**: Pass `-D` flags to libclang via `<define>` to enable optional API blocks and gate platform-specific code paths during parsing
|
|
52
52
|
- **Flag Enum Marking**: Tag auto-discovered C enums with `[Flags]` via `<flags>`, with exact-name or regex matching
|
|
53
|
+
- **UTF-8 Byte Overloads**: Opt in via `<utf8-byte-overloads/>` to emit a second `byte*`-param partial alongside every `string?`-param `[LibraryImport]`, so callers can hand pre-encoded UTF-8 buffers (u8 literals, pinned spans) to native code without re-encoding through a managed string
|
|
53
54
|
- **Macro Constants**: Extract C `#define` constants as C# enums (numeric mode) or as UTF-8 `ReadOnlySpan<byte>` members (string mode). Object-like and function-like macros are recursively expanded, and C-style casts in macro values are stripped before the numeric check, so chains like `SDL_BUTTON_MASK(SDL_BUTTON_LEFT)` and values like `((SDL_AudioDeviceID) 0xFFFFFFFFu)` resolve cleanly.
|
|
54
55
|
- **String Handling**: Provides both raw pointer and helper string methods for `char*` returns
|
|
55
56
|
- **Struct Generation**: Creates explicit layout structs with proper field offsets
|
|
@@ -240,6 +240,55 @@ public enum WindowFlags : ulong
|
|
|
240
240
|
}
|
|
241
241
|
```
|
|
242
242
|
|
|
243
|
+
### UTF-8 Byte Overloads: `<utf8-byte-overloads/>`
|
|
244
|
+
|
|
245
|
+
Opt in to a second `[LibraryImport]` partial method for every non-variadic function
|
|
246
|
+
whose primary signature contains at least one `string?` parameter. The overload
|
|
247
|
+
swaps every `string?` for `byte*`, letting callers pass pre-encoded UTF-8 buffers
|
|
248
|
+
(u8 literals, pinned `ReadOnlySpan<byte>`, `byte[]` via `fixed`) to native code
|
|
249
|
+
without the `string` → marshaller → UTF-8 round trip.
|
|
250
|
+
|
|
251
|
+
**Attributes:** none. Presence of the element enables the feature.
|
|
252
|
+
|
|
253
|
+
```xml
|
|
254
|
+
<bindings>
|
|
255
|
+
<utf8-byte-overloads/>
|
|
256
|
+
<library name="SDL3" namespace="MyApp">
|
|
257
|
+
<include file="/usr/include/SDL3/SDL.h"/>
|
|
258
|
+
</library>
|
|
259
|
+
</bindings>
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
**Example:**
|
|
263
|
+
|
|
264
|
+
Input C:
|
|
265
|
+
```c
|
|
266
|
+
int SDL_SetStringProperty(unsigned int props, const char* name, const char* value);
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
Generated C# (both partials reference the same native symbol):
|
|
270
|
+
```csharp
|
|
271
|
+
[LibraryImport("SDL3", EntryPoint = "SDL_SetStringProperty", StringMarshalling = StringMarshalling.Utf8)]
|
|
272
|
+
[UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
|
|
273
|
+
public static partial int SDL_SetStringProperty(uint props, string? name, string? value);
|
|
274
|
+
|
|
275
|
+
[LibraryImport("SDL3", EntryPoint = "SDL_SetStringProperty", StringMarshalling = StringMarshalling.Utf8)]
|
|
276
|
+
[UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
|
|
277
|
+
public static partial int SDL_SetStringProperty(uint props, byte* name, byte* value);
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
Caller:
|
|
281
|
+
```csharp
|
|
282
|
+
fixed (byte* p = "SDL.gpu.shader.create.name"u8)
|
|
283
|
+
fixed (byte* v = nameBytes)
|
|
284
|
+
SDL_SetStringProperty(props, p, v);
|
|
285
|
+
```
|
|
286
|
+
|
|
287
|
+
The C# compiler resolves overloads by argument type. The overload is only emitted
|
|
288
|
+
for non-variadic functions that have at least one `string?` param — functions with
|
|
289
|
+
no string params are left as-is. Non-string parameters (including bool with its
|
|
290
|
+
`[MarshalAs(UnmanagedType.I1)]` attribute) are forwarded verbatim into the overload.
|
|
291
|
+
|
|
243
292
|
### Flag Enums: `<flags>`
|
|
244
293
|
|
|
245
294
|
Mark **auto-discovered** C enums (those declared as `typedef enum { ... } Name;`
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
"""Tests for the opt-in byte*-param overload feature.
|
|
2
|
+
|
|
3
|
+
When ``<utf8-byte-overloads/>`` is present in the XML config, every non-variadic
|
|
4
|
+
function whose primary `[LibraryImport]` has at least one ``string?`` parameter
|
|
5
|
+
gets a parallel partial method with each of those parameters retyped as ``byte*``.
|
|
6
|
+
Lets callers hand pre-encoded UTF-8 buffers (u8 literals, pinned spans) to the
|
|
7
|
+
native side without re-encoding through a managed string.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
import pytest
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
|
|
13
|
+
from cs_binding_generator.config import parse_config_file
|
|
14
|
+
from cs_binding_generator.generator import CSharpBindingsGenerator
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class TestUtf8ByteOverloadsXMLParsing:
|
|
18
|
+
"""The XML config exposes the feature as a presence-only ``<utf8-byte-overloads/>``."""
|
|
19
|
+
|
|
20
|
+
def test_presence_sets_flag(self, tmp_path):
|
|
21
|
+
config_file = tmp_path / "cfg.xml"
|
|
22
|
+
config_file.write_text(
|
|
23
|
+
"""
|
|
24
|
+
<bindings>
|
|
25
|
+
<utf8-byte-overloads/>
|
|
26
|
+
<library name="testlib">
|
|
27
|
+
<include file="/tmp/test.h"/>
|
|
28
|
+
</library>
|
|
29
|
+
</bindings>
|
|
30
|
+
"""
|
|
31
|
+
)
|
|
32
|
+
config = parse_config_file(str(config_file))
|
|
33
|
+
assert config.utf8_byte_overloads is True
|
|
34
|
+
|
|
35
|
+
def test_absence_keeps_default(self, tmp_path):
|
|
36
|
+
config_file = tmp_path / "cfg.xml"
|
|
37
|
+
config_file.write_text(
|
|
38
|
+
"""
|
|
39
|
+
<bindings>
|
|
40
|
+
<library name="testlib">
|
|
41
|
+
<include file="/tmp/test.h"/>
|
|
42
|
+
</library>
|
|
43
|
+
</bindings>
|
|
44
|
+
"""
|
|
45
|
+
)
|
|
46
|
+
config = parse_config_file(str(config_file))
|
|
47
|
+
assert config.utf8_byte_overloads is False
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
class TestUtf8ByteOverloadsEmit:
|
|
51
|
+
"""End-to-end checks against the generated `.cs`."""
|
|
52
|
+
|
|
53
|
+
def _generate(self, tmp_path, header_src, *, opt_in: bool):
|
|
54
|
+
"""Helper: produce a one-library binding from `header_src`."""
|
|
55
|
+
header = tmp_path / "test.h"
|
|
56
|
+
header.write_text(header_src)
|
|
57
|
+
opt_in_xml = "<utf8-byte-overloads/>" if opt_in else ""
|
|
58
|
+
config_xml = tmp_path / "cs-bindings.xml"
|
|
59
|
+
config_xml.write_text(
|
|
60
|
+
f"""
|
|
61
|
+
<bindings>
|
|
62
|
+
{opt_in_xml}
|
|
63
|
+
<library name="testlib" namespace="Test" class="Native">
|
|
64
|
+
<include file="{header}"/>
|
|
65
|
+
</library>
|
|
66
|
+
</bindings>
|
|
67
|
+
"""
|
|
68
|
+
)
|
|
69
|
+
config = parse_config_file(str(config_xml))
|
|
70
|
+
generator = CSharpBindingsGenerator()
|
|
71
|
+
result = generator.generate(
|
|
72
|
+
config.header_library_pairs,
|
|
73
|
+
output=str(tmp_path),
|
|
74
|
+
include_dirs=[str(tmp_path)],
|
|
75
|
+
library_namespaces=config.library_namespaces,
|
|
76
|
+
library_class_names=config.library_class_names,
|
|
77
|
+
utf8_byte_overloads=config.utf8_byte_overloads,
|
|
78
|
+
)
|
|
79
|
+
return result["testlib.cs"]
|
|
80
|
+
|
|
81
|
+
def test_disabled_emits_only_primary(self, tmp_path):
|
|
82
|
+
# Baseline: a function with a string? param produces ONLY the primary P/Invoke.
|
|
83
|
+
header = """
|
|
84
|
+
void log_message(const char* message);
|
|
85
|
+
"""
|
|
86
|
+
output = self._generate(tmp_path, header, opt_in=False)
|
|
87
|
+
# Primary present.
|
|
88
|
+
assert "public static partial void log_message(string? message);" in output
|
|
89
|
+
# No byte* overload.
|
|
90
|
+
assert "byte* message" not in output
|
|
91
|
+
|
|
92
|
+
def test_enabled_emits_byte_overload_for_single_string_param(self, tmp_path):
|
|
93
|
+
header = """
|
|
94
|
+
void log_message(const char* message);
|
|
95
|
+
"""
|
|
96
|
+
output = self._generate(tmp_path, header, opt_in=True)
|
|
97
|
+
# Primary still emitted.
|
|
98
|
+
assert "public static partial void log_message(string? message);" in output
|
|
99
|
+
# Plus a byte* overload.
|
|
100
|
+
assert "public static partial void log_message(byte* message);" in output
|
|
101
|
+
# Both reference the same native entry point.
|
|
102
|
+
assert output.count('EntryPoint = "log_message"') == 2
|
|
103
|
+
|
|
104
|
+
def test_enabled_swaps_every_string_param(self, tmp_path):
|
|
105
|
+
# Two char* params should both become byte* in the overload.
|
|
106
|
+
header = """
|
|
107
|
+
int set_string_property(unsigned int props, const char* name, const char* value);
|
|
108
|
+
"""
|
|
109
|
+
output = self._generate(tmp_path, header, opt_in=True)
|
|
110
|
+
assert (
|
|
111
|
+
"public static partial int set_string_property(uint props, string? name, string? value);"
|
|
112
|
+
in output
|
|
113
|
+
)
|
|
114
|
+
assert (
|
|
115
|
+
"public static partial int set_string_property(uint props, byte* name, byte* value);"
|
|
116
|
+
in output
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
def test_enabled_preserves_non_string_params(self, tmp_path):
|
|
120
|
+
# Non-string params should be passed through verbatim in the overload.
|
|
121
|
+
header = """
|
|
122
|
+
int frobnicate(int count, const char* label, float weight);
|
|
123
|
+
"""
|
|
124
|
+
output = self._generate(tmp_path, header, opt_in=True)
|
|
125
|
+
# The overload keeps `int count` and `float weight` intact.
|
|
126
|
+
assert (
|
|
127
|
+
"public static partial int frobnicate(int count, byte* label, float weight);"
|
|
128
|
+
in output
|
|
129
|
+
)
|
|
130
|
+
|
|
131
|
+
def test_enabled_skips_functions_without_string_params(self, tmp_path):
|
|
132
|
+
# Functions with no char* params get no overload — there's nothing to swap.
|
|
133
|
+
header = """
|
|
134
|
+
int add_numbers(int a, int b);
|
|
135
|
+
"""
|
|
136
|
+
output = self._generate(tmp_path, header, opt_in=True)
|
|
137
|
+
# Exactly one partial method declaration.
|
|
138
|
+
assert output.count("public static partial int add_numbers") == 1
|
|
139
|
+
|
|
140
|
+
def test_enabled_preserves_bool_marshalling_on_overload(self, tmp_path):
|
|
141
|
+
# A bool param needs its MarshalAs attribute on BOTH the primary and the overload.
|
|
142
|
+
header = """
|
|
143
|
+
unsigned char set_flag(const char* name, _Bool enabled);
|
|
144
|
+
"""
|
|
145
|
+
output = self._generate(tmp_path, header, opt_in=True)
|
|
146
|
+
# The byte* overload also keeps `[MarshalAs(UnmanagedType.I1)] bool enabled`.
|
|
147
|
+
assert (
|
|
148
|
+
"public static partial byte set_flag(byte* name, "
|
|
149
|
+
"[MarshalAs(UnmanagedType.I1)] bool enabled);"
|
|
150
|
+
) in output
|
|
151
|
+
|
|
152
|
+
def test_enabled_does_not_duplicate_when_overload_already_unique(self, tmp_path):
|
|
153
|
+
# Two unrelated functions, only one of which has a string param. Only that one
|
|
154
|
+
# gets the overload.
|
|
155
|
+
header = """
|
|
156
|
+
void with_string(const char* s);
|
|
157
|
+
int without_string(int x);
|
|
158
|
+
"""
|
|
159
|
+
output = self._generate(tmp_path, header, opt_in=True)
|
|
160
|
+
assert output.count("public static partial void with_string") == 2
|
|
161
|
+
assert output.count("public static partial int without_string") == 1
|
|
File without changes
|
|
File without changes
|
{cs_binding_generator-1.0.1.dev39 → cs_binding_generator-1.0.1.dev40}/.github/workflows/publish.yml
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{cs_binding_generator-1.0.1.dev39 → cs_binding_generator-1.0.1.dev40}/docs/INCLUDE_DIRECTORIES.md
RENAMED
|
File without changes
|
{cs_binding_generator-1.0.1.dev39 → cs_binding_generator-1.0.1.dev40}/docs/MULTI_FILE_OUTPUT.md
RENAMED
|
File without changes
|
{cs_binding_generator-1.0.1.dev39 → cs_binding_generator-1.0.1.dev40}/docs/RENAMING_EXAMPLE.xml
RENAMED
|
File without changes
|
{cs_binding_generator-1.0.1.dev39 → cs_binding_generator-1.0.1.dev40}/docs/TROUBLESHOOTING.md
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{cs_binding_generator-1.0.1.dev39 → cs_binding_generator-1.0.1.dev40}/test_dotnet/SDL3Test/SDL3.cs
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{cs_binding_generator-1.0.1.dev39 → cs_binding_generator-1.0.1.dev40}/tests/test_cli_extended.py
RENAMED
|
File without changes
|
{cs_binding_generator-1.0.1.dev39 → cs_binding_generator-1.0.1.dev40}/tests/test_code_generators.py
RENAMED
|
File without changes
|
|
File without changes
|
{cs_binding_generator-1.0.1.dev39 → cs_binding_generator-1.0.1.dev40}/tests/test_edge_cases.py
RENAMED
|
File without changes
|
{cs_binding_generator-1.0.1.dev39 → cs_binding_generator-1.0.1.dev40}/tests/test_error_handling.py
RENAMED
|
File without changes
|
{cs_binding_generator-1.0.1.dev39 → cs_binding_generator-1.0.1.dev40}/tests/test_flag_enums.py
RENAMED
|
File without changes
|
{cs_binding_generator-1.0.1.dev39 → cs_binding_generator-1.0.1.dev40}/tests/test_generator.py
RENAMED
|
File without changes
|
{cs_binding_generator-1.0.1.dev39 → cs_binding_generator-1.0.1.dev40}/tests/test_macro_constants.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{cs_binding_generator-1.0.1.dev39 → cs_binding_generator-1.0.1.dev40}/tests/test_renaming.py
RENAMED
|
File without changes
|
{cs_binding_generator-1.0.1.dev39 → cs_binding_generator-1.0.1.dev40}/tests/test_type_mapper.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{cs_binding_generator-1.0.1.dev39 → cs_binding_generator-1.0.1.dev40}/tests/test_xml_config.py
RENAMED
|
File without changes
|