diff --git a/Delegate/AnalysisCompletionEventCallbackDelegate.cs b/Delegate/AnalysisCompletionEventCallbackDelegate.cs new file mode 100644 index 0000000..0465236 --- /dev/null +++ b/Delegate/AnalysisCompletionEventCallbackDelegate.cs @@ -0,0 +1,41 @@ +using System; + +namespace BinaryNinja +{ + internal static partial class NativeDelegates + { + // void (*callback)(void* ctxt) + // Invoked once by the core when an analysis pass completes. Mirrors the native callback + // registered by Python AnalysisCompletionEvent (binaryview.py:688). + public delegate void AnalysisCompletionEventCallbackDelegate(IntPtr context); + } + + internal static partial class UnsafeUtils + { + // Adapts a public Action into the native analysis-completion callback shape. The core + // passes only the opaque context pointer, which the binding does not need (the user Action + // captures whatever state it requires), so it is ignored. + internal sealed class AnalysisCompletionEventContext + { + private readonly Action m_callback; + + internal AnalysisCompletionEventContext(Action callback) + { + this.m_callback = callback; + } + + internal void OnNotify(IntPtr context) + { + this.m_callback(); + } + } + + internal static NativeDelegates.AnalysisCompletionEventCallbackDelegate WrapAnalysisCompletionEventCallback( + Action callback) + { + AnalysisCompletionEventContext context = new AnalysisCompletionEventContext(callback); + + return new NativeDelegates.AnalysisCompletionEventCallbackDelegate(context.OnNotify); + } + } +} diff --git a/Handle/BNAnalysisCompletionEvent.cs b/Handle/BNAnalysisCompletionEvent.cs index 8fa75db..7b04428 100644 --- a/Handle/BNAnalysisCompletionEvent.cs +++ b/Handle/BNAnalysisCompletionEvent.cs @@ -7,10 +7,17 @@ namespace BinaryNinja { public sealed class AnalysisCompletionEvent : AbstractSafeHandle { - internal AnalysisCompletionEvent(IntPtr handle , bool owner) + // Holds the NATIVE wrapper delegate (not the user Action): the wrapper owns the GC root for + // the user callback via its context, and its function pointer is what the core invokes when + // analysis completes. Rooting the wrapper for the event's lifetime keeps the callback + // reachable; the caller must in turn keep this AnalysisCompletionEvent alive (dispose or + // GC-collecting it cancels the event and frees the delegate). + private NativeDelegates.AnalysisCompletionEventCallbackDelegate? m_callback = null; + + internal AnalysisCompletionEvent(IntPtr handle , bool owner) : base(handle , owner) { - + } internal static AnalysisCompletionEvent? NewFromHandle(IntPtr handle) @@ -79,6 +86,47 @@ internal static AnalysisCompletionEvent MustBorrowHandle(IntPtr handle) return new AnalysisCompletionEvent(handle, false); } + /// + /// Registers a callback that fires once when the next analysis pass on + /// completes, mirroring Python BinaryView.add_analysis_completion_event + /// (binaryview.py:8068). The returned event must be kept alive by the caller (held in a field + /// or variable) for as long as the callback should remain armed; disposing or GC-collecting it + /// cancels the event. + /// + /// The view whose analysis completion triggers the callback. + /// The action invoked when analysis completes. + /// The armed event, or null if the core refused to create it. + internal static AnalysisCompletionEvent? Create(BinaryView view, Action callback) + { + // 1. Wrap the user Action into the native void(void*) callback shape and take a function + // pointer from the wrapper. + NativeDelegates.AnalysisCompletionEventCallbackDelegate wrapper = + UnsafeUtils.WrapAnalysisCompletionEventCallback(callback); + + IntPtr callbackPtr = + Marshal.GetFunctionPointerForDelegate( + wrapper); + + // 2. Register with the core. The context pointer is unused (the wrapper captures the + // callback), so pass null. + IntPtr handle = NativeMethods.BNAddAnalysisCompletionEvent( + view.DangerousGetHandle(), + IntPtr.Zero, + callbackPtr + ); + + // 3. Take ownership of the returned handle and root the wrapper on the instance so the + // function pointer stays valid for the event's lifetime (see m_callback). + AnalysisCompletionEvent? analysisCompletionEvent = AnalysisCompletionEvent.TakeHandle(handle); + + if (null != analysisCompletionEvent) + { + analysisCompletionEvent.m_callback = wrapper; + } + + return analysisCompletionEvent; + } + /// /// Releases the native BNAnalysisCompletionEvent handle when this instance is disposed or finalized. /// diff --git a/Handle/BNBinaryView.cs b/Handle/BNBinaryView.cs index 6476982..feb9389 100644 --- a/Handle/BNBinaryView.cs +++ b/Handle/BNBinaryView.cs @@ -1755,6 +1755,25 @@ public void UpdateAnalysisAndWait() NativeMethods.BNUpdateAnalysisAndWait(this.handle); } + /// + /// Registers a callback that fires once when the next analysis pass on this view completes, + /// mirroring Python BinaryView.add_analysis_completion_event (binaryview.py:8068). + /// Useful with , which returns before analysis finishes. The + /// returned event must be kept alive by the caller for as long as the callback should remain + /// armed; disposing or GC-collecting it cancels the event. + /// + /// The action invoked when analysis completes. + /// The armed event, or null if the core refused to create it. + public AnalysisCompletionEvent? AddAnalysisCompletionEvent(Action callback) + { + if (null == callback) + { + throw new ArgumentNullException(nameof(callback)); + } + + return AnalysisCompletionEvent.Create(this, callback); + } + public void AbortAnalysis() { NativeMethods.BNAbortAnalysis(this.handle);