VB6 BadImplementsRefInCompatLib and .NET-COM Interop

You've created a VB6 COM library, MyLib1 (1.0).
You then have two usage scenarios for MyLib1:
  1. MyLib1 is, in turn, used by another VB6 library, MyLib2

  2. MyLib1 is used to generate a COM Interop assembly which is consumed by a .NET assembly called MyDotNetLib, which itself is also a COM interop library.



It so happened that you needed to add some new public methods to MyLib1, which would bring its versioning to 1.1. You were careful to craft the change such that it's backward compatible. Yet, something happened--you don't yet know exactly what it was--and when to try to recompile the above two libraries, you get the following problem scenarios:

  1. Recompile MyLib2, you get this error: BadImplementsRefInCompatLib

  2. Recompile MyDotNetLib, you get:
    Error 63 The assembly "..MyDotNetLib.dll" could not be converted to a type library. Type library exporter encountered an error while processing 'MyDotNetLib.MyComponent, MyDotNetLib'. Error: Referenced type is defined in managed component, which is imported from a type library that could not be loaded (type: 'MyLib1._IComponent'; component: '...\Interop.MyLib1.dll')



What did you do wrong?
As it turns out, the "something" in "something happened" above was an IDE debug session that had previously crashed, and MyLib1.vbp (1.1) remained registered in the system.

Why did that cause the above problems to occur?
The IDE looks for the latest version of the library (same GUID) to bind to.

How to avoid it?
Well, you can't avoid it, any more than you can avoid making VB6 crash.

What to do when it happens?

  • Temporarily remove reference to MyLib1 from your MyLib2 project, then attempt to re-add the reference, looking through the list of registered libraries to see how many instances of MyLib1 are registered--there should be only one. If you see a MyLib1.vbp showing in your list, you'll need to go through the registry and manually remove all CLSIDs with InprocServer32="MyLib1.vbp". The automatic registry clean tools, that I've tried, couldn't remove these entries for me.

  • Retrace your changes to make sure that you haven't made any modification to public interfaces that could break binary compatibility.

  • Use the OLE/COM Object Viewer tool to view Type Library info of MyLib1.dll. Then compare the output with a previous version of the binary to see if any of the interface GUIDs got inadvertently changed.


---
Pre-emptive comment:
Yes, I know VB6 (the IDE) has reached its end-of-life, which means MS support for it will be scarce--all the more reason for writing this post.

NUnitForms rocks!

This NunitForms tool is really cool. Kudos to Luke Maxon and co. I'm running v2.0 alpha 5 and so far, my favourite feature has got to be ControlTester.FireEvent(). For instance, I can simulate the drag operation with something like this:


// initialize control tester for our Finder control
ControlTester myControl = new ControlTester("picFinder", "MyTestForm");

// simulate drag to coordinate (105,205)
myControl.FireEvent("MouseDown", (EventArgs)null);
Cursor.Position = new Point(105, 205); // drag mouse to here
myControl.FireEvent("MouseMove", (EventArgs)null);
myControl.FireEvent("MouseUp", (EventArgs)null);

// Assert MyTestForm GUI states
...


Pretty neat.

spamc.exe hangs

For the past several weeks, our ESA Sink has been clogging up once in a while. This is because spamc.exe, the SpamAssassin client program spawned by ESA, simply hung (probably due to a particularly large email).

I was thinking of rewriting spamc using Uwe Keim's ZetaSpamAssassin Wrapper, but that's probably a weekend type of project that I might entertain in the future. In the mean time, I wrote this simple little Windows service that occasionally checks and reaps stale spamc.exe processes, allowing the filter to continue. Source code (C#) and binary are available here.

Rhino.Mocks ExpectationViolationException

I'm trying out Rhino.Mocks in my NUnit test suites. Why Rhino.Mocks instead of NMock, you ask? Well, because I've heard that Rhino.Mocks can mock classes as well as interfaces.

Anyway, I was running into a little bit of a rookie's setback. I set up my mock objects like this:


1 const string TESTNAME = @"My Component Name";
2 MockRepository mocks = new MockRepository();
3 MyLib.MyTestClass tw = mocks.CreateMock<MyLib.MyTestClass>();
4 Expect.Call(tw.get_Name()).Return(TESTNAME);
5 mocks.ReplayAll();
// Test first invocation of get_Name()
6 Assert.AreEqual(tw.get_Name(), TESTNAME);
// Test second invocation of get_Name()
7 Assert.AreEqual(tw.get_Name(), TESTNAME);



I should probably add that MyLib.MyTestClass is a COM Interop class.

Executing line 7 resulted in this exception: Rhino.Mocks.Exceptions.ExpectationViolationException : _MyTestClass.get_Name(); Expected #1, Actual #2.

A little bit of googling did not immediately hint at the proper solution for me, but looking at the API documentation turned up this method: Repeat.AtLeastOnce(). So adjusting line 4 above to this did the trick:


4 Expect.Call(tw.get_Name()).Return(TESTNAME).Repeat.AtLeastOnce();

PMOG

Heard about this on CBC RadioOne's Spark this morning:
The Passively Multiplayer Online Game (PMOG):


Cool idea!