2011-02-02 12 views
7

Utilizzo di David Brown's downloadable sample at ImplicitOperator Ho creato un spesso Renderer GraphViz funzionante di un file DOT in un'immagine in memoria.GraphViz C# interop risultante in AccessViolationException occasionalmente

Sfortunatamente, la mia versione non riesce ad una velocità guestimated di 1 su 8 esecuzioni con l'applicazione Web IIS 7 ASP.NET in cui sono presente. So che i dati del file DOT sono coerenti perché ho confrontato il fallendo istanze contro le istanze di lavoro e sono identici.

Poiché il sito di David sembra suggerire che il futuro del blog è incerto, ristamperò qui i pezzi di interopzione. Spero che non gli dispiaccia. L'errore è verso la fine dell'esempio, all'interno di RenderImage alla terza serie di istruzioni. Ho notato la linea in errore con // TODO: .... L'errore si verifica sempre lì (se succede a tutti). Con questa linea, i puntatori g e gvc sono diversi da zero e la stringa di layout è correttamente compilata.

Non mi aspetto davvero che qualcuno esegua il debug di questo in fase di esecuzione. Piuttosto, spero che qualche analisi statica del codice di interoperabilità possa rivelare il problema. Non riesco a pensare a tecniche di marshalling avanzate disponibili qui: due IntPtr e una stringa non dovrebbero richiedere molto aiuto, giusto?

Grazie!

Nota a margine: ho esaminato una versione di prova di MSAGL e non mi entusiasma: per $ 99 di Microsoft, mi aspetto più funzionalità per il layout e/o la documentazione del nodo che spiegano cosa mi manca. Forse il mio rapido passaggio da QuickGraph a AGL distorce ingiustamente la mia esperienza a causa di alcune differenze fondamentali negli approcci (edge-centric vs node-centric, ad esempio).

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

So che questo potrebbe sembrare stupido. Ma c'è bisogno di eseguire la DLL direttamente nel tuo codice? Sarebbe possibile generare la lingua dei punti in un file di testo e quindi eseguire l'eseguibile con punti standalone con un comando come: "dot -Tpng -o MyFile.png MyFile.dot" usando la classe C# Process? Una volta che hai l'immagine, puoi caricarla direttamente nella memoria del tuo programma. – jluzwick

+0

Immagino che sia un'opzione, ma trattare i file su disco per questa operazione sarebbe lento e introdurrebbe ulteriori punti di errore nei meccanismi di I/O stessi. Dal momento che la dll è impiegata in entrambi i casi, preferirei capire come farlo funzionare correttamente nella memoria. –

+0

Puoi mostrare l'uso della tua classe? –

risposta

4

Visual Studio 2010 ha aggiunto un rilevamento "PInvokeStackImbalance" che penso mi abbia aiutato a risolvere il problema. Mentre l'immagine verrebbe comunque generata, otterrei questo errore più volte.

Specificando CallingConvention = CallingConvention.Cdecl su tutte le sigle di PInvoke LIBGVC, l'errore e gli arresti anomali scompaiono.

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

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

... 

Non ho avuto arresti anomali da quando ho apportato questa modifica, quindi contrassegnerò questa come la nuova risposta, per ora.

4

Mi ricordo di incorrere in problemi di questo tipo, mentre stavo lavorando su questo articolo e inviato domande su di loro here e here (il secondo dei quali si sembrano aver commentato; le mie scuse per non vedere il commento precedente) .

La prima domanda probabilmente non è direttamente correlata a questo perché stavo scrivendo un'applicazione di test in C, non C#, e gvLayout non funzionava ogni volta invece che solo ogni tanto. Indipendentemente da ciò, assicurarsi che l'applicazione abbia accesso al file di configurazione Graphviz (copiarlo insieme all'eseguibile o posizionare la directory bin di Graphviz nel PATH di sistema).

La seconda domanda è più pertinente, tranne che si applica a agmemread e non a gvLayout. Tuttavia, è molto probabile che entrambi siano causati dallo stesso problema. Non sono mai riuscito a trovare una soluzione, quindi ho inviato il team Graphviz allo bug report. Sfortunatamente, non è stato risolto.

L'API Graphviz è molto semplice, quindi è improbabile che il problema sia causato dal codice di interoperabilità. C'è una cosa che ho dimenticato di menzionare nell'articolo: il puntatore result deve essere liberato. Non so se questo risolverà il problema, ma è ancora una buona idea aggiungere comunque:

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

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

Per quanto ne so, questo problema è legato al modo in Graphivz recupera da errori interni, così fino a quando il bug è indirizzato, non sono sicuro che ci sia qualcosa che tu o io possiamo fare. Ma, io non sono un esperto di interopzioni, quindi spero che qualcun altro possa aiutarti un po 'di più.

+0

Ciao David. Grazie per la risposta e il cambio di codice. Ho provato ad applicarlo e ora si blocca SEMPRE su libero (risultato); Qualche idea del perché? Forse il mio altro codice non è sincronizzato con il tuo? –

+0

Ho appena eseguito nuovamente l'esempio con il codice 'free' e si è bloccato anche per me. Non sono sicuro del motivo per cui non l'ha fatto un anno fa, però. Ecco una segnalazione di bug (non risolta, purtroppo): http://www.graphviz.org/bugs/b1775.html –

+0

Bummer. GraphViz ha il miglior motore di layout del nodo e le opzioni generali, ma ha questi due bug irrisolti di cui abbiamo discusso. E li ho trovati entrambi in quella che considererei un'applicazione abbastanza semplice, quindi sono vicini alla superficie. Inoltre, nessuna distribuzione x64. Mi sento come se sapessi come riscrivere tutto il mio modo ideale in C# tranne che per gli algoritmi di routing del bordo. –

0

cambiare la convenzione di chiamata NON AIUTA !!

Sì, funziona quando una sorgente di punti semplice (in realtà non così semplice), ma si blocca SEMPRE se la fonte di punti contiene un carattere unicode (cinese semplificato).

+1

Forse si tratta di un altro problema che si blocca in ALSO? Non so che Graphviz supporti l'unicode, vero? [Ecco un link che menziona l'uso della codifica html dei caratteri unicode per alcuni contenuti.] (Http://www.graphviz.org/doc/info/lang.html) –

+0

Sì, posso confermare che Graphviz supporta l'unicode. Posso generare un grafico corretto con un file di punti scritto a mano. – Alsan