2011-02-02 9 views
7

Używanie David Brown's downloadable sample at ImplicitOperator Zrobiłem często działający renderer GraphViz pliku DOT na obraz w pamięci.GraphViz C# interop powodujący wyjątek AccessViolationException sporadycznie

Niestety, moja wersja kończy się niepowodzeniem w gościnnie ocenianej stawce 1 w 8 wykonaniach za pomocą aplikacji sieci Web programu ASP.NET programu IIS 7. Mam to dane. Wiem, że dane pliku DOT są zgodne, ponieważ porównałem niepoprawne instancje względem działających instancji i są identyczne.

Wygląda na to, że strona Davida sugeruje, że przyszłość bloga jest niepewna. Mam nadzieję, że nie ma nic przeciwko. Błąd kończy się na końcu próbki w RenderImage na trzecim zestawie instrukcji. Zanotowałem linię, która zawodziła z // TODO: .... Awaria zawsze się tam dzieje (jeśli w ogóle się zdarza). W tej linii wskaźniki g i gvc są niezerowe, a ciąg układu jest poprawnie wypełniony.

Naprawdę nie oczekuję, aby ktokolwiek debugował to podczas uruchamiania. Mam raczej nadzieję, że niektóre statyczne analizy kodu interopu mogą ujawnić problem. Nie mogę wymyślić żadnych zaawansowanych technik marszałkowskich dostępnych tutaj - dwa Inttraki i ciąg nie powinny wymagać dużej pomocy, prawda?

Dzięki!

Nota boczna: Sprawdziłem wersję próbną MSAGL i nie jestem pod wrażeniem - za 99 USD od Microsoftu, oczekiwałbym więcej funkcji układu węzła i/lub dokumentacji wyjaśniającej to, czego mi brakuje. Być może mój szybki port od QuickGraph do AGL niesprawiedliwie odchyla moje doświadczenie z powodu pewnych fundamentalnych różnic w podejściach (na przykład, zorientowanych na krawędzie i na węzłach).

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); 
    } 
    } 
} 
+1

Wiem, że to może brzmieć głupio. Ale czy istnieje potrzeba uruchomienia biblioteki DLL bezpośrednio w kodzie? Czy byłoby możliwe wygenerowanie języka kropki w pliku tekstowym, a następnie uruchomienie autonomicznego punktu wykonywalnego za pomocą polecenia typu: "dot -Tpng -o MyFile.png MyFile.dot" używając klasy C# Process? Gdy masz obraz, możesz go załadować bezpośrednio do pamięci programów. – jluzwick

+0

Myślę, że jest to opcja, ale zajmowanie się plikami na dysku dla tej operacji byłoby powolne I wprowadziłoby dodatkowe punkty awarii w samych mechanizmach We/Wy. Ponieważ biblioteka dll jest używana tak czy inaczej, wolę zrozumieć, jak sprawić, aby działała poprawnie w pamięci. –

+0

Czy możesz pokazać wykorzystanie swojej klasy? –

Odpowiedz

4

Program Visual Studio 2010 dodał wykrywanie "PInvokeStackImbalance", które według mnie pomogło mi rozwiązać problem. Podczas gdy obraz nadal byłby generowany, dostałbym ten błąd kilka razy.

Określając CallingConvention = CallingConvention.Cdecl dla wszystkich znaczników Pwvvb LIBGVC, błąd i awarie znikają.

[DllImport(LIB_GVC, CallingConvention = CallingConvention.Cdecl)] 
public static extern IntPtr gvContext(); 

[DllImport(LIB_GVC, CallingConvention = CallingConvention.Cdecl)] 
public static extern int gvFreeContext(IntPtr gvc); 

... 

Od czasu wprowadzenia tej zmiany nie miałem żadnych awarii, więc na razie oznaczę to jako nową odpowiedź.

4

Pamiętam systemem do problemów, takich jak ten, kiedy pracowałem na wyrobie i zamieszczonych pytań o nich here i here (drugi z którym wydaje się, że skomentował; moje przeprosiny za nie widząc komentarz wcześniej) .

Pierwsze pytanie prawdopodobnie nie jest bezpośrednio związane z tym, ponieważ pisałem aplikację testową w języku C, nie C#, a gvLayout zawodziło za każdym razem, zamiast od czasu do czasu. Niezależnie od tego upewnij się, że twoja aplikacja ma dostęp do pliku konfiguracyjnego Graphviz (skopiuj go wraz z plikiem wykonywalnym lub umieść katalog bin Graphviz w ścieżce systemowej PATH).

Drugie pytanie jest bardziej istotne, z wyjątkiem tego, że dotyczy ono agmemread, a nie gvLayout. Jednak bardzo możliwe, że oba są spowodowane tym samym problemem. Nigdy nie udało mi się znaleźć rozwiązania, dlatego wysłałem zespół Graphviz na numer bug report. Niestety, nie został rozwiązany.

Interfejs API Graphviz jest bardzo prosty, więc jest mało prawdopodobne, że przyczyną problemu jest kod interopowy. Jest jedna rzecz, o której nie wspomniałem w artykule: należy zwolnić wskaźnik result. Nie wiem, czy to rozwiąże problem, ale to wciąż dobry pomysł, aby go dodać w każdym razie:

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

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

O ile mi wiadomo, ten problem jest związany jak Graphivz odzyskuje od błędów wewnętrznych, więc dopóki błąd jest rozwiązany, nie jestem pewien, czy jest coś, co Ty lub ja możemy zrobić. Ale nie jestem ekspertem od współpracy, więc mam nadzieję, że ktoś inny może ci pomóc.

+0

Witaj David. Dziękuję za odpowiedź i zmianę kodu. Próbowałem go zastosować i teraz zawiesza się ZAWSZE na bezpłatnym (wynik); Każdy pomysł, dlaczego? Może mój drugi kod nie jest zsynchronizowany z twoim? –

+0

Po prostu uruchomiłem próbkę z kodem "free" i również się dla mnie zawiesił. Nie wiem, dlaczego nie robił tego rok temu. Oto raport o błędzie (niestety nierozwiązany): http://www.graphviz.org/bugs/b1775.html –

+0

Bummer. GraphViz ma najlepszy silnik układu węzłów i ogólne opcje, ale ma te dwa nierozwiązane błędy, które omawialiśmy. I znalazłem je zarówno w tym, co uważam za dość podstawową aplikację, więc są blisko powierzchni. Plus, bez dystrybucji x64. Czuję, że wiem, jak przepisać wszystko mój idealny sposób w C#, z wyjątkiem algorytmów routingu krawędzi. –

0

zmiana konwencji telefonicznej NIE POMOCY !!

Tak, działa przy prostym (właściwie nie tak prostym) źródle kropek, ale ZAWSZE ulega awarii, jeśli źródło kropki zawiera znak Unicode (chiński uproszczony).

+1

Być może to inny problem, który RÓWNIEŻ powoduje awarię? Nie wiem, czy Graphviz obsługuje Unicode, prawda? [Tutaj jest link, który wspomina o kodowaniu html znaków unicode dla niektórych treści.] (Http://www.graphviz.org/doc/info/lang.html) –

+0

Tak, mogę potwierdzić, że Graphviz obsługuje Unicode. Potrafię wygenerować poprawny wykres ręcznie zapisanym plikiem dot. – Alsan

Powiązane problemy