diff --git a/src/Generator/ClassOptions.cs b/src/Generator/ClassOptions.cs new file mode 100644 index 00000000..6b835c2e --- /dev/null +++ b/src/Generator/ClassOptions.cs @@ -0,0 +1,7 @@ +namespace CppSharp +{ + public class ClassGenerationOptions + { + public bool GenerateNativeToManaged { get; set; } + } +} \ No newline at end of file diff --git a/src/Generator/Generators/CSharp/CSharpSources.cs b/src/Generator/Generators/CSharp/CSharpSources.cs index 03b97157..dfbde177 100644 --- a/src/Generator/Generators/CSharp/CSharpSources.cs +++ b/src/Generator/Generators/CSharp/CSharpSources.cs @@ -438,7 +438,9 @@ namespace CppSharp.Generators.CSharp var valueType = Options.GenerateFinalizerFor(@class) ? $"global::System.WeakReference<{printedClass}>" : $"{printedClass}"; var dict = $@"global::System.Collections.Concurrent.ConcurrentDictionary"; - WriteLine("internal static readonly {0} NativeToManagedMap = new {0}();", dict); + var generateNativeToManaged = Options.GenerateNativeToManagedFor(@class); + if (generateNativeToManaged) + WriteLine("internal static readonly {0} NativeToManagedMap = new {0}();", dict); PopBlock(NewLineKind.BeforeNextBlock); // Create a method to record/recover a mapping to make it easier to call from template instantiation @@ -446,32 +448,35 @@ namespace CppSharp.Generators.CSharp // will never get invoked: unless the managed object is Disposed, there will always be a reference // in the dictionary. PushBlock(BlockKind.Method); - if (Options.GenerateFinalizerFor(@class)) - { - WriteLines($@" -internal static void {Helpers.RecordNativeToManagedMappingIdentifier}(IntPtr native, {printedClass} managed) -{{ - NativeToManagedMap[native] = new global::System.WeakReference<{printedClass}>(managed); -}} - -internal static bool {Helpers.TryGetNativeToManagedMappingIdentifier}(IntPtr native, out {printedClass} managed) -{{ - managed = default; - return NativeToManagedMap.TryGetValue(native, out var wr) && wr.TryGetTarget(out managed); -}}"); - } - else - { - WriteLines($@" -internal static void {Helpers.RecordNativeToManagedMappingIdentifier}(IntPtr native, {printedClass} managed) -{{ - NativeToManagedMap[native] = managed; -}} - -internal static bool {Helpers.TryGetNativeToManagedMappingIdentifier}(IntPtr native, out {printedClass} managed) -{{ - return NativeToManagedMap.TryGetValue(native, out managed); -}}"); + if (generateNativeToManaged) + { + if (Options.GenerateFinalizerFor(@class)) + { + WriteLines($@" + internal static void {Helpers.RecordNativeToManagedMappingIdentifier}(IntPtr native, {printedClass} managed) + {{ + NativeToManagedMap[native] = new global::System.WeakReference<{printedClass}>(managed); + }} + + internal static bool {Helpers.TryGetNativeToManagedMappingIdentifier}(IntPtr native, out {printedClass} managed) + {{ + managed = default; + return NativeToManagedMap.TryGetValue(native, out var wr) && wr.TryGetTarget(out managed); + }}"); + } + else + { + WriteLines($@" + internal static void {Helpers.RecordNativeToManagedMappingIdentifier}(IntPtr native, {printedClass} managed) + {{ + NativeToManagedMap[native] = managed; + }} + + internal static bool {Helpers.TryGetNativeToManagedMappingIdentifier}(IntPtr native, out {printedClass} managed) + {{ + return NativeToManagedMap.TryGetValue(native, out managed); + }}"); + } } } PopBlock(NewLineKind.BeforeNextBlock); @@ -2270,7 +2275,8 @@ internal static bool {Helpers.TryGetNativeToManagedMappingIdentifier}(IntPtr nat WriteLineIndent("return;"); // The local var must be of the exact type in the object map because of TryRemove - WriteLine("NativeToManagedMap.TryRemove({0}, out _);", Helpers.InstanceIdentifier); + if (Options.GenerateNativeToManagedFor(@class)) + WriteLine("NativeToManagedMap.TryRemove({0}, out _);", Helpers.InstanceIdentifier); var realClass = @class.IsTemplate ? @class.Specializations[0] : @class; var classInternal = TypePrinter.PrintNative(realClass); if (@class.IsDynamic && GetUniqueVTableMethodEntries(realClass).Count != 0) @@ -2402,7 +2408,10 @@ internal static bool {Helpers.TryGetNativeToManagedMappingIdentifier}(IntPtr nat { var @new = @class.HasBase && @class.HasRefBase(); - WriteLines($@" + bool generateNativeToManaged = Options.GenerateNativeToManagedFor(@class); + if (generateNativeToManaged) + { + WriteLines($@" internal static{(@new ? " new" : string.Empty)} {printedClass} __GetOrCreateInstance({TypePrinter.IntPtrType} native, bool saveInstance = false, bool skipVTables = false) {{ if (native == {TypePrinter.IntPtrType}.Zero) @@ -2414,7 +2423,8 @@ internal static{(@new ? " new" : string.Empty)} {printedClass} __GetOrCreateInst {Helpers.RecordNativeToManagedMappingIdentifier}(native, result); return result; }}"); - NewLine(); + NewLine(); + } if (HasVirtualTables(@class)) { @@ -2422,9 +2432,16 @@ internal static{(@new ? " new" : string.Empty)} {printedClass} __GetOrCreateInst WriteLines($@" internal static{(@new ? " new" : string.Empty)} {printedClass} __GetInstance({TypePrinter.IntPtrType} native) -{{ +{{"); + + if (generateNativeToManaged) + { +WriteLines($@" if (!{Helpers.TryGetNativeToManagedMappingIdentifier}(native, out var managed)) - throw new global::System.Exception(""No managed instance was found""); + throw new global::System.Exception(""No managed instance was found"");"); +} + +WriteLines($@" var result = ({printedClass})managed; if (result.{Helpers.OwnsNativeInstanceIdentifier}) result.SetupVTables(); @@ -2540,7 +2557,8 @@ internal static{(@new ? " new" : string.Empty)} {printedClass} __GetInstance({Ty if (@class.IsRefType) { WriteLine($"{Helpers.OwnsNativeInstanceIdentifier} = true;"); - WriteLine($"{Helpers.RecordNativeToManagedMappingIdentifier}({Helpers.InstanceIdentifier}, this);"); + if (Options.GenerateNativeToManagedFor(@class)) + WriteLine($"{Helpers.RecordNativeToManagedMappingIdentifier}({Helpers.InstanceIdentifier}, this);"); } else { @@ -2985,7 +3003,9 @@ internal static{(@new ? " new" : string.Empty)} {printedClass} __GetInstance({Ty @class.IsAbstractImpl ? @class.BaseClass : @class); WriteLine($"{Helpers.InstanceIdentifier} = Marshal.AllocHGlobal(sizeof({@internal}));"); WriteLine($"{Helpers.OwnsNativeInstanceIdentifier} = true;"); - WriteLine($"{Helpers.RecordNativeToManagedMappingIdentifier}({Helpers.InstanceIdentifier}, this);"); + + if (Options.GenerateNativeToManagedFor(@class)) + WriteLine($"{Helpers.RecordNativeToManagedMappingIdentifier}({Helpers.InstanceIdentifier}, this);"); if (method.IsCopyConstructor) { diff --git a/src/Generator/Options.cs b/src/Generator/Options.cs index b7370ed4..0f0bc257 100644 --- a/src/Generator/Options.cs +++ b/src/Generator/Options.cs @@ -136,6 +136,12 @@ namespace CppSharp /// public Func GenerateFinalizersFilter = (@class) => true; + /// + /// A callback that allows the user to provide generator options per-class. + /// + public Func GetClassGenerationOptions = null; + internal bool GenerateNativeToManagedFor(Class @class) => GetClassGenerationOptions?.Invoke(@class)?.GenerateNativeToManaged ?? true; + /// /// An internal convenience method that combines the effect of and . diff --git a/tests/CSharp/CSharp.Gen.cs b/tests/CSharp/CSharp.Gen.cs index f6fe8dd0..3c76f9f4 100644 --- a/tests/CSharp/CSharp.Gen.cs +++ b/tests/CSharp/CSharp.Gen.cs @@ -33,6 +33,12 @@ namespace CppSharp.Tests driver.Options.MarshalCharAsManagedChar = true; driver.Options.GenerateDefaultValuesForArguments = true; driver.Options.GenerateClassTemplates = true; + + var disableNativeToManaged = new ClassGenerationOptions { GenerateNativeToManaged = false }; + driver.Options.GetClassGenerationOptions = e => + { + return e.Name == "ClassWithoutNativeToManaged" ? disableNativeToManaged : null; + }; } public override void Preprocess(Driver driver, ASTContext ctx) diff --git a/tests/CSharp/CSharp.Tests.cs b/tests/CSharp/CSharp.Tests.cs index 41394621..66e469a9 100644 --- a/tests/CSharp/CSharp.Tests.cs +++ b/tests/CSharp/CSharp.Tests.cs @@ -1893,4 +1893,11 @@ public unsafe class CSharpTests Assert.That(CSharp.CSharp.TestFunctionToInstanceMethodConstStruct(new FTIStruct(), new FTIStruct() { A = 6 }), Is.EqualTo(6)); Assert.That(CSharp.CSharp.TestFunctionToInstanceMethodConstRefStruct(new FTIStruct(), new FTIStruct() { A = 6 }), Is.EqualTo(6)); } + + [TestCase(typeof(FTIStruct), ExpectedResult = true)] + [TestCase(typeof(ClassWithoutNativeToManaged), ExpectedResult = false)] + public bool TestClassGenerateNativeToManaged(Type type) + { + return type.GetMethods(BindingFlags.NonPublic | BindingFlags.Static).Any(x => x.Name.Contains("NativeToManaged")); + } } diff --git a/tests/CSharp/CSharp.h b/tests/CSharp/CSharp.h index 2ccd8482..4aee98d5 100644 --- a/tests/CSharp/CSharp.h +++ b/tests/CSharp/CSharp.h @@ -1534,3 +1534,5 @@ DLL_API int TestFunctionToInstanceMethodStruct(FTIStruct* bb, FTIStruct defaultV DLL_API int TestFunctionToInstanceMethodRefStruct(FTIStruct* bb, FTIStruct& defaultValue); DLL_API int TestFunctionToInstanceMethodConstStruct(FTIStruct* bb, const FTIStruct defaultValue); DLL_API int TestFunctionToInstanceMethodConstRefStruct(FTIStruct* bb, const FTIStruct& defaultValue); + +class ClassWithoutNativeToManaged { }; \ No newline at end of file