Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
97 changes: 85 additions & 12 deletions Bindgen.NET/BindingGenerator.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using System.Diagnostics.CodeAnalysis;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.Text;
using ClangSharp;
Expand Down Expand Up @@ -112,11 +112,11 @@ public static string Generate(BindingOptions options)
using System;
using System.Runtime.InteropServices;
using System.Runtime.CompilerServices;

{{(Options.GenerateDisableRuntimeMarshallingAttribute ? "[assembly: DisableRuntimeMarshalling]" : "")}}

namespace {{Options.Namespace}};

public static unsafe partial class {{Options.Class}}
{
{{GenerateBindgenInternal()}}
Expand Down Expand Up @@ -439,7 +439,7 @@ private static string GetTypeName(Type? type)
typeName = GetTypeName(autoType.GetDeducedType);

if (type is ConstantArrayType constantArrayType)
typeName = $"{GetTypeName(constantArrayType.ElementType)}_{constantArrayType.Size}";
typeName = $"{GetTypeIdentifier(constantArrayType.ElementType)}_{constantArrayType.Size}";

if (type is FunctionProtoType functionProtoType)
typeName = GetCSharpFunctionPointer(functionProtoType);
Expand Down Expand Up @@ -664,23 +664,24 @@ private static string GenerateFunctionDecl(FunctionDecl functionDecl)

private static string GenerateConstantArrayType(ConstantArrayType type)
{
string name = GetTypeName(type.ElementType);
string elementTypeName = GetTypeName(type.ElementType);
string structName = GetTypeName(type);
return GenerateOuterDeclarations(type, $$"""
[InlineArray({{type.Size}})]
public partial struct {{name}}_{{type.Size}}
public partial struct {{structName}}
{
public {{name}} Item0;
public {{elementTypeName}} Item0;
}
""");
}

private static string GenerateConstantArrayTypeEqualityMethods(ConstantArrayType type)
{
string name = GetTypeName(type);
string structIdentifier = GetTypeName(type);
return GenerateOuterDeclarations(type, $$"""
public partial struct {{name}} : IEquatable<{{name}}>
public partial struct {{structIdentifier}} : IEquatable<{{structIdentifier}}>
{
{{GenerateRecordEqualityFunctions(name)}}
{{GenerateRecordEqualityFunctions(structIdentifier)}}
}
""");
}
Expand Down Expand Up @@ -762,7 +763,7 @@ private static string GenerateEnumDecl(EnumDecl enumDecl)

return GenerateOuterDeclarations(enumDecl.TypeForDecl, $@"
public enum {GetTypeName(enumDecl.TypeForDecl)} : {GetIntegerName(enumDecl.IntegerType.Handle.SizeOf, hasNegatives, "INVALID_ENUM_INTEGER")}
{{
{{
{string.Join(",\n", enumMembers)}
}}
");
Expand Down Expand Up @@ -914,6 +915,78 @@ private static string GenerateMacroDummy(MacroDefinitionRecord macro)
return $"const __auto_type {MacroPrefix}{macro.Name} = {value};";
}

/// <summary>
/// Returns a valid C# identifier name for the given type.
/// Unlike <see cref="GetTypeName"/>, this always returns a name that can
/// be used as a struct/class identifier (no special characters like *, &lt;, &gt;).
/// </summary>
private static string GetTypeIdentifier(Type type)
{
string name = GetTypeName(type);

// Check if the name is already a valid C# identifier
if (IsValidIdentifier(name))
return name;

// For function pointer types, generate a deterministic hash-based name
if (type is FunctionProtoType fpt)
{
string sig = GetCSharpFunctionPointer(fpt);
int hash = GetDeterministicHashCode(sig);
return $"_FunctionPtr_{hash:X8}";
}

if (type is PointerType ptrType && ptrType.CanonicalType.PointeeType is FunctionProtoType)
{
string sig = GetCSharpFunctionPointer((FunctionProtoType)ptrType.CanonicalType.PointeeType);
int hash = GetDeterministicHashCode(sig);
return $"_FunctionPtr_{hash:X8}";
}

// For pointer/reference types, replace special chars for identifier safety
if (name.Contains('*') || name.Contains('<') || name.Contains('>') || name.Contains(','))
{
return name
.Replace('*', 'P')
.Replace('<', '_')
.Replace('>', '_')
.Replace(',', '_')
.Replace(' ', '_')
.Replace('.', '_');
}

// Fallback: strip all non-identifier characters
return string.Concat(name.Where(c => char.IsLetterOrDigit(c) || c == '_'));
}

private static bool IsValidIdentifier(string name)
{
if (string.IsNullOrEmpty(name))
return false;

if (!char.IsLetter(name[0]) && name[0] != '_')
return false;

for (int i = 1; i < name.Length; i++)
{
if (!char.IsLetterOrDigit(name[i]) && name[i] != '_')
return false;
}

return true;
}

private static int GetDeterministicHashCode(string str)
{
unchecked
{
int hash = 17;
foreach (char c in str)
hash = hash * 31 + c;
return hash;
}
}

private static string GenerateRecordEqualityFunctions(string recordName)
{
return $$"""
Expand Down