Skip to content

Commit 6bb6a1e

Browse files
committed
Add support for generated instance methods being marked readonly
1 parent 6fa8605 commit 6bb6a1e

26 files changed

+1185
-1725
lines changed

README.md

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ See [LICENSE.md](LICENSE.md) in the repository root for more information.
5555

5656
### Building Managed
5757

58-
ClangSharp requires the [.NET 8 SDK](https://dotnet.microsoft.com/download/dotnet/8.0) and can be built simply with `dotnet build -c Release`.
58+
ClangSharp requires the [.NET 10 SDK](https://dotnet.microsoft.com/download/dotnet/10.0) and can be built simply with `dotnet build -c Release`.
5959

6060
You can reproduce what the CI environment does by running `./scripts/cibuild.cmd` on Windows or `./scripts.cibuild.sh` on Unix.
6161
This will download the required .NET SDK locally and use that to build the repo; it will also run through all available actions in the appropriate order.
@@ -133,7 +133,7 @@ This program will take a given set of C or C++ header files and generate C# bind
133133

134134
The simplest and recommended setup is to install the generator as a .NET tool and then use response files:
135135
```
136-
dotnet tool install --global ClangSharpPInvokeGenerator --version 20.1.2
136+
dotnet tool install --global ClangSharpPInvokeGenerator
137137
ClangSharpPInvokeGenerator @generate.rsp
138138
```
139139

@@ -202,9 +202,9 @@ Options:
202202
# Codegen Options
203203
204204
compatible-codegen Bindings should be generated with .NET Standard 2.0 compatibility. Setting this disables preview code generation.
205-
default-codegen Bindings should be generated for the previous LTS version of .NET/C#. This is currently .NET 6/C# 10.
206-
latest-codegen Bindings should be generated for the current LTS/STS version of .NET/C#. This is currently .NET 8/C# 12.
207-
preview-codegen Bindings should be generated for the preview version of .NET/C#. This is currently .NET 9/C# 13.
205+
default-codegen Bindings should be generated for the previous LTS version of .NET/C#. This is currently .NET 8/C# 12.
206+
latest-codegen Bindings should be generated for the current LTS/STS version of .NET/C#. This is currently .NET 10/C# 14.
207+
preview-codegen Bindings should be generated for the preview version of .NET/C#. This is currently .NET 10/C# 14.
208208
209209
# File Options
210210

sources/ClangSharp.PInvokeGenerator/Abstractions/FunctionOrDelegateDesc.cs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,21 @@ readonly get
6666
}
6767
}
6868

69+
public bool IsReadOnly
70+
{
71+
readonly get
72+
{
73+
return (Flags & FunctionOrDelegateFlags.IsReadOnly) != 0;
74+
}
75+
76+
set
77+
{
78+
Flags = value
79+
? Flags | FunctionOrDelegateFlags.IsReadOnly
80+
: Flags & ~FunctionOrDelegateFlags.IsReadOnly;
81+
}
82+
}
83+
6984
public bool HasFnPtrCodeGen
7085
{
7186
readonly get

sources/ClangSharp.PInvokeGenerator/CSharp/CSharpOutputBuilder.Visit.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,7 @@ public void WriteIid(string name, Guid value)
8989

9090
WriteIndented("ReadOnlySpan<byte> data = ");
9191

92-
if (_generator.Config.GenerateLatestCode)
92+
if (!_generator.Config.GenerateCompatibleCode)
9393
{
9494
WriteLine('[');
9595
}
@@ -124,7 +124,7 @@ public void WriteIid(string name, Guid value)
124124
WriteNewline();
125125
DecreaseIndentation();
126126

127-
if (_generator.Config.GenerateLatestCode)
127+
if (!_generator.Config.GenerateCompatibleCode)
128128
{
129129
WriteIndented(']');
130130
}

sources/ClangSharp.PInvokeGenerator/CSharp/CSharpOutputBuilder.VisitDecl.cs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -517,6 +517,11 @@ public void BeginFunctionOrDelegate(in FunctionOrDelegateDesc desc, ref bool isM
517517
Write("new ");
518518
}
519519

520+
if (desc.IsReadOnly)
521+
{
522+
Write("readonly ");
523+
}
524+
520525
if (desc.IsUnsafe)
521526
{
522527
if (!desc.IsCtxCxxRecord)
@@ -808,7 +813,7 @@ public void BeginStruct(in StructDesc desc)
808813
Write(".Interface");
809814
}
810815

811-
if ((desc.Uuid is not null) && _generator.Config.GenerateGuidMember && _generator.Config.GenerateLatestCode)
816+
if ((desc.Uuid is not null) && _generator.Config.GenerateGuidMember && !_generator.Config.GenerateCompatibleCode)
812817
{
813818
Write(desc.HasVtbl ? ", " : " : ");
814819
Write("INativeGuid");

sources/ClangSharp.PInvokeGenerator/FunctionOrDelegateFlags.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,4 +21,5 @@ internal enum FunctionOrDelegateFlags
2121
NeedsReturnFixup = 1 << 13,
2222
IsCxxConstructor = 1 << 14,
2323
IsManualImport = 1 << 15,
24+
IsReadOnly = 1 << 16,
2425
}

sources/ClangSharp.PInvokeGenerator/PInvokeGenerator.VisitDecl.cs

Lines changed: 45 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -607,6 +607,7 @@ private void VisitFunctionDecl(FunctionDecl functionDecl)
607607
IsCxx = cxxMethodDecl is not null,
608608
IsStatic = isDllImport || (cxxMethodDecl is null) || cxxMethodDecl.IsStatic,
609609
NeedsNewKeyword = NeedsNewKeyword(escapedName, functionDecl.Parameters),
610+
IsReadOnly = (cxxMethodDecl is not null) && cxxMethodDecl.IsConst,
610611
IsUnsafe = IsUnsafe(functionDecl),
611612
IsCtxCxxRecord = cxxRecordDecl is not null,
612613
IsCxxRecordCtxUnsafe = cxxRecordDecl is not null && IsUnsafe(cxxRecordDecl),
@@ -944,7 +945,7 @@ private void VisitIndirectFieldDecl(IndirectFieldDecl indirectFieldDecl)
944945
ParentName = GetRemappedCursorName(parent),
945946
Offset = null,
946947
NeedsNewKeyword = false,
947-
NeedsUnscopedRef = _config.GenerateLatestCode && !fieldDecl.IsBitField,
948+
NeedsUnscopedRef = !_config.GenerateCompatibleCode && !fieldDecl.IsBitField,
948949
Location = fieldDecl.Location,
949950
HasBody = true,
950951
WriteCustomAttrs = static context => {
@@ -1110,7 +1111,7 @@ private void VisitIndirectFieldDecl(IndirectFieldDecl indirectFieldDecl)
11101111
code.Write(arraySize);
11111112
code.Write(')');
11121113
}
1113-
else if (!_config.GenerateLatestCode || arraySize == 1)
1114+
else if (_config.GenerateCompatibleCode || arraySize == 1)
11141115
{
11151116
code.Write(".AsSpan(");
11161117

@@ -1657,9 +1658,23 @@ private void VisitRecordDecl(RecordDecl recordDecl)
16571658
_outputBuilder.BeginValue(in valueDesc);
16581659

16591660
var code = _outputBuilder.BeginCSharpCode();
1660-
code.Write("(Guid*)Unsafe.AsPointer(ref Unsafe.AsRef(in ");
1661+
1662+
code.Write("(Guid*)Unsafe.AsPointer(");
1663+
1664+
if (!_config.GenerateLatestCode)
1665+
{
1666+
code.Write("ref Unsafe.AsRef(");
1667+
}
1668+
1669+
code.Write("in ");
16611670
code.Write(usableUuidName);
1662-
code.Write("))");
1671+
code.Write(')');
1672+
1673+
if (!_config.GenerateLatestCode)
1674+
{
1675+
code.Write(')');
1676+
}
1677+
16631678
_outputBuilder.EndCSharpCode(code);
16641679

16651680
_outputBuilder.EndValue(in valueDesc);
@@ -2213,6 +2228,7 @@ void OutputVtblHelperMethod(CXXRecordDecl cxxRecordDecl, CXXMethodDecl cxxMethod
22132228
HasFnPtrCodeGen = !_config.ExcludeFnptrCodegen,
22142229
IsCtxCxxRecord = true,
22152230
IsCxxRecordCtxUnsafe = IsUnsafe(cxxRecordDecl),
2231+
IsReadOnly = cxxMethodDecl.IsConst,
22162232
IsUnsafe = true,
22172233
NeedsReturnFixup = needsReturnFixup,
22182234
ReturnType = returnTypeName,
@@ -2335,7 +2351,27 @@ void OutputVtblHelperMethod(CXXRecordDecl cxxRecordDecl, CXXMethodDecl cxxMethod
23352351
{
23362352
body.Write('(');
23372353
body.Write(escapedCXXRecordDeclName);
2338-
body.Write("*)Unsafe.AsPointer(ref this)");
2354+
body.Write("*)Unsafe.AsPointer(");
2355+
2356+
if (cxxMethodDecl.IsConst)
2357+
{
2358+
if (!_config.GenerateLatestCode)
2359+
{
2360+
body.Write("ref Unsafe.AsRef(");
2361+
}
2362+
2363+
body.Write("in this");
2364+
2365+
if (!_config.GenerateLatestCode)
2366+
{
2367+
body.Write(')');
2368+
}
2369+
}
2370+
else
2371+
{
2372+
body.Write("ref this");
2373+
}
2374+
body.Write(')');
23392375
}
23402376
body.EndMarker("param");
23412377

@@ -2974,7 +3010,7 @@ void VisitConstantOrIncompleteArrayFieldDecl(RecordDecl recordDecl, FieldDecl co
29743010
AddDiagnostic(DiagnosticLevel.Info, $"{escapedName} (constant array field) has a size of 0", constantOrIncompleteArray);
29753011
}
29763012

2977-
if (!_config.GenerateLatestCode || (totalSize <= 1) || isUnsafeElementType)
3013+
if (_config.GenerateCompatibleCode || (totalSize <= 1) || isUnsafeElementType)
29783014
{
29793015
totalSizeString = null;
29803016
}
@@ -3100,7 +3136,7 @@ void VisitConstantOrIncompleteArrayFieldDecl(RecordDecl recordDecl, FieldDecl co
31003136
}
31013137
else if (totalSizeString is null)
31023138
{
3103-
_outputBuilder.BeginIndexer(AccessSpecifier.Public, isUnsafe: false, needsUnscopedRef: _config.GenerateLatestCode);
3139+
_outputBuilder.BeginIndexer(AccessSpecifier.Public, isUnsafe: false, needsUnscopedRef: !_config.GenerateCompatibleCode);
31043140
_outputBuilder.WriteIndexer($"ref {arrayTypeName}");
31053141
_outputBuilder.BeginIndexerParameters();
31063142
var param = new ParameterDesc {
@@ -3146,7 +3182,7 @@ void VisitConstantOrIncompleteArrayFieldDecl(RecordDecl recordDecl, FieldDecl co
31463182
ReturnType = $"Span<{arrayTypeName}>",
31473183
Location = constantOrIncompleteArray.Location,
31483184
HasBody = true,
3149-
NeedsUnscopedRef = _config.GenerateLatestCode,
3185+
NeedsUnscopedRef = !_config.GenerateCompatibleCode,
31503186
};
31513187

31523188
var isUnsafe = false;
@@ -3463,7 +3499,7 @@ private void VisitVarDecl(VarDecl varDecl)
34633499

34643500
case CX_SLK_UTF32:
34653501
{
3466-
typeName = (_config.GenerateLatestCode && flags.HasFlag(ValueFlags.Constant)) ? "ReadOnlySpan<uint>" : "uint[]";
3502+
typeName = (!_config.GenerateCompatibleCode && flags.HasFlag(ValueFlags.Constant)) ? "ReadOnlySpan<uint>" : "uint[]";
34673503
break;
34683504
}
34693505

sources/ClangSharp.PInvokeGenerator/PInvokeGenerator.VisitStmt.cs

Lines changed: 71 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -247,7 +247,7 @@ private void VisitCallExpr(CallExpr callExpr)
247247
{
248248
var args = callExpr.Args;
249249

250-
if (Config.GenerateLatestCode)
250+
if (!Config.GenerateCompatibleCode)
251251
{
252252
outputBuilder.AddUsingDirective("System.Runtime.InteropServices");
253253
outputBuilder.Write("NativeMemory.Copy");
@@ -306,13 +306,13 @@ private void VisitCallExpr(CallExpr callExpr)
306306
break;
307307
}
308308

309-
if (Config.GenerateLatestCode)
309+
if (!Config.GenerateCompatibleCode)
310310
{
311311
args = [args[0], args[2], args[1]];
312312
}
313313
}
314314

315-
if (Config.GenerateLatestCode)
315+
if (!Config.GenerateCompatibleCode)
316316
{
317317
outputBuilder.AddUsingDirective("System.Runtime.InteropServices");
318318
outputBuilder.Write("NativeMemory.Fill");
@@ -409,7 +409,27 @@ void VisitArgs(CallExpr callExpr, IReadOnlyList<Expr>? args = null)
409409
outputBuilder.AddUsingDirective("System.Runtime.CompilerServices");
410410
outputBuilder.Write('(');
411411
outputBuilder.Write(referenceTypeName);
412-
outputBuilder.Write(")Unsafe.AsPointer(ref this)");
412+
outputBuilder.Write(")Unsafe.AsPointer(");
413+
414+
if (referenceType.IsLocalConstQualified)
415+
{
416+
if (!_config.GenerateLatestCode)
417+
{
418+
outputBuilder.Write("ref Unsafe.AsRef(");
419+
}
420+
421+
outputBuilder.Write("in this");
422+
423+
if (!_config.GenerateLatestCode)
424+
{
425+
outputBuilder.Write(')');
426+
}
427+
}
428+
else
429+
{
430+
outputBuilder.Write("ref this");
431+
}
432+
outputBuilder.Write(')');
413433

414434
needsComma = true;
415435
continue;
@@ -432,7 +452,27 @@ void VisitArgs(CallExpr callExpr, IReadOnlyList<Expr>? args = null)
432452
else if (IsStmtAsWritten<CXXThisExpr>(arg, out _) && (functionName == "memcpy"))
433453
{
434454
outputBuilder.AddUsingDirective("System.Runtime.CompilerServices");
435-
outputBuilder.Write("Unsafe.AsPointer(ref this)");
455+
outputBuilder.Write("Unsafe.AsPointer(");
456+
457+
if (functionProtoType.ParamTypes[i].IsLocalConstQualified)
458+
{
459+
if (!_config.GenerateLatestCode)
460+
{
461+
outputBuilder.Write("ref Unsafe.AsRef(");
462+
}
463+
464+
outputBuilder.Write("in this");
465+
466+
if (!_config.GenerateLatestCode)
467+
{
468+
outputBuilder.Write(')');
469+
}
470+
}
471+
else
472+
{
473+
outputBuilder.Write("ref this");
474+
}
475+
outputBuilder.Write(')');
436476

437477
needsComma = true;
438478
continue;
@@ -1756,7 +1796,7 @@ void HandleUnmanagedConstant(CSharpOutputBuilder outputBuilder, InitListExpr ini
17561796

17571797
outputBuilder.WriteIndented("ReadOnlySpan<byte> data = ");
17581798

1759-
if (_config.GenerateLatestCode)
1799+
if (!_config.GenerateCompatibleCode)
17601800
{
17611801
outputBuilder.WriteLine("[");
17621802
}
@@ -1772,7 +1812,7 @@ void HandleUnmanagedConstant(CSharpOutputBuilder outputBuilder, InitListExpr ini
17721812
outputBuilder.WriteNewline();
17731813
outputBuilder.DecreaseIndentation();
17741814

1775-
if (_config.GenerateLatestCode)
1815+
if (!_config.GenerateCompatibleCode)
17761816
{
17771817
outputBuilder.WriteIndented(']');
17781818
}
@@ -2190,7 +2230,27 @@ private void VisitReturnStmt(ReturnStmt returnStmt)
21902230
outputBuilder.AddUsingDirective("System.Runtime.CompilerServices");
21912231
outputBuilder.Write('(');
21922232
outputBuilder.Write(referenceTypeName);
2193-
outputBuilder.Write(")Unsafe.AsPointer(ref this)");
2233+
outputBuilder.Write(")Unsafe.AsPointer(");
2234+
2235+
if (referenceType.IsLocalConstQualified)
2236+
{
2237+
if (!_config.GenerateLatestCode)
2238+
{
2239+
outputBuilder.Write("ref Unsafe.AsRef(");
2240+
}
2241+
2242+
outputBuilder.Write("in this");
2243+
2244+
if (!_config.GenerateLatestCode)
2245+
{
2246+
outputBuilder.Write(')');
2247+
}
2248+
}
2249+
else
2250+
{
2251+
outputBuilder.Write("ref this");
2252+
}
2253+
outputBuilder.Write(')');
21942254

21952255
StopCSharpCode();
21962256
return;
@@ -2793,7 +2853,7 @@ private void VisitStringLiteral(StringLiteral stringLiteral)
27932853
case CX_SLK_Ordinary:
27942854
case CX_SLK_UTF8:
27952855
{
2796-
if (Config.GenerateLatestCode)
2856+
if (!Config.GenerateCompatibleCode)
27972857
{
27982858
outputBuilder.Write('"');
27992859
outputBuilder.Write(EscapeString(stringLiteral.String));
@@ -2851,7 +2911,7 @@ private void VisitStringLiteral(StringLiteral stringLiteral)
28512911

28522912
case CX_SLK_UTF32:
28532913
{
2854-
if (_config.GenerateLatestCode)
2914+
if (!_config.GenerateCompatibleCode)
28552915
{
28562916
outputBuilder.Write('[');
28572917
}
@@ -2872,7 +2932,7 @@ private void VisitStringLiteral(StringLiteral stringLiteral)
28722932

28732933
outputBuilder.Write("0x00000000");
28742934

2875-
if (_config.GenerateLatestCode)
2935+
if (!_config.GenerateCompatibleCode)
28762936
{
28772937
outputBuilder.Write(']');
28782938
}

sources/ClangSharp.PInvokeGenerator/PInvokeGenerator.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5588,7 +5588,7 @@ internal bool IsSupportedFixedSizedBufferType(string typeName)
55885588
case "ulong":
55895589
{
55905590
// We want to prefer InlineArray in modern code, as it is safer and supports more features
5591-
return !Config.GenerateLatestCode;
5591+
return Config.GenerateCompatibleCode;
55925592
}
55935593

55945594
default:

0 commit comments

Comments
 (0)