GraphViz C# 互操作偶尔会导致 AccessViolationException
使用 David Brown 在 ImplicitOperator 上的可下载示例 I'我们将 DOT 文件的常用工作 GraphViz 渲染器组合到内存中的图像中。
不幸的是,我的版本在 IIS 7 ASP.NET Web 应用程序中的执行中估计有八分之一失败。我知道 DOT 文件数据是一致的,因为我已将失败的实例与工作实例并且它们是相同的。
由于 David 的网站似乎表明该博客的未来不确定,因此我将在此处重印互操作部分。希望他不介意。失败发生在示例的末尾,在 RenderImage 内的第三个语句集处。我已经注意到失败的行 // TODO: .... 失败总是发生在那里(如果它发生的话)。通过这一行,g 和 gvc 指针不为零,并且布局字符串已正确填充。
我真的不希望有人在运行时对此进行调试。相反,我希望对互操作代码进行一些静态分析可以揭示问题。我想不出这里有任何可用的高级封送技术 - 两个 IntPtr 和一个字符串不需要太多帮助,对吧?
谢谢!
旁注:我看过 MSAGL 的试用版,但没有留下深刻的印象 - 对于 Microsoft 的 99 美元,我希望有更多的节点布局功能和/或解释我所缺少的内容的文档。也许我从 QuickGraph 到 AGL 的快速移植不公平地影响了我的经验,因为方法上存在一些根本差异(例如,以边缘为中心与以节点为中心)。
public static class Graphviz
{
public const string LIB_GVC = "gvc.dll";
public const string LIB_GRAPH = "graph.dll";
public const int SUCCESS = 0;
/// <summary>
/// Creates a new Graphviz context.
/// </summary>
[DllImport(LIB_GVC)]
public static extern IntPtr gvContext();
/// <summary>
/// Releases a context's resources.
/// </summary>
[DllImport(LIB_GVC)]
public static extern int gvFreeContext(IntPtr gvc);
/// <summary>
/// Reads a graph from a string.
/// </summary>
[DllImport(LIB_GRAPH)]
public static extern IntPtr agmemread(string data);
/// <summary>
/// Releases the resources used by a graph.
/// </summary>
[DllImport(LIB_GRAPH)]
public static extern void agclose(IntPtr g);
/// <summary>
/// Applies a layout to a graph using the given engine.
/// </summary>
[DllImport(LIB_GVC)]
public static extern int gvLayout(IntPtr gvc, IntPtr g, string engine);
/// <summary>
/// Releases the resources used by a layout.
/// </summary>
[DllImport(LIB_GVC)]
public static extern int gvFreeLayout(IntPtr gvc, IntPtr g);
/// <summary>
/// Renders a graph to a file.
/// </summary>
[DllImport(LIB_GVC)]
public static extern int gvRenderFilename(IntPtr gvc, IntPtr g,
string format, string fileName);
/// <summary>
/// Renders a graph in memory.
/// </summary>
[DllImport(LIB_GVC)]
public static extern int gvRenderData(IntPtr gvc, IntPtr g,
string format, out IntPtr result, out int length);
public static Image RenderImage(string source, string layout, string format)
{
// Create a Graphviz context
IntPtr gvc = gvContext();
if (gvc == IntPtr.Zero)
throw new Exception("Failed to create Graphviz context.");
// Load the DOT data into a graph
IntPtr g = agmemread(source);
if (g == IntPtr.Zero)
throw new Exception("Failed to create graph from source. Check for syntax errors.");
// Apply a layout
if (gvLayout(gvc, g, layout) != SUCCESS) // TODO: Fix AccessViolationException here
throw new Exception("Layout failed.");
IntPtr result;
int length;
// Render the graph
if (gvRenderData(gvc, g, format, out result, out length) != SUCCESS)
throw new Exception("Render failed.");
// Create an array to hold the rendered graph
byte[] bytes = new byte[length];
// Copy the image from the IntPtr
Marshal.Copy(result, bytes, 0, length);
// Free up the resources
gvFreeLayout(gvc, g);
agclose(g);
gvFreeContext(gvc);
using (MemoryStream stream = new MemoryStream(bytes))
{
return Image.FromStream(stream);
}
}
}
Using David Brown's downloadable sample at ImplicitOperator I've put together an often working GraphViz renderer of a DOT file to an in-memory image.
Unfortunately, my version fails at a guestimated rate of 1 in 8 executions from with the IIS 7 ASP.NET web application I've got it in. I know that the DOT file data is consistent because I've compared the failing instances against the working instances and they are identical.
As David's site seems to suggest that the blog's future is uncertain, I'll reprint the interop pieces here. Hope he doesn't mind. The failure is toward the end of the sample, within RenderImage at the third statement set. I've noted the failing line with // TODO: .... The failure always happens there (if it happens at all). By this line, g and gvc pointers are non-zero and the layout string is correctly populated.
I don't really expect anyone to debug this at runtime. Rather, I hope that some static analysis of the interop code might reveal the problem. I can't think of any advanced marshaling techniques available here - two IntPtrs and a string shouldn't need a lot of help, right?
Thanks!
Side note: I've looked at a trial of MSAGL and I'm not impressed - for $99 from Microsoft, I'd expect more features for node layout and/or documentation explaining what I'm missing. Maybe my rapid port from QuickGraph to AGL unfairly biases my experience because of some fundamental differences in the approaches (edge-centric vs node-centric, for example).
public static class Graphviz
{
public const string LIB_GVC = "gvc.dll";
public const string LIB_GRAPH = "graph.dll";
public const int SUCCESS = 0;
/// <summary>
/// Creates a new Graphviz context.
/// </summary>
[DllImport(LIB_GVC)]
public static extern IntPtr gvContext();
/// <summary>
/// Releases a context's resources.
/// </summary>
[DllImport(LIB_GVC)]
public static extern int gvFreeContext(IntPtr gvc);
/// <summary>
/// Reads a graph from a string.
/// </summary>
[DllImport(LIB_GRAPH)]
public static extern IntPtr agmemread(string data);
/// <summary>
/// Releases the resources used by a graph.
/// </summary>
[DllImport(LIB_GRAPH)]
public static extern void agclose(IntPtr g);
/// <summary>
/// Applies a layout to a graph using the given engine.
/// </summary>
[DllImport(LIB_GVC)]
public static extern int gvLayout(IntPtr gvc, IntPtr g, string engine);
/// <summary>
/// Releases the resources used by a layout.
/// </summary>
[DllImport(LIB_GVC)]
public static extern int gvFreeLayout(IntPtr gvc, IntPtr g);
/// <summary>
/// Renders a graph to a file.
/// </summary>
[DllImport(LIB_GVC)]
public static extern int gvRenderFilename(IntPtr gvc, IntPtr g,
string format, string fileName);
/// <summary>
/// Renders a graph in memory.
/// </summary>
[DllImport(LIB_GVC)]
public static extern int gvRenderData(IntPtr gvc, IntPtr g,
string format, out IntPtr result, out int length);
public static Image RenderImage(string source, string layout, string format)
{
// Create a Graphviz context
IntPtr gvc = gvContext();
if (gvc == IntPtr.Zero)
throw new Exception("Failed to create Graphviz context.");
// Load the DOT data into a graph
IntPtr g = agmemread(source);
if (g == IntPtr.Zero)
throw new Exception("Failed to create graph from source. Check for syntax errors.");
// Apply a layout
if (gvLayout(gvc, g, layout) != SUCCESS) // TODO: Fix AccessViolationException here
throw new Exception("Layout failed.");
IntPtr result;
int length;
// Render the graph
if (gvRenderData(gvc, g, format, out result, out length) != SUCCESS)
throw new Exception("Render failed.");
// Create an array to hold the rendered graph
byte[] bytes = new byte[length];
// Copy the image from the IntPtr
Marshal.Copy(result, bytes, 0, length);
// Free up the resources
gvFreeLayout(gvc, g);
agclose(g);
gvFreeContext(gvc);
using (MemoryStream stream = new MemoryStream(bytes))
{
return Image.FromStream(stream);
}
}
}
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(3)
Visual Studio 2010 添加了“PInvokeStackImbalance”检测,我认为它帮助我解决了问题。虽然图像仍然会生成,但我会多次收到此错误。
通过在所有 LIBGVC PInvoke 签名上指定 CallingConvention = CallingConvention.Cdecl,错误和崩溃就会消失。
自从进行此更改以来,我没有发生过崩溃,因此我现在将其标记为新答案。
Visual Studio 2010 added a "PInvokeStackImbalance" detection that I think helped me fix the problem. While the image would still get generated, I would get this error several times.
By specifying
CallingConvention = CallingConvention.Cdecl
on all the LIBGVC PInvoke sigantures, the error and crashes disappear.I've had no crashes since making this change, so I'll mark this as the new answer, for now.
我记得在写这篇文章时遇到了这样的问题,并在此处发布了有关它们的问题 和此处(其中第二个您似乎已发表评论;我很抱歉没有早点看到评论)。
第一个问题可能与此没有直接关系,因为我正在用 C 而不是 C# 编写测试应用程序,并且
gvLayout
每次都会失败,而不是偶尔失败。无论如何,请确保您的应用程序有权访问 Graphviz 配置文件(将其与可执行文件一起复制或将 Graphviz bin 目录放入系统路径中)。第二个问题更相关,只不过它适用于
agmemread
而不是gvLayout
。但是,两者很可能是由同一问题引起的。我始终无法找到解决方案,因此我向 Graphviz 团队发送了一份 错误报告。不幸的是,问题还没有得到解决。Graphviz API 非常简单,因此问题不太可能是由互操作代码引起的。我在文章中忽略了一件事:需要释放
result
指针。我不知道这是否会解决您的问题,但无论如何添加它仍然是一个好主意:据我所知,这个问题与 Graphivz 如何从内部错误中恢复有关,因此在解决错误之前,我'我不确定你或我能做些什么。但是,我不是互操作专家,所以希望其他人可以为您提供更多帮助。
I remember running into problems like this while I was working on the article and posted questions about them here and here (the second of which you appear to have commented on; my apologies for not seeing the comment earlier).
The first question is probably not directly related to this because I was writing a test application in C, not C#, and
gvLayout
was failing every single time instead of just every now and then. Regardless, make sure your application does have access to the Graphviz configuration file (copy it alongside your executable or place the Graphviz bin directory in your system PATH).The second question is more relevant, except it applies to
agmemread
and notgvLayout
. However, it's very possible that both are caused by the same issue. I was never able to figure out a solution, so I sent the Graphviz team a bug report. Unfortunately, it hasn't been resolved.The Graphviz API is very simple, so it's unlikely that the issue is caused by the interop code. There is one thing that I neglected to mention in the article: the
result
pointer needs to be freed. I don't know if this will fix your issue, but it's still a good idea to add it anyway:As far as I know, this issue is related to how Graphivz recovers from internal errors, so until the bug is addressed, I'm not sure there's anything you or I can do. But, I'm not an interop expert, so hopefully someone else can help you out a little more.
改变调用约定没有帮助!
是的,当一个简单(实际上不是那么简单)的点源时它可以工作,但如果点源包含 unicode 字符(简体中文),它总是崩溃。
changing the calling convention DOESN'T help!!
Yes, it works when a simple (actually not that simple) dot source, but it ALWAYS crash if the dot source containing unicode character (simplified chinese).