[CoreServices] Fix FSEventStream to properly pass the managed context to the FSEventStreamCreate method. Fixes #13325. (#13329)

The FSEventStreamCreate method takes a pointer to a structure with context information,
which contains a user-defined pointer value in addition to a few callbacks. Previously
we were passing the GCHandle as a pointer to this structure, which is obviously quite
wrong (as evidenced by a native crash when calling FSEventStreamCreate).

Changes:

* Modify the code to provide the expected context structure instead with the GCHandle
  as a field in that structure.
* Add a release callback to the context structure to release the GCHandle.
    * This avoids the need for storing the GCHandle as a field in the FSEventStream instance.
    * It also avoids also the need for overriding Dispose to release said GCHandle.
* Modify the callback code to use the [UnmanagedCallersOnly] attribute for .NET
  (ref: #10470).

This was a regression introduced in 8c99bdc9ad.

Fixes https://github.com/xamarin/xamarin-macios/issues/13325.
This commit is contained in:
Rolf Bjarne Kvinge 2021-11-10 17:13:28 +01:00 коммит произвёл GitHub
Родитель a35e470cbc
Коммит a8bf64b053
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
2 изменённых файлов: 88 добавлений и 16 удалений

Просмотреть файл

@ -133,6 +133,18 @@ namespace CoreServices
}
}
struct FSEventStreamContext {
nint version; /* CFIndex: only valid value is zero */
internal IntPtr Info; /* void * __nullable */
IntPtr Retain; /* CFAllocatorRetainCallBack __nullable */
#if NET
internal unsafe delegate* unmanaged<IntPtr, void> Release; /* CFAllocatorReleaseCallBack __nullable */
#else
internal FSEventStream.ReleaseContextCallback Release; /* CFAllocatorReleaseCallBack __nullable */
#endif
IntPtr CopyDescription; /* CFAllocatorCopyDescriptionCallBack __nullable */
}
public delegate void FSEventStreamEventsHandler (object sender, FSEventStreamEventsArgs args);
public sealed class FSEventStreamEventsArgs : EventArgs
@ -147,8 +159,6 @@ namespace CoreServices
public class FSEventStream : NativeObject
{
GCHandle gch;
[DllImport (Constants.CoreServicesLibrary)]
static extern void FSEventStreamRetain (IntPtr handle);
@ -165,19 +175,17 @@ namespace CoreServices
FSEventStreamRelease (GetCheckedHandle ());
}
protected override void Dispose (bool disposing)
{
if (gch.IsAllocated)
gch.Free ();
base.Dispose (disposing);
}
delegate void FSEventStreamCallback (IntPtr handle, IntPtr userData, nint numEvents,
IntPtr eventPaths, IntPtr eventFlags, IntPtr eventIds);
[DllImport (Constants.CoreServicesLibrary)]
static extern IntPtr FSEventStreamCreate (IntPtr allocator,
FSEventStreamCallback callback, IntPtr context, IntPtr pathsToWatch,
unsafe static extern IntPtr FSEventStreamCreate (IntPtr allocator,
#if NET
delegate* unmanaged<IntPtr, IntPtr, nint, IntPtr, IntPtr, IntPtr, void> callback,
#else
FSEventStreamCallback callback,
#endif
ref FSEventStreamContext context, IntPtr pathsToWatch,
ulong sinceWhen, double latency, FSEventStreamCreateFlags flags);
public FSEventStream (CFAllocator? allocator, NSArray pathsToWatch,
@ -186,12 +194,30 @@ namespace CoreServices
if (pathsToWatch is null)
throw new ArgumentNullException (nameof (pathsToWatch));
gch = GCHandle.Alloc (this);
var gch = GCHandle.Alloc (this);
var handle = FSEventStreamCreate (
allocator.GetHandle (),
eventsCallback, GCHandle.ToIntPtr (gch), pathsToWatch.Handle,
sinceWhenId, latency.TotalSeconds, flags | (FSEventStreamCreateFlags)0x1 /* UseCFTypes */);
var context = default (FSEventStreamContext);
context.Info = GCHandle.ToIntPtr (gch);
#if NET
unsafe {
context.Release = &FreeGCHandle;
}
#else
context.Release = releaseContextCallback;
#endif
IntPtr handle;
unsafe {
handle = FSEventStreamCreate (
allocator.GetHandle (),
#if NET
&EventsCallback,
#else
eventsCallback,
#endif
ref context, pathsToWatch.Handle,
sinceWhenId, latency.TotalSeconds, flags | (FSEventStreamCreateFlags)0x1 /* UseCFTypes */);
}
InitializeHandle (handle);
}
@ -201,8 +227,24 @@ namespace CoreServices
{
}
#if !NET
static readonly FSEventStreamCallback eventsCallback = EventsCallback;
static readonly ReleaseContextCallback releaseContextCallback = FreeGCHandle;
internal delegate void ReleaseContextCallback (IntPtr info);
#endif
#if NET
[UnmanagedCallersOnly]
#endif
static void FreeGCHandle (IntPtr gchandle)
{
GCHandle.FromIntPtr (gchandle).Free ();
}
#if NET
[UnmanagedCallersOnly]
#endif
static void EventsCallback (IntPtr handle, IntPtr userData, nint numEvents,
IntPtr eventPaths, IntPtr eventFlags, IntPtr eventIds)
{

Просмотреть файл

@ -0,0 +1,30 @@
//
// Unit tests for FSEventStream
//
#if __MACOS__
using System;
using System.IO;
using CoreFoundation;
using CoreServices;
using Foundation;
using NUnit.Framework;
namespace MonoTouchFixtures.CoreServices {
[TestFixture]
[Preserve (AllMembers = true)]
public class FSEventStreamTest {
[Test]
public void Create ()
{
using var eventStream = new FSEventStream (new [] { Path.Combine (Environment.GetEnvironmentVariable ("HOME"), "Desktop") }, TimeSpan.FromSeconds (5), FSEventStreamCreateFlags.FileEvents);
}
}
}
#endif // __MACOS__