GraphViz C#interop偶尔会导致AccessViolationException
在ImplicitOperator中使用David Brown的可下载示例我将一个经常工作的DOT文件的GraphViz渲染器组合到一个内存中的图像中。
不幸的是,我的版本因为我已经使用的IIS 7 ASP.NET Web应用程序的8次执行中的1次估计失败率。我知道DOT文件数据是一致的,因为我将失败的实例与工作实例,他们是相同的。
由于大卫的网站似乎暗示博客的未来不确定,我将在这里重印互联文件。 希望他不介意。 失败是在示例的末尾,在第三个语句集的RenderImage中。 我已经注意到// TODO的失败行:……失败总是发生在那里(如果它发生的话)。 通过这一行,g和gvc指针不为零,并且正确填充了布局字符串。
我真的不希望任何人在运行时调试它。 相反,我希望对互操作代码的一些静态分析可能会揭示问题。 我想不出这里有任何先进的编组技术 – 两个IntPtrs和一个字符串不需要很多帮助,对吧?
谢谢!
旁注:我看了一下MSAGL的试用版,我没有留下深刻的印象 – 微软的99美元,我希望节点布局和/或文档的更多function可以解释我所缺少的内容。 也许我从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; /// /// Creates a new Graphviz context. /// [DllImport(LIB_GVC)] public static extern IntPtr gvContext(); /// /// Releases a context's resources. /// [DllImport(LIB_GVC)] public static extern int gvFreeContext(IntPtr gvc); /// /// Reads a graph from a string. /// [DllImport(LIB_GRAPH)] public static extern IntPtr agmemread(string data); /// /// Releases the resources used by a graph. /// [DllImport(LIB_GRAPH)] public static extern void agclose(IntPtr g); /// /// Applies a layout to a graph using the given engine. /// [DllImport(LIB_GVC)] public static extern int gvLayout(IntPtr gvc, IntPtr g, string engine); /// /// Releases the resources used by a layout. /// [DllImport(LIB_GVC)] public static extern int gvFreeLayout(IntPtr gvc, IntPtr g); /// /// Renders a graph to a file. /// [DllImport(LIB_GVC)] public static extern int gvRenderFilename(IntPtr gvc, IntPtr g, string format, string fileName); /// /// Renders a graph in memory. /// [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); } } }
Visual Studio 2010添加了一个“PInvokeStackImbalance”检测,我认为这有助于我解决问题。 虽然图像仍然会生成,但我会多次出现此错误。
通过在所有LIBGVC PInvoke sigantures上指定CallingConvention = CallingConvention.Cdecl
,错误和崩溃消失。
[DllImport(LIB_GVC, CallingConvention = CallingConvention.Cdecl)] public static extern IntPtr gvContext(); [DllImport(LIB_GVC, CallingConvention = CallingConvention.Cdecl)] public static extern int gvFreeContext(IntPtr gvc); ...
自从做出这个改变后我没有崩溃,所以我现在将此标记为新的答案。
我记得当我正在处理这篇文章并在这里和这里发布有关它们的问题时遇到这样的问题(第二个你似乎已经评论过了;我很抱歉没有看到之前的评论)。
第一个问题可能与此没有直接关系,因为我在C中编写了一个测试应用程序,而不是C#,并且gvLayout
都失败而不是gvLayout
失败。 无论如何,请确保您的应用程序可以访问Graphviz配置文件(将其与可执行文件一起复制或将Graphviz bin目录放在系统PATH中)。
第二个问题更相关,除了它适用于agmemread
而不是gvLayout
。 但是,两者都很可能是由同一问题引起的。 我无法找到解决方案,因此我向Graphviz团队发送了一份错误报告 。 不幸的是,它还没有得到解决。
Graphviz API非常简单,因此问题不太可能是由互操作代码引起的。 在文章中我忽略了一件事: result
指针需要被释放。 我不知道这是否能解决您的问题,但无论如何都要添加它仍然是一个好主意:
[DllImport("msvcrt.dll", SetLastError = true)] private static extern void free(IntPtr pointer); // After Marshal.Copy in RenderImage free(result);
据我所知,这个问题与Graphivz如何从内部错误中恢复有关,因此在解决错误之前,我不确定你或我能做什么。 但是,我不是互操作专家,所以希望其他人可以帮助你多一点。
改变召唤惯例没有帮助!!
是的,当一个简单的(实际上不是那么简单的)点源时它起作用,但如果点源包含unicode字符(简体中文),它总是会崩溃。