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.
Files changed (67) hide show
  1. {cs_binding_generator-1.0.1.dev39 → cs_binding_generator-1.0.1.dev40}/PKG-INFO +2 -1
  2. {cs_binding_generator-1.0.1.dev39 → cs_binding_generator-1.0.1.dev40}/README.md +1 -0
  3. {cs_binding_generator-1.0.1.dev39 → cs_binding_generator-1.0.1.dev40}/cs_binding_generator/_version.py +3 -3
  4. {cs_binding_generator-1.0.1.dev39 → cs_binding_generator-1.0.1.dev40}/cs_binding_generator/code_generators.py +46 -1
  5. {cs_binding_generator-1.0.1.dev39 → cs_binding_generator-1.0.1.dev40}/cs_binding_generator/config.py +8 -0
  6. {cs_binding_generator-1.0.1.dev39 → cs_binding_generator-1.0.1.dev40}/cs_binding_generator/generator.py +5 -2
  7. {cs_binding_generator-1.0.1.dev39 → cs_binding_generator-1.0.1.dev40}/cs_binding_generator/main.py +1 -0
  8. {cs_binding_generator-1.0.1.dev39 → cs_binding_generator-1.0.1.dev40}/cs_binding_generator.egg-info/PKG-INFO +2 -1
  9. {cs_binding_generator-1.0.1.dev39 → cs_binding_generator-1.0.1.dev40}/cs_binding_generator.egg-info/SOURCES.txt +1 -0
  10. {cs_binding_generator-1.0.1.dev39 → cs_binding_generator-1.0.1.dev40}/docs/XML_CONFIG.md +49 -0
  11. cs_binding_generator-1.0.1.dev40/tests/test_utf8_byte_overloads.py +161 -0
  12. {cs_binding_generator-1.0.1.dev39 → cs_binding_generator-1.0.1.dev40}/.coverage +0 -0
  13. {cs_binding_generator-1.0.1.dev39 → cs_binding_generator-1.0.1.dev40}/.flake8 +0 -0
  14. {cs_binding_generator-1.0.1.dev39 → cs_binding_generator-1.0.1.dev40}/.github/workflows/publish.yml +0 -0
  15. {cs_binding_generator-1.0.1.dev39 → cs_binding_generator-1.0.1.dev40}/.gitignore +0 -0
  16. {cs_binding_generator-1.0.1.dev39 → cs_binding_generator-1.0.1.dev40}/COPILOT_CONTEXT.md +0 -0
  17. {cs_binding_generator-1.0.1.dev39 → cs_binding_generator-1.0.1.dev40}/LICENSE +0 -0
  18. {cs_binding_generator-1.0.1.dev39 → cs_binding_generator-1.0.1.dev40}/cs_binding_generator/__init__.py +0 -0
  19. {cs_binding_generator-1.0.1.dev39 → cs_binding_generator-1.0.1.dev40}/cs_binding_generator/constants.py +0 -0
  20. {cs_binding_generator-1.0.1.dev39 → cs_binding_generator-1.0.1.dev40}/cs_binding_generator/type_mapper.py +0 -0
  21. {cs_binding_generator-1.0.1.dev39 → cs_binding_generator-1.0.1.dev40}/cs_binding_generator.egg-info/dependency_links.txt +0 -0
  22. {cs_binding_generator-1.0.1.dev39 → cs_binding_generator-1.0.1.dev40}/cs_binding_generator.egg-info/entry_points.txt +0 -0
  23. {cs_binding_generator-1.0.1.dev39 → cs_binding_generator-1.0.1.dev40}/cs_binding_generator.egg-info/requires.txt +0 -0
  24. {cs_binding_generator-1.0.1.dev39 → cs_binding_generator-1.0.1.dev40}/cs_binding_generator.egg-info/top_level.txt +0 -0
  25. {cs_binding_generator-1.0.1.dev39 → cs_binding_generator-1.0.1.dev40}/docs/ARCHITECTURE.md +0 -0
  26. {cs_binding_generator-1.0.1.dev39 → cs_binding_generator-1.0.1.dev40}/docs/INCLUDE_DIRECTORIES.md +0 -0
  27. {cs_binding_generator-1.0.1.dev39 → cs_binding_generator-1.0.1.dev40}/docs/MULTI_FILE_OUTPUT.md +0 -0
  28. {cs_binding_generator-1.0.1.dev39 → cs_binding_generator-1.0.1.dev40}/docs/RENAMING_EXAMPLE.xml +0 -0
  29. {cs_binding_generator-1.0.1.dev39 → cs_binding_generator-1.0.1.dev40}/docs/TROUBLESHOOTING.md +0 -0
  30. {cs_binding_generator-1.0.1.dev39 → cs_binding_generator-1.0.1.dev40}/enter_devenv.sh +0 -0
  31. {cs_binding_generator-1.0.1.dev39 → cs_binding_generator-1.0.1.dev40}/pyproject.toml +0 -0
  32. {cs_binding_generator-1.0.1.dev39 → cs_binding_generator-1.0.1.dev40}/run_tests.sh +0 -0
  33. {cs_binding_generator-1.0.1.dev39 → cs_binding_generator-1.0.1.dev40}/setup.cfg +0 -0
  34. {cs_binding_generator-1.0.1.dev39 → cs_binding_generator-1.0.1.dev40}/test_dotnet/FreeTypeTest/FreeTypeTest.csproj +0 -0
  35. {cs_binding_generator-1.0.1.dev39 → cs_binding_generator-1.0.1.dev40}/test_dotnet/FreeTypeTest/bindings.cs +0 -0
  36. {cs_binding_generator-1.0.1.dev39 → cs_binding_generator-1.0.1.dev40}/test_dotnet/FreeTypeTest/cs-bindings.xml +0 -0
  37. {cs_binding_generator-1.0.1.dev39 → cs_binding_generator-1.0.1.dev40}/test_dotnet/FreeTypeTest/freetype.cs +0 -0
  38. {cs_binding_generator-1.0.1.dev39 → cs_binding_generator-1.0.1.dev40}/test_dotnet/LibtcodTest/LibtcodTest.csproj +0 -0
  39. {cs_binding_generator-1.0.1.dev39 → cs_binding_generator-1.0.1.dev40}/test_dotnet/LibtcodTest/SDL3.cs +0 -0
  40. {cs_binding_generator-1.0.1.dev39 → cs_binding_generator-1.0.1.dev40}/test_dotnet/LibtcodTest/bindings.cs +0 -0
  41. {cs_binding_generator-1.0.1.dev39 → cs_binding_generator-1.0.1.dev40}/test_dotnet/LibtcodTest/cs-bindings.xml +0 -0
  42. {cs_binding_generator-1.0.1.dev39 → cs_binding_generator-1.0.1.dev40}/test_dotnet/LibtcodTest/libtcod.cs +0 -0
  43. {cs_binding_generator-1.0.1.dev39 → cs_binding_generator-1.0.1.dev40}/test_dotnet/SDL3Test/SDL3.cs +0 -0
  44. {cs_binding_generator-1.0.1.dev39 → cs_binding_generator-1.0.1.dev40}/test_dotnet/SDL3Test/SDL3Test.csproj +0 -0
  45. {cs_binding_generator-1.0.1.dev39 → cs_binding_generator-1.0.1.dev40}/test_dotnet/SDL3Test/SDL3Test.sln +0 -0
  46. {cs_binding_generator-1.0.1.dev39 → cs_binding_generator-1.0.1.dev40}/test_dotnet/SDL3Test/bindings.cs +0 -0
  47. {cs_binding_generator-1.0.1.dev39 → cs_binding_generator-1.0.1.dev40}/test_dotnet/SDL3Test/cs-bindings.xml +0 -0
  48. {cs_binding_generator-1.0.1.dev39 → cs_binding_generator-1.0.1.dev40}/tests/__init__.py +0 -0
  49. {cs_binding_generator-1.0.1.dev39 → cs_binding_generator-1.0.1.dev40}/tests/conftest.py +0 -0
  50. {cs_binding_generator-1.0.1.dev39 → cs_binding_generator-1.0.1.dev40}/tests/fixtures.py +0 -0
  51. {cs_binding_generator-1.0.1.dev39 → cs_binding_generator-1.0.1.dev40}/tests/test_cli.py +0 -0
  52. {cs_binding_generator-1.0.1.dev39 → cs_binding_generator-1.0.1.dev40}/tests/test_cli_extended.py +0 -0
  53. {cs_binding_generator-1.0.1.dev39 → cs_binding_generator-1.0.1.dev40}/tests/test_code_generators.py +0 -0
  54. {cs_binding_generator-1.0.1.dev39 → cs_binding_generator-1.0.1.dev40}/tests/test_defines.py +0 -0
  55. {cs_binding_generator-1.0.1.dev39 → cs_binding_generator-1.0.1.dev40}/tests/test_edge_cases.py +0 -0
  56. {cs_binding_generator-1.0.1.dev39 → cs_binding_generator-1.0.1.dev40}/tests/test_error_handling.py +0 -0
  57. {cs_binding_generator-1.0.1.dev39 → cs_binding_generator-1.0.1.dev40}/tests/test_flag_enums.py +0 -0
  58. {cs_binding_generator-1.0.1.dev39 → cs_binding_generator-1.0.1.dev40}/tests/test_generator.py +0 -0
  59. {cs_binding_generator-1.0.1.dev39 → cs_binding_generator-1.0.1.dev40}/tests/test_macro_constants.py +0 -0
  60. {cs_binding_generator-1.0.1.dev39 → cs_binding_generator-1.0.1.dev40}/tests/test_multi_file_deduplication.py +0 -0
  61. {cs_binding_generator-1.0.1.dev39 → cs_binding_generator-1.0.1.dev40}/tests/test_opaque_typedef_underlying.py +0 -0
  62. {cs_binding_generator-1.0.1.dev39 → cs_binding_generator-1.0.1.dev40}/tests/test_removal.py +0 -0
  63. {cs_binding_generator-1.0.1.dev39 → cs_binding_generator-1.0.1.dev40}/tests/test_renaming.py +0 -0
  64. {cs_binding_generator-1.0.1.dev39 → cs_binding_generator-1.0.1.dev40}/tests/test_type_mapper.py +0 -0
  65. {cs_binding_generator-1.0.1.dev39 → cs_binding_generator-1.0.1.dev40}/tests/test_type_mapping_extended.py +0 -0
  66. {cs_binding_generator-1.0.1.dev39 → cs_binding_generator-1.0.1.dev40}/tests/test_variadic_functions.py +0 -0
  67. {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.dev39
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.dev39'
22
- __version_tuple__ = version_tuple = (1, 0, 1, 'dev39')
21
+ __version__ = version = '1.0.1.dev40'
22
+ __version_tuple__ = version_tuple = (1, 0, 1, 'dev40')
23
23
 
24
- __commit_id__ = commit_id = 'gb6b8a2450'
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__(self, type_mapper: TypeMapper, visibility: str = "public", skip_variadic: bool = False):
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)
@@ -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 skip_variadic flag
802
- self.code_generator = CodeGenerator(self.type_mapper, visibility, skip_variadic)
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 {}
@@ -126,6 +126,7 @@ Examples:
126
126
  visibility=config.visibility,
127
127
  global_constants=config.global_constants,
128
128
  global_defines=config.global_defines,
129
+ utf8_byte_overloads=config.utf8_byte_overloads,
129
130
  )
130
131
  except Exception as e:
131
132
  import traceback
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cs_binding_generator
3
- Version: 1.0.1.dev39
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
@@ -60,5 +60,6 @@ tests/test_removal.py
60
60
  tests/test_renaming.py
61
61
  tests/test_type_mapper.py
62
62
  tests/test_type_mapping_extended.py
63
+ tests/test_utf8_byte_overloads.py
63
64
  tests/test_variadic_functions.py
64
65
  tests/test_xml_config.py
@@ -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