Here’s an example of an API versioning problem.
Anytime you take two separate concepts and tie them together based off some current implementation assumption, you’re going to get trouble when that assumption is broken.
The specific example:
You currently (as of .NET 2.0) can’t unload the CLR once it’s loaded.
The managed exit process debug event (ICorDebugManagedCallback::ExitProcess) is overloaded to mean two things:
a) when the CLR is unloaded. This is important because it lets you know when can get rid of your ICorDebug instance. It also matches the semantics of the CreateProcess event.
b) AND when the actual process exits. This is important because once the process exits, things happen like the process handle is signaled, file locks are released (so you could recompiled the debuggee), etc.
Because you can’t unload the CLR, these are always the same. But let’s say we add the ability to unload the CLR. Now how should the managed ExitProcess event behave?
1. We could stick with A and fire the event when the CLR is unloaded. This is probably the “purest” solution, but it would break debuggers depending on B.
2. We could keep ExitProcess with B the same and add a new event like “ClrUnloaded” that fires at A. This is technically correct. However, it may require keeping ICorDebug loaded in the debugger once the CLR is no longer in the debuggee. This could become very cumbersome if a process loaded and unloaded runtimes in a loop (the debugger would get cluttered with a bunch of ICorDebugProcess instances waiting for the actual debuggee exit). We’d also need to provide an intelligent path for debugger authors to unload ICorDebug at the ClrUnloaded event.