简体   繁体   English

GraphViz C#interop偶尔会导致AccessViolationException

[英]GraphViz C# interop resulting in AccessViolationException occasionally

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. 在ImplicitOperator中使用David Brown的可下载示例我将一个经常工作的DOT文件的GraphViz渲染器组合到一个内存中的图像中。

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. 不幸的是,我的版本因为我已经使用IIS 7 ASP.NET Web应用程序的8次执行中的1次失败率而失败。我知道DOT文件数据是一致的,因为我将失败的实例与工作实例,它们是相同的。

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. 失败是在示例的末尾,在第三个语句集的RenderImage中。 I've noted the failing line with // TODO: .... The failure always happens there (if it happens at all). 我已经注意到// TODO的失败行:......失败总是发生在那里(如果它发生的话)。 By this line, g and gvc pointers are non-zero and the layout string is correctly populated. 通过这一行,g和gvc指针不为零,并且正确填充了布局字符串。

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? 我想不出任何先进的编组技术 - 两个IntPtrs和一个字符串不需要很多帮助,对吧?

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. 旁注:我看了一下MSAGL的试用版,我没有留下深刻的印象 - 微软99美元,我希望节点布局和/或文档的更多功能可以解释我所缺少的内容。 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). 也许我从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);
    }
  }
}

Visual Studio 2010 added a "PInvokeStackImbalance" detection that I think helped me fix the problem. Visual Studio 2010添加了一个“PInvokeStackImbalance”检测,我认为这有助于我解决问题。 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. 通过在所有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);

...

I've had no crashes since making this change, so I'll mark this as the new answer, for now. 自从做出这个改变后我没有崩溃,所以我现在将此标记为新的答案。

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. 第一个问题可能与此没有直接关系,因为我用C编写测试应用程序,而不是C#,并且gvLayout都失败而不是gvLayout失败。 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). 无论如何,请确保您的应用程序可以访问Graphviz配置文件(将其与可执行文件一起复制或将Graphviz bin目录放在系统PATH中)。

The second question is more relevant, except it applies to agmemread and not gvLayout . 第二个问题更相关,除了它适用于agmemread而不是gvLayout 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 . 我无法找到解决方案,因此我向Graphviz团队发送了一份错误报告 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. Graphviz API非常简单,因此问题不太可能是由互操作代码引起的。 There is one thing that I neglected to mention in the article: the result pointer needs to be freed. 在文章中我忽略了一件事: result指针需要被释放。 I don't know if this will fix your issue, but it's still a good idea to add it anyway: 我不知道这是否会解决您的问题,但无论如何仍然是一个好主意:

[DllImport("msvcrt.dll", SetLastError = true)]
private static extern void free(IntPtr pointer);

// After Marshal.Copy in RenderImage
free(result);

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. 据我所知,这个问题与Graphivz如何从内部错误中恢复有关,因此在解决错误之前,我不确定你或我能做什么。 But, I'm not an interop expert, so hopefully someone else can help you out a little more. 但是,我不是互操作专家,所以希望其他人可以帮助你多一点。

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). 是的,当一个简单的(实际上不是那么简单的)点源时它起作用,但如果点源包含unicode字符(简体中文),它总是会崩溃。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM