[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:
Родитель
a35e470cbc
Коммит
a8bf64b053
|
@ -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__
|
Загрузка…
Ссылка в новой задаче