Hi there,
My RhinoCommon plugin has been suffering from very intermittent crashes caused by an AccessViolationException getting thrown when refreshing a certain panel that contains a ViewportControl. I’ve finally managed to reproduce it fairly reliably in a very minimal example, and it seems to be a bug with the ViewportControl.
Here’s part of the stacktrace when this happened with a debugger attached:
rhcommon_c.dll!00007ffa674bb1ac()
[Managed to Native Transition]
RhinoWindows.dll!RhinoWindows.Forms.Controls.ViewportControl.OnPaint(System.Windows.Forms.PaintEventArgs e)
System.Windows.Forms.dll!System.Windows.Forms.Control.PaintWithErrorHandling(System.Windows.Forms.PaintEventArgs e, short layer)
System.Windows.Forms.dll!System.Windows.Forms.Control.WmPaint(ref System.Windows.Forms.Message m)
System.Windows.Forms.dll!System.Windows.Forms.Control.WndProc(ref System.Windows.Forms.Message m)
System.Windows.Forms.dll!System.Windows.Forms.NativeWindow.Callback(Windows.Win32.Foundation.HWND hWnd, Windows.Win32.MessageId msg, Windows.Win32.Foundation.WPARAM wparam, Windows.Win32.Foundation.LPARAM lparam)
[Native to Managed Transition]
user32.dll!00007ffbf97bc396()
user32.dll!00007ffbf97bbe5c()
Microsoft.VisualStudio.Debugger.Runtime.Impl.dll!00007ffbaf5b1bb8()
opengl32.dll!00007ffb221ebe23()
user32.dll!00007ffbf97bc396()
user32.dll!00007ffbf97bbc1c()
user32.dll!00007ffbf97f22a3()
ntdll.dll!00007ffbf9de5cc4()
win32u.dll!00007ffbf702cb04()
[Managed to Native Transition]
System.Windows.Forms.Primitives.dll!Windows.Win32.PInvoke.UpdateWindow<System.__Canon>(System.__Canon hWnd)
System.Windows.Forms.dll!System.Windows.Forms.Control.Refresh()
RhinoWindows.dll!RhinoWindows.Forms.Controls.ViewportControlHandler.Refresh()
Rhino.UI.dll!Rhino.UI.Controls.ViewportControl.Refresh()
The basic description is that I want a panel that has a viewport inside it. Within my plugin I can mutate values that change what will be drawn inside the panel’s viewport. To have the viewport update with the changed information, I call Rhino.UI.Controls.ViewportControl.Refresh(). This call can randomly cause an uncatchable AccessViolationException to get thrown (very randomly and can be very infrequently).
My minimal example for reproducing below is made up of two things:
- A RhinoCommon plugin csproj file and one source file
- It registers one panel that contains a
ViewportControl - It contains one command that starts a thread and repeatedly (with pauses) calls
Refresh()on the viewport control
- It registers one panel that contains a
- A small batch script called
provoke-access-violation.batthat, in a loop, opens Rhino, runs the command described above and outputs if the Rhino exits in error.
Both are in the zip below:
ViewportPanelPlugin.zip (26.6 KB)
To reproduce the issue:
- Build the ViewportPanelPlugin.csproj
- Register the plugin’s output rhp file with Rhino (drag and drop into an open Rhino instance)
- Close Rhino
- Run the batch script
- This will open and close Rhino 100 times, running the
RefreshCommandeach time, and printing if Rhino exited early in any of the 100 iterations.
- This will open and close Rhino 100 times, running the
Here’s a recording of that script running, and finding it crashing on the 49’th iteration:
The Minimal Viewport Panel Plugin
using Eto.Forms;
using Rhino;
using Rhino.Commands;
using Rhino.Input;
using Rhino.Input.Custom;
using Rhino.PlugIns;
using Rhino.UI;
using Rhino.UI.Controls;
using System.Runtime.InteropServices;
using System.Threading.Tasks;
using Command = Rhino.Commands.Command;
namespace ViewportPanelPlugin
{
[Guid("5D290748-D453-456E-99F7-49DC558E8C0C")]
public class MyViewportPanel : Panel
{
public ViewportControl ViewportControl { get; }
public MyViewportPanel()
{
ViewportControl viewportControl = new();
ViewportControl = viewportControl;
Content = viewportControl;
}
}
public class ViewportPanelCommand : Command
{
public ViewportPanelCommand()
{
// Rhino only creates one instance of each command class defined in a
// plug-in, so it is safe to store a refence in a static property.
Instance = this;
}
///<summary>The only instance of this command.</summary>
public static ViewportPanelCommand Instance { get; private set; }
///<returns>The command name as it appears on the Rhino command line.</returns>
public override string EnglishName => "RefreshCommand";
protected override Result RunCommand(RhinoDoc doc, RunMode mode)
{
var count = new OptionInteger(1, 1, 10000);
var gOpt = new GetOption();
gOpt.AcceptEnterWhenDone(true);
gOpt.AddOptionInteger("Count", ref count);
gOpt.AcceptNothing(true);
while (true)
{
var result = gOpt.Get();
if (gOpt.CommandResult() == Result.Cancel)
{
RhinoApp.WriteLine("Cancelled");
return Result.Cancel;
}
if (result == GetResult.Option)
{
continue;
}
break;
}
Rhino.UI.Panels.OpenPanel(typeof(MyViewportPanel).GUID);
Task.Run(() =>
{
int iterations = count.CurrentValue;
for (int i = 1; i <= iterations; i++)
{
var panel = Rhino.UI.Panels.GetPanel<MyViewportPanel>();
RhinoApp.InvokeOnUiThread(() =>
{
panel.ViewportControl.Refresh();
});
System.Threading.Thread.Sleep(100);
RhinoApp.WriteLine($"{i}/{iterations}");
}
RhinoApp.WriteLine($"Completed {iterations} iterations");
RhinoDoc.ActiveDoc.Modified = false;
RhinoApp.Exit(false);
});
return Result.Success;
}
}
///<summary>
/// <para>Every RhinoCommon .rhp assembly must have one and only one PlugIn-derived
/// class. DO NOT create instances of this class yourself. It is the
/// responsibility of Rhino to create an instance of this class.</para>
/// <para>To complete plug-in information, please also see all PlugInDescription
/// attributes in AssemblyInfo.cs (you might need to click "Project" ->
/// "Show All Files" to see it in the "Solution Explorer" window).</para>
///</summary>
public class ViewportPanelPlugin : Rhino.PlugIns.PlugIn
{
public ViewportPanelPlugin()
{
Instance = this;
}
///<summary>Gets the only instance of the ViewportPanelPlugin plug-in.</summary>
public static ViewportPanelPlugin Instance { get; private set; }
// You can override methods here to change the plug-in behavior on
// loading and shut down, add options pages to the Rhino _Option command
// and maintain plug-in wide options in a document.
protected override LoadReturnCode OnLoad(ref string errorMessage)
{
Panels.RegisterPanel(Instance,
typeof(MyViewportPanel),
"Refreshing this can cause an AccessViolationException",
null,
PanelType.PerDoc
);
return LoadReturnCode.Success;
}
}
}
The Batch Script to provoke it through many iterations
@echo off
set Rhino="C:/Program Files/Rhino 8/System/Rhino.exe"
set script="-RefreshCommand Count=50 _Enter"
set RhinoScript=%Rhino% /nosplash /runscript=%script%
set total=100
SETLOCAL ENABLEDELAYEDEXPANSION
for /l %%x in (1, 1, %total%) do (
echo Running iteration %%x
%RhinoScript% || (
echo Failed: 'AccessViolationException' thrown
set /a err_count=err_count+1
)
)
echo Errored %err_count%\%total% times
Example output of the script
Here’s a particularly bad run of the script, where it crashed 4/20 times.




