Jump to content
  • entries
    3
  • comment
    1
  • views
    5002

Processes, Events, and Debugging


The Background

I'm a professional developer using mainly C#. On the side, I write a ROM editor using VB.Net. They're very similar thanks to them mostly wrapping the .Net Framework, and because of how complex this application is, I'm in no hurry to rewrite it. This application knows the ins and outs of a few dozen file types and can, well, edit them. One such file type is a compiled LUA script. In order to edit these, they must first be decompiled. Rather than try to figure out that myself, I use a 3rd party tool that can do that for me (sometimes the best code is the code you didn't have to write). To use it, I point it to the compiled script on disk, and it spits out the decompiled text into its standard output. Integrating with it is simple thanks to .Net: I can use a Process object to start it, redirect its standard output, and read the decompiled text as it's written. This is all part of a larger process, where transparent to the user, I decompile a bunch of these scripts, automatically make some changes, and recompile them, all in one go, so people can play a game using different characters.

A few days ago, I did some refactoring to it so I can more easily get notified of unsuccessful exit codes when recompiling (previously the program would just continue fine, and any files with syntax errors would simply remain unmodified). Unfortunately for me, I was in a hurry and didn't test anything except that program before I pushed the change to the master branch and let my CI server publish a development build, currently the only way for people to get precompiled versions of my application. (The CI server is Team City, since they've generously given me unlimited everything with my open source license.) Also I don't have any tests covering this part of the code.

The Problem

One of my application's users encountered some (well, dozens) of exceptions when my program tried to recompile the decompiled scripts. I try reproducing the issue, and I indeed find a syntax error in the same spot the newly-created error message said it was. After looking closer, I found the issue was there before the decompiled scripts were automatically modified. The script looked something like this:

function addTheNumbers(numberA, numberB)
function addTheNumbers(numberA, numberB)
  return numberA + numberB
  return numberA + numberB
end
end
print(addTheNumbers(10, 20))
print(addTheNumbers(10, 20))

It should be fairly obvious what the problem here is. The lines are doubled. I facepalm on the inside and start investigating.

The Adventure

Because the code I changed is being called about a dozen times on different threads, I did the responsible thing and wrote a test for it. After getting my test to fail for the right reason, I look at my code, and here's what I see:

image.png

That looks like pretty standard stuff. The variables "captureConsoleOutput" and "captureConsoleError" are both true, telling the Process to redirect standard output and standard error, and the code will proceed to register event handlers. When I place breakpoints on the event handlers, I find that they're being hit twice per line the LUA decompiler writes. I scour the code and make sure that I only call Start once, and the event handlers are only added once as shown.

I tried a bunch of other things too. What you see above is actually after another thing I tried. Previously, I was dumping standard output and error into the same StringBuilder , in case the program was outputting to both for some reason.

I finally went to GitHub to check what actually changed when I did the thing mentioned in The Background above, and while it was a substantial change to the class shown above, it all seems pretty sane. But then I spot it. One crucial thing that didn't change.

image.png

Oh. The event handler was registered twice. I added lines 39 and 40 (the two AddHandler lines) and forgot completely about the Handles clause.

How Events Work And Why It Took Me So Long To Spot This

Events are magical things that can be called, and event handlers that have been registered to the event are run, being given the data that was provided when the event was called. This isn't so much like method calls as it is interrupts.

The syntax for adding an event handler in C# looks like this:

myEvent += myEventHandlerMethod;

And the syntax for adding an event handler in VB.Net looks like this:

AddHandler myEvent, AddressOf myEventHandlerMethod

Usually C# and VB.Net are very similar thanks to the .Net Framework, but sometimes there are some differences, where one language can do a thing that the other cannot.

There are some gotchas when it comes to events, so they need to be used with care. .Net is a framework that tries its best to clean up after you and prevent memory leaks. Simply remove all references to an object, and .Net makes it go away. But when you register an event handler to an event in another class, that event handler has a reference to the event, and by extension the class. If you get rid of the references to that class, the reference to the event is still there, and .Net can't clean it up. But you also can no longer remove the event handler since the reference to the class is gone. That's why it's important to remove event handlers as you would pointers in C/C++.

The syntax for removing an event in C# looks like this:

myEvent -= myEventHandlerMethod;

And the syntax for removing an event in VB.Net looks like this:

RemoveHandler myEvent, AddressOf myEventHandlerMethod

To help make managing this easier, VB.Net has some extra syntax. Instead of manually adding event handlers, you can declare a variable as WithEvents, then use the Handles clause on a method, like so:

Private WithEvents _myMemberVariable As SomeRandomClassWithEvents

Private Sub _myMemberVariable_OnPropertyChanged(sender as Object, e as PropertyChangedEventArgs) Handles _myMemberVariable.PropertyChanged
    'Do the thing
End Sub

With no additional setup, when I assign something to _myMemberVariable, _myMemberVariable_OnPropertyChanged will automatically be run whenever _myMemberVariable.PropertyChanged is raised. If I change _myMemberVariable to some other SomeRandomClassWithEvents, it'll take care of removing the old handlers and adding the new ones for me. And setting _myMemberVariable to Nothing (aka null) will clean it up for me.

I've been using C# a lot more lately, I've had to start doing events the old fashioned, no hand-holding way. I've been doing it for long enough now that I forgot to even check for the Handles clause when all evidence pointed to an event handler being registered twice.

VB.Net is a fine language, and IMO it's easier to read and easier to type (not having to constantly type '{' and ':' when simply pressing enter works instead). However, it no longer has language parity with C#, meaning C# will continue to evolve, and VB.Net won't grow with it. The future is C#, and it's sad to see VB.Net be left behind.

Guess I'll have to get around to rewriting this thing for it to be part of the future.

0 Comments


Recommended Comments

There are no comments to display.

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
×
×
  • Create New...