views:

157

answers:

1

I'm P/Invoking out to Graphviz as shown here. When I wrote that blog entry, the code worked just fine. Now, I'm putting together an HttpModule that renders Graphviz graphs using that code, but I get an AccessViolationException at agmemread.

// Native signature
Agraph_t agmemread(char *);

// P/Invoke Signature
[DllImport(LIB_GRAPH)]
private static extern IntPtr agmemread(string data);

// Usage
IntPtr g = agmemread(data);

Like I said, this worked perfectly before. But now, I can't get my code to work in anything. Even my old Graphviz apps based on the same code don't work anymore.

What could I have possibly changed that would cause this? I haven't even downloaded a new version of Graphviz or anything, so the DLLs are all the same.

EDIT: I tried changing string to StringBuilder, but that produced the same result. Then, I added a MarshalAs attribute:

static extern IntPtr agmemread([MarshalAs(UnmanagedType.LPWStr)] string data);

With that, I no longer get an AccessViolationException, but Graphviz fails to read the string correctly and returns a null pointer.

A: 

Unmanaged code rarely needs a lot of help from C# to start generating access violations. There's nothing wrong with your P/Invoke signature, that cannot be the cause.

The most common source of AVs in unmanaged code is heap corruption. C/C++ code doesn't have a garbage collector, memory must be managed explicitly. Not only must it take care of releasing memory (or it will leak), it is also responsible for allocating the correct size and making sure that the code that writes to the allocated memory doesn't write past the end of the allocated memory block or writes into memory that was already freed. That last requirement is where C/C++ code often fails.

The trouble with heap corruption is that it is extremely hard to diagnose. It can go unnoticed for quite a while. The typical damage done is that the internal heap structure is compromised, or the data in another heap allocation is overwritten. That doesn't cause a problem until later, when the heap block is released or the overwritten data is used. The code that generates the exception is not actually responsible for the damage that was done earlier. Which sets you off on the wrong track trying to find the source of the problem.

Finding the real trouble maker is very hard, you'd have only a few breadcrumbs to figure out what might have gone wrong. It is very hard when you have the C/C++ source code, but running it in a debug build with a debug allocator helps. It is impossible when you don't have the source code.

Unless you can pin-point a problem with using the API from earlier calls made, you'll need help from the vendor or support group to really solve this problem. Good luck.

Hans Passant
I believe you hit the nail on the head! Before the access violation exceptions, I had been supplying invalid data to `agmemread`, causing it to fail. So, with what you said in mind, I thought maybe `agmemread` wasn't cleaning up properly when it fails. I restarted my machine to get a clean slate and now it works just fine with the original code and proper input data. However, I started trying to reproduce it by making multiple invalid calls to `agmemread`, but it's not exhibiting any access violations this time. Very strange. Perhaps I'll email the Graphviz developers about it.
David Brown
By the way, when I said "cause it to fail" and "make multiple invalid calls", I mean so that the function returns a null pointer, which is what it's supposed to do when an error occurs.
David Brown
Sure, the code that's usually tested least is the error handling code.
Hans Passant
I submitted a bug report to the Graphviz team and they think it has to do with how the parser tries to recover from errors. Hopefully it gets resolved soon. Thanks for pointing me in the right direction, again!
David Brown
Yeah, I'm pretty happy with my answer. Compensates for the complete lack of "this was helpful" upvotes in the most popular tag.
Hans Passant