//-----------------------------------------------------------------------
//
// Copyright (c) Microsoft Corporation.
//
//-----------------------------------------------------------------------
namespace Microsoft.Isam.Esent.Interop
{
using System;
using System.Diagnostics;
using System.Globalization;
using System.Reflection;
using System.Runtime.CompilerServices;
///
/// A multi-purpose callback function used by the database engine to inform
/// the application of an event involving online defragmentation and cursor
/// state notifications.
///
/// The session for which the callback is being made.
/// The database for which the callback is being made.
/// The cursor for which the callback is being made.
/// The operation for which the callback is being made.
/// First callback-specific argument.
/// Second callback-specific argument.
/// Callback context.
/// This parameter is not used.
/// An ESENT error code.
internal delegate JET_err NATIVE_CALLBACK(
IntPtr sesid,
uint dbid,
IntPtr tableid,
uint cbtyp,
IntPtr arg1,
IntPtr arg2,
IntPtr context,
IntPtr unused);
///
/// Wraps a NATIVE_CALLBACK callback around a JET_CALLBACK. This is
/// used to catch exceptions and provide argument conversion.
///
internal sealed class JetCallbackWrapper
{
///
/// API call tracing.
///
private static readonly TraceSwitch TraceSwitch = new TraceSwitch("ESENT JetCallbackWrapper", "Wrapper around unmanaged ESENT callback");
///
/// The wrapped status callback.
///
private readonly WeakReference wrappedCallback;
///
/// The native version of the callback. This will actually be a closure
/// because we are calling a non-static method. Keep track of it here
/// to make sure that it isn't garbage collected.
///
private readonly NATIVE_CALLBACK nativeCallback;
#if !MANAGEDESENT_ON_WSA
///
/// Initializes static members of the class.
///
static JetCallbackWrapper()
{
// We don't want a JIT failure when trying to execute the callback
// because that would throw an exception through ESENT, corrupting it.
// It is fine for the wrapped callback to fail because CallbackImpl
// will catch the exception and deal with it.
RuntimeHelpers.PrepareMethod(typeof(StatusCallbackWrapper).GetMethod(
"CallbackImpl",
BindingFlags.NonPublic | BindingFlags.Instance).MethodHandle);
}
#endif // !MANAGEDESENT_ON_WSA
///
/// Initializes a new instance of the JetCallbackWrapper class.
///
///
/// The managed callback to use.
///
public JetCallbackWrapper(JET_CALLBACK callback)
{
this.wrappedCallback = new WeakReference(callback);
this.nativeCallback = this.CallbackImpl;
Debug.Assert(this.wrappedCallback.IsAlive, "Callback isn't alive");
}
///
/// Gets a value indicating whether the wrapped callback has been garbage
/// collected.
///
public bool IsAlive
{
get
{
return this.wrappedCallback.IsAlive;
}
}
///
/// Gets a NATIVE_CALLBACK callback that wraps the managed callback.
///
public NATIVE_CALLBACK NativeCallback
{
get
{
return this.nativeCallback;
}
}
///
/// Determine if the callback is wrapping the specified JET_CALLBACK.
///
/// The callback.
/// True if this wrapper is wrapping the callback.
public bool IsWrapping(JET_CALLBACK callback)
{
return callback.Equals(this.wrappedCallback.Target);
}
///
/// Callback function for native code. We don't want to throw an exception through
/// unmanaged ESENT because that will corrupt ESENT's internal state. Instead we
/// catch all exceptions and return an error instead. We use a CER to make catching
/// the exceptions as reliable as possible.
///
/// The session for which the callback is being made.
/// The database for which the callback is being made.
/// The cursor for which the callback is being made.
/// The operation for which the callback is being made.
/// First callback-specific argument.
/// Second callback-specific argument.
/// Callback context.
/// This parameter is not used.
/// An ESENT error code.
private JET_err CallbackImpl(
IntPtr nativeSesid,
uint nativeDbid,
IntPtr nativeTableid,
uint nativeCbtyp,
IntPtr arg1,
IntPtr arg2,
IntPtr nativeContext,
IntPtr unused)
{
RuntimeHelpers.PrepareConstrainedRegions();
try
{
var sesid = new JET_SESID { Value = nativeSesid };
var dbid = new JET_DBID { Value = nativeDbid };
var tableid = new JET_TABLEID { Value = nativeTableid };
JET_cbtyp cbtyp = (JET_cbtyp)nativeCbtyp;
Debug.Assert(this.wrappedCallback.IsAlive, "Wrapped callback has been garbage collected");
// This will throw an exception if the wrapped callback has been collected. The exception
// will be handled below.
JET_CALLBACK callback = (JET_CALLBACK)this.wrappedCallback.Target;
return callback(sesid, dbid, tableid, cbtyp, null, null, nativeContext, IntPtr.Zero);
}
catch (Exception ex)
{
// Thread aborts aren't handled here. ESENT callbacks can execute on client threads or
// internal ESENT threads so it isn't clear what should be done on an abort.
Trace.WriteLineIf(
TraceSwitch.TraceWarning,
string.Format(CultureInfo.InvariantCulture, "Caught Exception {0}", ex));
return JET_err.CallbackFailed;
}
}
}
}