Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 41 additions & 0 deletions Delegate/AnalysisCompletionEventCallbackDelegate.cs
Original file line number Diff line number Diff line change
@@ -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);
}
}
}
52 changes: 50 additions & 2 deletions Handle/BNAnalysisCompletionEvent.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,17 @@ namespace BinaryNinja
{
public sealed class AnalysisCompletionEvent : AbstractSafeHandle<AnalysisCompletionEvent>
{
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)
Expand Down Expand Up @@ -79,6 +86,47 @@ internal static AnalysisCompletionEvent MustBorrowHandle(IntPtr handle)
return new AnalysisCompletionEvent(handle, false);
}

/// <summary>
/// Registers a callback that fires once when the next analysis pass on <paramref name="view"/>
/// completes, mirroring Python <c>BinaryView.add_analysis_completion_event</c>
/// (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.
/// </summary>
/// <param name="view">The view whose analysis completion triggers the callback.</param>
/// <param name="callback">The action invoked when analysis completes.</param>
/// <returns>The armed event, or <c>null</c> if the core refused to create it.</returns>
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<NativeDelegates.AnalysisCompletionEventCallbackDelegate>(
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;
}

/// <summary>
/// Releases the native BNAnalysisCompletionEvent handle when this instance is disposed or finalized.
/// </summary>
Expand Down
19 changes: 19 additions & 0 deletions Handle/BNBinaryView.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1755,6 +1755,25 @@ public void UpdateAnalysisAndWait()
NativeMethods.BNUpdateAnalysisAndWait(this.handle);
}

/// <summary>
/// Registers a callback that fires once when the next analysis pass on this view completes,
/// mirroring Python <c>BinaryView.add_analysis_completion_event</c> (binaryview.py:8068).
/// Useful with <see cref="UpdateAnalysis"/>, 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.
/// </summary>
/// <param name="callback">The action invoked when analysis completes.</param>
/// <returns>The armed event, or <c>null</c> if the core refused to create it.</returns>
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);
Expand Down
Loading