Do you Wiki? Word2MediaWiki.NET first release is live!

Snippy0001

I just wanted to let my faithful readers know that I have finally completed a beta version of the Word2MediaWiki.NET add-in for Microsoft Word 2003.

This’ll be useful to anyone that regularly contributes to Wikipedia, or those that have a bunch of Word documents that they’d like to share on Wikipedia.

It’s just as useful for any of the other hundreds of wikis that use the same server software as Wikipedia (known as MediaWiki), and it’ll be of interest to those folks who’ve used Word2MediaWikiPlus (from whose VBA source code I converted much of the basis of Word2MediaWiki.NET).

Grab a copy here:

http://www.codeplex.com/Word2MediaWikiDotNET

And please, let me know about any issues or experiences you have with it by posting a Discussion item here:

http://www.codeplex.com/Word2MediaWikiDotNET/Thread/List.aspx

ATOM, RSS & feeds – have YOU ever known which was the "right" one to choose??

What a friggin relief:

http://dev.live.com/blogs/devlive/archive/2008/02/27/213.aspx

“Microsoft is making a large investment in unifying our developer platform protocols for services on the open, standards-based Atom format (RFC 4287) and the Atom Publishing Protocol (RFC 5023).”

Finally I can try to stop worrying about which of the jillion “feed types” I should select to make sure that the feed subscriptions I’ve amassed are as “future-proof” as possible.  Good gravy, the number of times I’ve gone to subscribe to a certain feed, only to be faced with the choice among 2-6 different feeds (all apparently for the same set of articles) is just paralyzing:

  • Do I want the one with RSS in the final suffix?
  • Should I try to figure out which protocol is most popular/widest supported?
  • Do I need to try to figure out which one provides the most metadata with each downloaded article?
  • And which one might be sending the least data back to the author?  [This is ParanoidMike after all… wouldn’t want to disappoint my fans with a rare moment of rational thinking now]

I don’t normally like any one behemoth [aside: wasn’t that one of Godzilla’s opponent?] dictating to me a single format for anything, and I’m especially wary of any such edicts from Microsoft (having been privy to watching the sausage get made there for six years), but any time they make such an unequivocal commitment to an RFC standard and away from their “not built here” crap, I’m all in favour.

MindManager 7 ToDoList AddIn development Part 6: the Mysteries of COM interop

Sometimes there are inevitable mysteries uncovered when writing code.  Well, I’ve bumped into quite a rich source of mysteries in trying to use an aspect of the MindManager object model that thunks through a brittle COM interop module, known as CmjDocumentCollectionComObject.  Here’s just a couple of examples that have come up recently:

“unable to create document”

Here is the ‘offending’ code:

public static MMInterop.Document GetMap(string filename)
{
    MMInterop.Document toDoListMap;
    MMInterop.Documents maps;
    string toDoListMapFullPath = Connect.applicationObject.get_Path(MMInterop.MmDirectory.mmDirectoryMyMaps) + filename;
    maps = Connect.applicationObject.get_Documents(true);
    try // open a ToDoList map
    {
        toDoListMap = maps.Open(toDoListMapFullPath, String.Empty, true); // here's where the COMException is raised
    }
}

Here is the exception raised:

System.Runtime.InteropServices.COMException occurred
  Message=”Object ‘CmjDocumentCollectionComObject’ reports an error: ‘unable to create document'”
  Source=”MindManager.Application.7″
  ErrorCode=-2147220992
  StackTrace:
       at Mindjet.MindManager.Interop.DocumentsClass.Open(String pFileName, String pPassword, Boolean Visible)
       at ParanoidMike.MindManager.ToDoList.ToDoListMap.GetMap(String filename) in C:\personal\VS Projects\MM7TODOList\ToDoList.cs:line 187
  InnerException:

And do you want to know what that exception really means?  “The specified file does not exist” would be my interpretation.  If I understand the MindManager function maps.Open() correctly, this is meant to open an existing document.  I’ve just asked it to open a non-existent document, so I’d expect “can’t find it”, but I’m puzzled by “unable to create document”.  It’s like whoever wrote the error strings for the CmjDocumentCollectionComObject is interpreting the name of the Win32 CreateFile() API literally.

I am attempting to use a Try…Catch approach to testing whether the requested file exists. If the file exists, then maps.Open() would succeed; if it didn’t exist, then I’d use the Catch block to instead create the named file.  I didn’t expect to have to catch a System.Runtime.InteropServices.COMException, nor in figuring out how to catch only the exception with a specific ErrorCode/HRESULT returned by the COM object.

But then again, “Nobody expects the Spanish Inquisition”

{“Retrieving the COM class factory for component with CLSID {5B9EA9CE-76A3-4878-9A6B-22D0A3042774} failed due to the following error: 80040154.”}

Here’s the code:

MMInterop.Document toDoListMap;
try
{
    toDoListMap = new MMInterop.Document();
}
catch (System.Runtime.InteropServices.COMException e)
{
    throw;
}

Here’s the .NET exception that gets thrown:

{“Retrieving the COM class factory for component with CLSID {5B9EA9CE-76A3-4878-9A6B-22D0A3042774} failed due to the following error: 80040154.”}

And here’s the HRESULT that is being thrown by the COM object: -2147220992

In various posts, the common theme seems to be that the “component with the noted CLSID” needs to be re-registered.  Searching for this CLSID on my system (predictably) leads to Mindjet.MindManager.Interop, Version=7.0.323.0, Culture=neutral, PublicKeyToken=19247b5ea06b230f and Mindjet.MindManager.Interop, Version=7.1.388.0, Culture=neutral, PublicKeyToken=19247b5ea06b230f.  [However, there’s no ProgID registered for Mindjet.MindManager.Interop.  Is that bad — I know it’s not good for typical apps, but is a COM interop assembly a “typical” COM app?]

I remember reading somewhere that the MindManager 7 Primary Interop Assemblies were installed by default when installing MM7, so I thought perhaps and Add/Remove Programs “Repair” operation would suffice.

Unfortunately no — damned MindManager, I ran the full Repair, and it even reinstalled all the 41 default templates (so I know it did completely successfully), but my code is still throwing this same error.  So I went looking for the file path, which meant examining the Properties of the “MindManager” Reference, and it reports that this “ActiveX” file is stored here: C:\WINDOWS\assembly\GAC_MSIL\Mindjet.MindManager.Interop\7.1.388.0__19247b5ea06b230f\Mindjet.MindManager.Interop.dll.  Fire up a Command Prompt in that directory, run “REGSVR32.EXE Mindjet.MindManager.Interop.dll”, and I end up with this error:

image

REGASM.EXE

Here’s a riddle: how does one initialize a variable that cannot be initialized?

When I try to create a new mindmap document using a .NET AddIn, I’m finding that the variable I declare to hold a reference to the new mindmap is not usable: (a) it won’t work until it’s initialized, but (b) when it’s initialized it throws the error documented here:
http://tech.groups.yahoo.com/group/MindManagerDev/message/447

I’ve tried to dig up any hints that would help me figure out what to do to resolve this error, but so far nothing has worked (including trying to Register the Interop DLL, and Repairing the installation of MindManager).

There are plenty of people asking about this kind of issue — some related to straight COM objects, others talking about COM Interop assemblies:
http://www.thescripts.com/forum/thread731361.html
http://forums.cnet.com/5208-6141_102-0.html?forumID=8&threadID=216925&messageID=2313201
http://channel9.msdn.com/ShowPost.aspx?PostID=333247
http://forums.microsoft.com/MSDN/ShowPost.aspx?PostID=765439&SiteID=1
http://groups.google.com/group/microsoft.public.dotnet.framework.interop/browse_thread/thread/e8d8ccb58221f843/2263fa2b3db5895b

(1) I tried loading up the Interop DLL in DEPENDS.EXE, but there appear to be no missing dependencies.
(2) I tried registering the assembly using REGASM.EXE, according to the article here: http://www.simple-talk.com/dotnet/visual-studio/build-and-deploy-a-.net-com-assembly/
using the following command:

C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727>regasm C:\WINDOWS\assembly\GAC_MSIL\Mindjet.MindManager.Interop\7.1.388.0__19247b5ea06b230f\mindjet.mindmanager.interop.dll
Microsoft (R) .NET Framework Assembly Registration Utility 2.0.50727.1433
Copyright (C) Microsoft Corporation 1998-2004.  All rights reserved.

Types registered successfully

C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727>

Now I’m getting the same .NET exception, with a different HRESULT: -2147221164.  That HRESULT appears to be associated with needing to re-register Atl.dll, so I gave that a shot.  Unfortunately, it gave me the same error and the same HRESULT.  I also tried re-running the above REGASM.EXE command, with no change — still throwing the same exception.

Cheap Hack: avoid init issues with dummy calls

I’m almost embarrassed to even discuss how I’m getting around this intractable problem…but not embarrassed enough not to do it, so I might as well own up to it.  Who knows?  Maybe one of you will know what I’m overlooking.  Maybe I’ll find this article in six months’ time and remember why I put this hack in place.  Maybe this will just be my own personal legacy to the world. 🙂

Let’s recap: my design for this AddIn is to use a separate map to list copies of all Tasks found in the searched maps. If the named map already existed, just open it; if not, a new map should be created.  However, I kept getting blocked by not “initializing” the local variable to hold the new/opened Document handle before returning it to the calling function.  However, initializing the local variable with a “new MMInterop.Document()” call just caused other problems.

I was thinking that because Visual Studio was throwing the CS0165 error “Use of unassigned local variable ‘toDoListMap” on the final line of code “return toDoListMap”, and not on any of the preceding where the toDoListMap variable was being assigned, what Visual Studio might be reacting to is that toDoListMap wouldn’t have a non-null value on all code paths through the function.  I’d ignored this thought because (a) the code paths where it wouldn’t get a value were intentional, and (b) the documentation on CS0165 kept indicating that I needed to initialize the variable.

However, after exhausting all the “right way” options, I finally just dropped some initializations (assignments?) into the code paths where toDoListMap wasn’t previously being touched:

public static MMInterop.Document GetMap(string filename)
{
    MMInterop.Document toDoListMap;
    MMInterop.Documents maps;
    string toDoListMapFullPath = Connect.applicationObject.get_Path(MMInterop.MmDirectory.mmDirectoryMyMaps) + filename; // TODO: remove this hard-coded pathing to the ToDoList map
    maps = Connect.applicationObject.get_Documents(true); // assign/initialize the maps variable so the array can be searched by maps.Open().  "Connect" is the Class that implements a static variable "applicationObject" which is assigned to the "application" object that's captured during AddIn initialization.

    try // open the existing ToDoList map
    {
        toDoListMap = maps.Open(toDoListMapFullPath, String.Empty, false);
    }
    catch (System.Runtime.InteropServices.COMException e) // if a ToDoList map by that name does not exist, create one
    {
        if (e.ErrorCode == -2147220992) // "Object 'CmjDocumentCollectionComObject' reports an error: 'unable to create document'"
        {
            System.Windows.Forms.MessageBox.Show("Exception opening map: " + e.Message); // TODO: remove once initial debugging is complete
            string templateFullPath = Connect.applicationObject.get_Path(MMInterop.MmDirectory.mmDirectoryTemplates) + templateFileName; //construct reference to the template that's required when creating a new Document

            toDoListMap = maps.AddFromTemplate(templateFullPath, String.Empty, false);
        }
        else // this is never intended to be run - it's just here to convince the compiler that the toDoListMap variable has been initialized on all code paths, as it otherwise throws an exception "Use of unassigned local variable 'toDoListMap'.
        {
            System.Windows.Forms.MessageBox.Show("If you see this message, record the error and report it to the developer - this should never be seen: error code = " + e.ErrorCode.ToString());
            toDoListMap = new MMInterop.Document();
        }
    }
    catch (Exception e) // this is never intended to be run - it's just here to convince the compiler that the toDoListMap variable has been initialized on all code paths, as it otherwise throws an exception "Use of unassigned local variable 'toDoListMap'.
    {
        System.Windows.Forms.MessageBox.Show("If you see this message, record the error and report it to the developer - this should never be seen: error code = " + e.ErrorCode.ToString());
        toDoListMap = new MMInterop.Document();
    }

    return toDoListMap;
}

And now, miraculously this damned code compiles and runs correctly.  I really wish I had some better ideas than these “new” calls, ’cause I’ve got a feeling they’ll come back to bite me far down the line.  I’ve added some MessageBox “please let me know if you see this” dialogs, but that won’t really solve the problem — just make me look a little more like an amateur code-jockey.

[I’m still not sure why in this case, I was able to assign the toDoListMap variable without first initializing it — it’s not one of the simple datatypes, so I don’t think it’s being implicitly initialized by the compiler, and I thought I’d just re-learned that a complex Object always needs to be declared, initialized and then assigned.  Once again there’s something I don’t get about this, but I’ll leave that to dig up in the future, as yet another great surprise. :)]

MindManager 7 ToDoList AddIn development Part 5: minimizing Object overhead between classes

I’ve created quite a puzzle for myself.  In writing an AddIn that calls functions from custom Classes, I am forcing the AddIn to pass in all the data that would be needed in the custom code.  I’ve added a few helper functions to the ToDoListBuilder class including GetAllTasks(), IsTaskCompleted() and IsTopicATask().  These are easy because I’m passing simple variables into each function.

However, I haven’t yet wired up the primary function that will be called from the AddIn’s mmCommand_Click() event, and I haven’t decided what I need to pass down from the button_Click() to the cascade of encapsulated functions, nor how far down to pass any object like a baton.

It seems like it’d be correct to pass the MMInterop.Application object into the ToDoListBuilder code, but in passing the Application object to a GenerateToDoListItems() method, I’ll have to pass the Application object to at least one further layer of called functions such as GetAllToDoListMaps().  Or will I?

Now that I’m looking around, the Connect class has a private applicationObject variable.  While it seems unlikely I’d be able to make reference to that Class’ private variable from a called Class, there should be no reason why I couldn’t create another private variable in the ToDoListBuilder class as soon as the GenerateToDoListItems() method is called, and then call on that Class-wide variable from then on.

Reducing Memory Footprint of Application Object(s) without Passing References Everywhere

Here’s how the code around the Application object comes out from the Visual Studio AddIn Template wizard by default:

    public class Connect : Object, Extensibility.IDTExtensibility2
{
private Mindjet.MindManager.Interop.Application applicationObject; private Mindjet.MindManager.Interop.Command mmCommand;
... }

Changing the first declaration to public from private makes that applicationObject object available outside of the assembly as well as within it, so that pointers (references) to the object don’t have to be explicitly passed around the code.

Assuming that nothing can affect the state of the applicationObject, there should be no reason not to declare it as “public”.  I’m not entirely naive though — I assume there’s reasons why you’d want to do one and not the other, but I have to believe that any code running in the MindManager application should necessarily want/try to use a single application object anyway.

Solution: Static/Shared modifier

After bashing my skull into the wall of my ineptitude for a few days, I finally dislodged a bone fragment of useful info: the “static” modifier.  Somehow this does what I had expected the “public” keyword to accomplish — makes it possible to access the variable from outside the class, without having to instantiate another copy of the object.

Open Questions

Now I’m left wondering two things:

  1. What’s the lowest level of accessibility that the object needs?  Does it still need to be public, or would protected or less be acceptable?
  2. Under what circumstances would it be inadvisable to use the static modifier?  I can imagine that in theory, anything that is supposed to represent multiple objects should not be marked “static”, but that still leaves a ton of room for interpretation (and for subtle mistakes in code that will bite me only later).

Aside: Registry entries in Setup project Aren’t Automatically Installed

Idiot assumption of the day: just because I entered the correct Registry settings in the Setup project, doesn’t mean that when I Debug the Solution those settings will be automatically added to the computer’s Registry.  (Sigh, sometimes I surprise myself with how dense I can be.)

So which “hack” would be better — should I hand-enter the Registry settings I need (which seems pretty lame) or should I build the Setup project and actually install this AddIn (which might end up leaving behind stuff that I’ll need to rip out later after I’ve rev’d the AddIn a few times)?

I guess I’m going down the path of hand-entering the Registry settings.  I don’t like doing this, and I really wish debugging MindManager AddIns didn’t require this lame step, but it looks like I’ve got no better option.

NullReferenceException: When will I ever learn?

I can’t believe the number of times I get caught by this seemingly predictable error:

        private System.Collections.ArrayList toDoListItems; // building list of all Tasks to be emitted as the items for the ToDoList
        private Mindjet.MindManager.Interop.Application applicationObject; // local instance of the Application object

        public System.Collections.ArrayList GenerateToDoListItems(MMInterop.Application application)
        {
            System.Collections.ArrayList toDoListMaps; // collection of all maps to be searched for Task topics
            applicationObject = application; // sets the local variable equal to the value of the passed-in parameter
            toDoListMaps = GetAllToDoListMaps(); // generate the collection of maps to be enumerated and searched
            toDoListItems = new System.Collections.ArrayList(); // declare this to avoid a NullReferenceException when it's assigned below
            
            foreach (MMInterop.Document map in toDoListMaps)
            {
                    toDoListItems.AddRange(GetAllTasks(map));
            }
            return toDoListItems;
        }

I really don’t get why I don’t have to declare the “new toDoListMaps” object, but I have to declare the “new toDoListItems”.  Is there something about creating the object inside the method that implicitly initializes it, but this implicit initialization doesn’t occur for objects that are created outside of the method?

And why does creating an int object not require initialization, but creating an ArrayList object does?  This might make sense to veteran coders, but my god it’s confusing for those of us just trying to get their first few apps out the door…