GraphViz C#interop偶尔会导致AccessViolationException

时间:2011-02-02 00:27:46

标签: c# graphviz access-violation

使用David Brown's downloadable sample at ImplicitOperator我已将DOT文件的经常工作的GraphViz渲染器放到内存中的图像中。

不幸的是,我的版本因为我已经获得的IIS 7 ASP.NET Web应用程序的8次执行中的1次估计失败率。我知道DOT文件数据是一致的,因为我已经比较了失败针对工作实例的实例,它们完全相同。

由于大卫的网站似乎暗示博客的未来不确定,我将在这里重新打印互联网文章。希望他不介意。失败是在示例的末尾,在第三个语句集的RenderImage中。我已经注意到// TODO的失败行:......失败总是发生在那里(如果它发生的话)。通过这一行,g和gvc指针不为零,并且正确填充了布局字符串。

我真的不希望任何人在运行时调试它。相反,我希望对互操作代码的一些静态分析可能会揭示问题。我想不出这里有任何先进的编组技术 - 两个IntPtrs和一个字符串不需要很多帮助,对吗?

谢谢!

旁注:我看了一下MSAGL的试用版,我没有留下深刻的印象 - 从微软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);
    }
  }
}

3 个答案:

答案 0 :(得分:5)

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);

...

自从进行此更改后我没有崩溃,所以我现在将此标记为新答案。

答案 1 :(得分:4)

我记得当我处理文章并发布有关他们herehere的问题时遇到类似这样的问题(您似乎对其中的第二个进行了评论;对于没有看到我抱歉先前的评论)。

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

第二个问题更具相关性,但它适用于agmemread而非gvLayout。但是,两者都很可能是由同一问题引起的。我无法找到解决方案,因此我向Graphviz团队发送了bug report。不幸的是,它还没有得到解决。

Graphviz API非常简单,因此问题不太可能是由互操作代码引起的。在文章中我忽略了一件事:需要释放result指针。我不知道这是否会解决您的问题,但无论如何仍然是一个好主意:

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

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

据我所知,这个问题与Graphivz如何从内部错误中恢复有关,因此在解决错误之前,我不确定你或我能做什么。但是,我不是互操作专家,所以希望其他人可以帮助你多一点。

答案 2 :(得分:0)

更改呼叫约定DOESN&#39; T help !!

是的,当一个简单的(实际上并非那么简单)点源时,它会起作用,但如果点源包含unicode字符(简体中文),它总是会崩溃。