diff --git a/src/CoreGraphics/CGPDFOperatorTable.cs b/src/CoreGraphics/CGPDFOperatorTable.cs index d93fe5e471b6..a87461030a33 100644 --- a/src/CoreGraphics/CGPDFOperatorTable.cs +++ b/src/CoreGraphics/CGPDFOperatorTable.cs @@ -71,10 +71,25 @@ protected virtual void Dispose (bool disposing) public IntPtr Handle { get; private set; } + // We need this P/Invoke for legacy AOT scenarios (since we have public API taking a 'Action', and with this particular native function we can't wrap the delegate) + // Unfortunately CoreCLR doesn't support generic Action delegates in P/Invokes: https://github.com/dotnet/runtime/issues/32963 [DllImport (Constants.CoreGraphicsLibrary)] extern static void CGPDFOperatorTableSetCallback (/* CGPDFOperatorTableRef */ IntPtr table, /* const char */ string name, /* CGPDFOperatorCallback */ Action callback); +#if NET + // This signature requires C# 9 (so .NET only). + // The good part about this signature is that it enforces at compile time that 'callback' is callable from native code in a FullAOT scenario. + // The bad part is that it's unsafe code (and callers must be in unsafe mode as well). + [DllImport (Constants.CoreGraphicsLibrary)] + unsafe extern static void CGPDFOperatorTableSetCallback (/* CGPDFOperatorTableRef */ IntPtr table, /* const char */ string name, /* CGPDFOperatorCallback */ delegate* unmanaged callback); +#endif + #if MONOMAC + // This signature can work everywhere, but we can't enforce at compile time that 'callback' is a delegate to a static function (which is required for FullAOT scenarios), + // so limit it to non-FullAOT platforms (macOS) + [DllImport (Constants.CoreGraphicsLibrary)] + extern static void CGPDFOperatorTableSetCallback (/* CGPDFOperatorTableRef */ IntPtr table, /* const char */ string name, /* CGPDFOperatorCallback */ CGPDFOperatorCallback callback); + // this won't work with AOT since the callback must be decorated with [MonoPInvokeCallback] public void SetCallback (string name, Action callback) { @@ -82,9 +97,9 @@ public void SetCallback (string name, Action callback) throw new ArgumentNullException ("name"); if (callback == null) - CGPDFOperatorTableSetCallback (Handle, name, null); + CGPDFOperatorTableSetCallback (Handle, name, (CGPDFOperatorCallback) null); else - CGPDFOperatorTableSetCallback (Handle, name, new Action (delegate (IntPtr reserved, IntPtr gchandle) { + CGPDFOperatorTableSetCallback (Handle, name, new CGPDFOperatorCallback (delegate (IntPtr reserved, IntPtr gchandle) { // we could do 'new CGPDFScanner (reserved, true)' but we would not get `UserInfo` (managed state) back // we could GCHandle `userInfo` but that would (even more) diverge both code bases :( var scanner = GetScannerFromInfo (gchandle); @@ -94,7 +109,11 @@ public void SetCallback (string name, Action callback) [Advice ("Use the nicer SetCallback(string,Action) API when possible.")] #endif + // this API is ugly - but I do not see a better way with the AOT limitation +#if NET && !MONOMAC + [Obsolete ("Use the overload that takes a function pointer ('delegate*') instead.")] +#endif public void SetCallback (string name, Action callback) { if (name == null) @@ -103,6 +122,17 @@ public void SetCallback (string name, Action callback) CGPDFOperatorTableSetCallback (Handle, name, callback); } +#if NET + // this signature requires C# 9 and unsafe code + public unsafe void SetCallback (string name, delegate* unmanaged callback) + { + if (name == null) + throw new ArgumentNullException (nameof (name)); + + CGPDFOperatorTableSetCallback (Handle, name, callback); + } +#endif + static public CGPDFScanner GetScannerFromInfo (IntPtr gchandle) { return GCHandle.FromIntPtr (gchandle).Target as CGPDFScanner; diff --git a/tests/monotouch-test/CoreGraphics/PDFScannerTest.cs b/tests/monotouch-test/CoreGraphics/PDFScannerTest.cs index 0a8831ee0af4..264cc3091e31 100644 --- a/tests/monotouch-test/CoreGraphics/PDFScannerTest.cs +++ b/tests/monotouch-test/CoreGraphics/PDFScannerTest.cs @@ -8,6 +8,8 @@ // using System; +using System.Runtime.InteropServices; + using Foundation; using CoreGraphics; using ObjCRuntime; @@ -22,7 +24,11 @@ public class PDFScannerTest { int bt_count; int do_checks; +#if NET + [UnmanagedCallersOnly] +#else [MonoPInvokeCallback (typeof (Action))] +#endif static void BT (IntPtr reserved, IntPtr info) { // sadly the parameters are always identical and we can't know the operator name @@ -35,7 +41,11 @@ static void BT (IntPtr reserved, IntPtr info) (scanner.UserInfo as PDFScannerTest).bt_count++; } +#if NET + [UnmanagedCallersOnly] +#else [MonoPInvokeCallback (typeof (Action))] +#endif static void Do (IntPtr reserved, IntPtr info) { // sadly the parameters are always identical and we can't know the operator name @@ -97,6 +107,13 @@ public void Tamarin () table.SetCallback ("Do", delegate (CGPDFScanner scanner) { // ... drill down to the image }); +#elif NET + unsafe { + // BT == new paragraph + table.SetCallback ("BT", &BT); + // Do == the image is inside it + table.SetCallback ("Do", &Do); + } #else // BT == new paragraph table.SetCallback ("BT", BT);