[Previous articles in this series: Prologue, Part 1, Part 2, Part 3, Part 4, Part 5, Part 6, Part 7, Part 8, Part 9, Part 10, Part 11 (The Return), Part 12 (Initialization continued), Part 13 (VBA Oddities).]
Mysterious Character: Vertical Tab (VT) — Do These Still Show Up in Word Documents?
In working through the code in MediaWikiConvert_Lists(), I ran across a block of code that purports to “replace manual page breaks”, and is using the Chr(11) construct to do so. I must’ve been feeling extra-curious ’cause I went digging into what this means, and the harder I looked, the more puzzled I became.
According to ASCIITables.com, the character represented by decimal “11” is the so-called “vertical tab”. I’ve never heard of this before (but then, there’s a whole host of ASCII & Unicode characters I’ve never paid attention to before), so I had to check with a half-dozen other references on the ‘net before I was sufficiently convinced that this wasn’t some “off-by-one” problem where the VBA coders were intending to look for Chr(10) (aka “line feed”) or Chr(12) (aka “form feed”).
On the assumption that we’re really and truly looking for “vertical tab”, I had to do some deep digging to figure out what this might actually represent in a Word document. There’s the obligatory Wikipedia entry, which only said that “The vertical tab is  but is not allowed in SGML (including HTML) or XML 1.0.”. Then I found this amusing reference to one of the Perl RFCs, which quotes Russ Allbery to say “The last time I used a vertical tab intentionally and for some productive purpose was about 1984.”. [Sometimes these quotes get better with age…]
OK, so if the vertical tab is so undesirable and irrelevant, what could our VBA predecessors be thinking? What is the intended purpose of looking for an ASCII character that is so unappreciated?
Mysterious Code Fragment: “If 1 = 2” – WTF?
I started to notice these odd little appendages growing out of some of the newer code in the VBA macro. At first I figured there must be some special property of VBA that makes “If 1=2” a valid statement under some circumstances, and I just had to ferret out what that was.
Instead, the more I looked at it, the more puzzled I became. What the hell could this possibly mean? Under what circumstances would *any* logical programming language ever treat “If 1 = 2” as anything but a comparison of two absolute numbers, that will ALWAYS evaluate to False?
Eventually I had to find out what greater minds that mine thought about this, and so off to Google I go. As you might expect, there’s not much direct evidence of any programming practices that include adding this “If 1 = 2” statement. In fact, though it appears in the odd piece of code here and there, it’s surprisingly infrequent. However, I finally ran across what I take to be the best lesson on what this really means (even if I had to unearth it through the infamous “Google cache”):
>>>Anyone know how to comment out a whole section in VBA rather than just
>>>line by line with a ” ‘ “?
>>If the code is acceptable (won’t break because some control doesn’t
>>exist, etc), I sometimes to
>> If 1 = 2 then
>> ….existing code
>> End If
>>The code will never fire until the day 1 = 2.
> Thanks, think Id prefer the first option. The second option might
> confuse any programmers that try and read my code.
Now that’s the understatement of the year.
So as far as I’m concerned, I’m going to go back and comment out any and all instances where I find this statement, as it tells me the original programmer didn’t want this code to fire, and was thinking of coming back to it someday after their last check-in.
Mysterious Approach: Localization via Macro? No way.
There are a few routines that attempt to implement localization at runtime. While this makes sense for VBA, this makes little if any sense for the use of VB.NET. Any English-only strings can be substituted in the corresponding Resources file that will accompany this code.
Thus, the MW_LanguageTexts() routine will be skipped, since it had little if any effect anyway.
Mysterious Exception: “add-in could not be found or could not be loaded”
I’ve been struggling for a few days to try to actually run this add-in, and after finding out why, I can say with confidence that there was no good troubleshooting guide for this.
Here’s the setup:
- I could Build the add-in just fine — no build-time errors, only two compiler warnings (about unused variables).
- However, when I tried to either (a) Debug the project from within Visual Studio, or (b) add the add-in manually to Word, I was completely stymied.
- When I started the Debug sequence (F5) from Visual Studio, it would launch Word 2003, which created all its default menus and toolbars, and then threw this error dialog:
- The details of this exception read:
Could not create an instance of startup object Word2MediaWiki__.ThisAddIn in assembly Word2MediaWikiPlusPlus, Version=126.96.36.199, Culture=neutral, PublicKeyToken=1a75eafd9e81be84.
************** Exception Text **************
Microsoft.VisualStudio.Tools.Applications.Runtime.CannotCreateStartupObjectException: Could not create an instance of startup object Word2MediaWiki__.ThisAddIn in assembly Word2MediaWikiPlusPlus, Version=188.8.131.52, Culture=neutral, PublicKeyToken=1a75eafd9e81be84. —> System.Reflection.TargetInvocationException: Exception has been thrown by the target of an invocation. —> System.NullReferenceException: Object reference not set to an instance of an object.
at Word2MediaWiki__.Word2MediaWikiPlusPlus.Convert..ctor() in C:\VS2005 Projects\Word2MediaWiki++\Word2MediaWiki++\Convert.vb:line 44
at Word2MediaWiki__.ThisAddIn..ctor(IRuntimeServiceProvider RuntimeCallback) in C:\VS2005 Projects\Word2MediaWiki++\Word2MediaWiki++\ThisAddIn.vb:line 29
— End of inner exception stack trace —
If I tried to load the add-in from within Word (using the Tools > COM Add-ins… menu — which you can add with these instructions), Word would only tell me:
Load Behavior: Not loaded. A runtime error occurred during the loading of the COM Add-in.
I won’t even bore you with the details of all the stuff I tried to do to debug this issue. It turned out that I was instantiating my Application object too early in the code (at least, the way I’d constructed it).
ThisAddin.vb (relevant chunk)
Imports Office = Microsoft.Office.Core Imports Word2MediaWiki__.Word2MediaWikiPlusPlus.Convert Public Class ThisAddIn #Region " Variables " Private W2MWPPBar As Office.CommandBar WithEvents uiConvert As Office.CommandBarButton WithEvents uiUpload As Office.CommandBarButton WithEvents uiConfig As Office.CommandBarButton Dim DocumentConversion As Word2MediaWikiPlusPlus.Convert = New Word2MediaWikiPlusPlus.Convert ' Line 29 #End Region
Convert.vb (relevant chunk)
Imports Word = Microsoft.Office.Interop.Word Namespace Word2MediaWikiPlusPlus Public Class Convert #Region "Variables"Dim App As Word.Application = Globals.ThisAddIn.Application 'PROBLEM - Line 44 Dim Doc As Word.Document = App.ActiveDocument 'PROBLEM#End Region#Region "Public Subs" Public Sub InitializeActiveDocument() If Doc Is Nothing Then Exit Sub End If
…End Sub#End Region
#Region “Public Subs”
Convert.vb (relevant chunk)
Imports Word = Microsoft.Office.Interop.Word Namespace Word2MediaWikiPlusPlus Public Class Convert #Region "Variables" Dim App As Word.Application 'FIXED Dim Doc As Word.Document 'FIXED #End Region #Region "Public Subs" Public Sub InitializeActiveDocument() App = Globals.ThisAddIn.Application 'NEW Doc = App.ActiveDocument 'NEW If Doc Is Nothing Then Exit Sub End If … End Sub #End Region
What I Think Went Wrong
As much as I understand of this, it seems like when the ThisAddIn class tries to create a new instance of the Convert class as a DocumentConversion object, the ThisAddIn object hasn’t been instantiated yet, so the reference in the Convert class to Globals.ThisAddIn.Application can’t be resolved (how can you get the ThisAddin.Application object if its parent object — ThisAddIn — doesn’t exist yet?) causes the NullReferenceException that is the heart of the problem.
By pulling out that instantiation code from the App variable declaration, and delaying it instead to one of the Convert class’s Subs, there was no need for the managed code to “chase its tail” — trying to resolve an object reference back through the calling code, which hadn’t been instantiated yet.
Y’know, I’m sure I read somewhere over the last year that combining the declaration with the instantiation of a variable is bound to lead to subtle debugging issues, but man. Losing three days to this? What a disaster.
Lesson for the day: It never pays to take shortcuts.