From 779f064d0445c581aac20c2dee1898a252cdfdcb Mon Sep 17 00:00:00 2001 From: Rolf Bjarne Kvinge Date: Thu, 11 Jan 2024 09:39:27 +0100 Subject: [PATCH] [src] Add a TransientCFString struct. (#19763) In order to make it easier to pass C-style strings to P/Invokes, we introduced a TransientString struct some time ago. This works quite well, so I implemented the same for CFStrings - a TransientCFString struct - and started using it in a few places. The idea would be to slowly start migrating our codebase to this new pattern. Instead of: var ptr = CFString.CreateNative ("somestring"); try { CallPInvoke (ptr); } finally { CFString.ReleaseNative (ptr); } we'll do: using var ptr = new TransientCFString ("somestring"); CallPInvoke (ptr); --- src/CoreMidi/MidiThruConnection.cs | 20 +++++----------- src/ObjCRuntime/TransientCFString.cs | 35 ++++++++++++++++++++++++++++ src/UIKit/UIApplication.cs | 12 ++++------ src/frameworks.sources | 1 + tests/cecil-tests/CecilExtensions.cs | 13 ++++++++--- 5 files changed, 56 insertions(+), 25 deletions(-) create mode 100644 src/ObjCRuntime/TransientCFString.cs diff --git a/src/CoreMidi/MidiThruConnection.cs b/src/CoreMidi/MidiThruConnection.cs index c9a5e5b8a9..6e54395086 100644 --- a/src/CoreMidi/MidiThruConnection.cs +++ b/src/CoreMidi/MidiThruConnection.cs @@ -73,13 +73,9 @@ namespace CoreMidi { MidiThruConnectionRef ret; using (var data = connectionParams.WriteStruct ()) { - var retStr = CFString.CreateNative (persistentOwnerID); - try { - unsafe { - error = MIDIThruConnectionCreate (retStr, data.Handle, &ret); - } - } finally { - CFString.ReleaseNative (retStr); + using var retStr = new TransientCFString (persistentOwnerID); + unsafe { + error = MIDIThruConnectionCreate (retStr, data.Handle, &ret); } } @@ -155,13 +151,9 @@ namespace CoreMidi { ObjCRuntime.ThrowHelper.ThrowArgumentNullException (nameof (persistentOwnerID)); IntPtr ret; - var persistentOwnerIDHandle = CFString.CreateNative (persistentOwnerID); - try { - unsafe { - error = MIDIThruConnectionFind (persistentOwnerIDHandle, &ret); - } - } finally { - CFString.ReleaseNative (persistentOwnerIDHandle); + using var persistentOwnerIDHandle = new TransientCFString (persistentOwnerID); + unsafe { + error = MIDIThruConnectionFind (persistentOwnerIDHandle, &ret); } using (var data = Runtime.GetNSObject (ret)) { if (data is null) diff --git a/src/ObjCRuntime/TransientCFString.cs b/src/ObjCRuntime/TransientCFString.cs new file mode 100644 index 0000000000..9460e671c7 --- /dev/null +++ b/src/ObjCRuntime/TransientCFString.cs @@ -0,0 +1,35 @@ +using System; +using System.Runtime.InteropServices; + +using CoreFoundation; + +#nullable enable + +namespace ObjCRuntime { + // a short-lived holder for a CFString/NSString-like string for native interop + // typical usage: + // using var cstring = new NativeCFString (str); + // SomePInvoke (cstring); + // + internal ref struct TransientCFString { +#if !COREBUILD + IntPtr ptr; + + public TransientCFString (string? str) + { + ptr = CFString.CreateNative (str); + } + + public void Dispose () + { + if (ptr != IntPtr.Zero) { + CFString.ReleaseNative (ptr); + ptr = IntPtr.Zero; + } + } + + public static implicit operator IntPtr (TransientCFString str) => str.ptr; + public static explicit operator string? (TransientCFString str) => CFString.FromHandle (str.ptr); +#endif + } +} diff --git a/src/UIKit/UIApplication.cs b/src/UIKit/UIApplication.cs index faf44fdb2d..0dcecb3da6 100644 --- a/src/UIKit/UIApplication.cs +++ b/src/UIKit/UIApplication.cs @@ -82,22 +82,18 @@ namespace UIKit { [EditorBrowsable (EditorBrowsableState.Never)] public static void Main (string []? args, string? principalClassName, string? delegateClassName) { - var p = CFString.CreateNative (principalClassName); - var d = CFString.CreateNative (delegateClassName); + using var p = new TransientCFString (principalClassName); + using var d = new TransientCFString (delegateClassName); Initialize (); UIApplicationMain (args?.Length ?? 0, args, p, d); - CFString.ReleaseNative (d); - CFString.ReleaseNative (p); } public static void Main (string []? args, Type? principalClass, Type? delegateClass) { - var p = principalClass is null ? NativeHandle.Zero : CFString.CreateNative (new Class (principalClass).Name); - var d = delegateClass is null ? NativeHandle.Zero : CFString.CreateNative (new Class (delegateClass).Name); + using var p = new TransientCFString (principalClass is null ? null : new Class (principalClass).Name); + using var d = new TransientCFString (delegateClass is null ? null : new Class (delegateClass).Name); Initialize (); UIApplicationMain (args?.Length ?? 0, args, p, d); - CFString.ReleaseNative (d); - CFString.ReleaseNative (p); } public static void Main (string []? args) diff --git a/src/frameworks.sources b/src/frameworks.sources index c09e5d8ee2..b38c545e0e 100644 --- a/src/frameworks.sources +++ b/src/frameworks.sources @@ -1932,6 +1932,7 @@ SHARED_CORE_SOURCES = \ ObjCRuntime/NativeAttribute.cs \ ObjCRuntime/NativeHandle.cs \ ObjCRuntime/NativeNameAttribute.cs \ + ObjCRuntime/TransientCFString.cs \ ObjCRuntime/TransientString.cs \ ObjCRuntime/NFloat.cs \ ObjCRuntime/ObsoleteConstants.cs \ diff --git a/tests/cecil-tests/CecilExtensions.cs b/tests/cecil-tests/CecilExtensions.cs index 5cd749bf47..085f622f8f 100644 --- a/tests/cecil-tests/CecilExtensions.cs +++ b/tests/cecil-tests/CecilExtensions.cs @@ -120,9 +120,16 @@ namespace Xamarin.Utils { if (provider?.HasCustomAttributes != true) return false; - foreach (var attrib in provider.CustomAttributes) - if (IsObsoleteAttribute (attrib)) - return true; + foreach (var attrib in provider.CustomAttributes) { + if (!IsObsoleteAttribute (attrib)) + continue; + + // The compiler will emit a fake Obsolete attribute for ref structs. Ignore those Obsolete attributes, because the type isn't really obsolete. + if (attrib.HasConstructorArguments && attrib.ConstructorArguments [0].Value is string obsoleteMessage && obsoleteMessage == "Types with embedded references are not supported in this version of your compiler.") + continue; + + return true; + } return false; }