I got a note a couple weeks back from Andrew MacNeill that he had built a quick and dirty Html Help Workshop importer for Help Builder. He casually pointed out, that he was looking for the ‘add-in manager’ and well, there wasn’t one. <g>
So, I thought why not, he’s right. It sure would be nice if there was a standard way to create an add-in for Help Builder. Over the years people have built all sorts of custom importers and custom renderers for Help Builder, but very few ever got shared in any way. So an Add-in architecture might get more people to muck around with what’s available for extending Help Builder.
So, I created an Add-in interface that works with FoxPro (since Help Builder is a FoxPro app), but it also supports .NET and COM based add-ins. Since these days Help Builder sells mostly to .NET developers, having .NET support for add-ins is fairly important.
The add-in architecture is pretty simple actually. It consists of an Add-in manager that allows specifying of an add-in file that is the executable and a class and method that gets called in this add-in. The Add-in Manager demonstrates this functionality best:

Once installed there’s also an Add-ins… menu option that gives you direct access to the add-in outside of the manager.
All the add-ins types work the same way behind the scenes regardless of language used. The add-in developer basically creates a class with a target method. The target method must accept a single parameter that Help Builder passes to the method, which is a reference to the Help Builder IDE. This instance is a top level instance and it provides access to the Help Builder UI, the active project and the active topic.
************************************************************************
* AddinManager :: ActivateAddin
****************************************
*** Function: Activates an add-in based on it’s Add-in PK
*** Pass:
*** Return:
************************************************************************
FUNCTION ActivateAddin(lcPk)
LOCAL llError, loAddin
loAddIn = this.GetAddin(lcPk)
IF ISNULL(loAddin)
RETURN .F.
ENDIF
IF loAddin.Type # "COM" AND !FILE(loAddin.Filename)
this.SetError("Addin file does not exist: " + loAddin.Filename)
RETURN .F.
ENDIF
DO CASE
CASE loAddin.Type = "FOX"
RETURN this.ActivateFoxAddin(loAddin)
CASE loAddin.Type = ".NET"
RETURN this.ActivateNetAddin(loAddin)
CASE loAddin.Type = "COM"
RETURN THIS.ActivateCOMAddin(loAddin)
CASE loAddin.Type = "EXE"
ENDCASE
RETURN .T.
ENDFUNC
* AddinManager :: ActivateAddin
For FoxPro developers creating an add-in is as simple as creating a PRG file, creating a class and method that is called. Help Builder can compile the class on the fly and execute it. FoxPro Exes are also supported – in that case the EXE’s mainline is called first to load all classes methods or whatever is needed to run the Add-in. Then it instantiates the class and calls it. The process is actually fairly simple and demonstrates dynamic PRG execution in the case of PRG files.
************************************************************************
* AddinManager :: ActivateFoxAddin
****************************************
*** Function: Activates a VFP based add-in. Handles both PRG and EXEs
*** Assume:
*** Pass:
*** Return:
************************************************************************
PROTECTED FUNCTION ActivateFoxAddin(loAddin)
LOCAL loException, llError
llError = .f.
lcFileName = LOWER(TRIM(loAddin.FileName))
DO CASE
CASE JUSTEXT(lcFileName) = "prg"
llNeedToCompile = .T.
TRY
*** This catches both fxp file not existing and being out of date
*** Slightly more efficient than checking file, and the dates
llNeedToCompile = FDATE(lcFilename,1) > FDATE(FORCEEXT(lcFileName,"fxp"),1)
CATCH
llNeedToCompile = .t.
ENDTRY
IF llNeedToCompile
TRY
COMPILE (lcFileName)
CATCH TO loException
llError = .t.
ENDTRY
IF llError
this.SetError("Error compiling PRG file:" + CRLF +;
loException.Message)
RETURN .f.
ENDIF
ENDIF
TRY
loObject = NEWOBJECT(TRIM(loAddin.ObjName),lcFileName)
lcAddinMethod = "loObject." + TRIM(loAddIn.ObjMethod) + "(goHelp)"
llResult = EVALUATE( lcAddinMethod )
CATCH TO loException
llError = .t.
ENDTRY
IF llError
THIS.SetError("Error executing addin method: " + lcAddinMethod + CRLF +;
loException.Message)
RETURN .F.
ENDIF
RETURN .T.
CASE JUSTEXT(lcFileName) = "exe" OR JUSTEXT(lcFilename) = "app"
LOCAL lcAddinMethod
lcAddinMethod = ""
TRY
*** Load the libraries
DO (lcFileName)
loObject = NEWOBJECT(TRIM(loAddin.ObjName),lcFileName)
lcAddinMethod = "loObject." + TRIM(loAddIn.ObjMethod) + "(goHelp)"
llResult = EVALUATE( lcAddinMethod )
CATCH TO loException
llError = .t.
ENDTRY
IF llError
THIS.SetError("Error executing addin method: " + lcAddinMethod + CRLF +;
loException.Message)
RETURN .F.
ENDIF
RETURN .T.
ENDCASE
THIS.SetError("Unsupported file format for a FoxPro addin")
RETURN .F.
ENDFUNC
* AddinManager :: ActivateFoxAddin
Ok, the FoxPro stuff was really trivial, since HB is after all a Fox application so dynamically invoking other Fox code is pretty straight forward.
Onward to .NET
Doing the same for .NET was a fun exercise. The basics of the process are pretty easy to accomplish through COM interop. So let’s look at the basics required. The idea is that we need to execute code dynamically based on an assembly, and .NET type and method that the user provides in the Add-in manager.
To do this I need to use an intermediate .NET Interop Assembly that provides the ‘dynamic Invokation’ mechanism. Help Builder already has such an assembly – wwReflection.dll - which performs all the .NET type parsing for importing .NET types. So I ended up adding another object and method that handles the Add-in Execution. Here’s the Fox code that launches the process:
************************************************************************
* AddinManager :: ActivateNetAddin
****************************************
*** Function: activates a .NET addin.
*** Assume: requires wwReflection.dll in the current path!
*** and it must be registered.
*** Pass:
*** Return:
************************************************************************
FUNCTION ActivateNetAddin(loAddin)
lcFileName = LOWER(TRIM(loAddin.FileName))
*** Make sure COM object exists
IF !ISCOMOBJECT("wwReflection.AddinFactory")
lcError = ""
DotNetTypeParserCheck(lcError,.T.) && Force Registration
IF !ISCOMOBJECT("wwReflection.AddinFactory")
THIS.SetError("Unable to register Interop Assembly." + CHR(13) + CHR(10)+;
lcError)
RETURN .F.
ENDIF
ENDIF
LOCAL aiFactory as wwReflection.AddinFactory
LOCAL ai as wwReflection.AddinExecution
aiFactory = CREATEOBJECT("wwReflection.AddinFactory")
ai = aiFactory.CreateAddin()
Result = ai.Execute(TRIM(loAddin.Filename),TRIM(loAddin.ObjName),;
TRIM(loAddin.ObjMethod),goHelp)
IF !Result
this.SetError(ai.ErrorMessage)
aiFactory.UnloadAddin()
RETURN .f.
ENDIF
aiFactory.UnloadAddin()
RETURN .t.
ENDFUNC
* AddinManager :: ActivateNetAddin
The first thing that needs to happen is to make sure that the COM Interop assembly is registered. For more details on this check out my COM interop article that talks about how to do this in detail. In short, if the object is not registered it tries to do this from within the application, by finding RegAsm and executing it against the assembly to register it.
Once you know the COM Interop assembly is in place we can set out to actually create the Add-in. You notice that I’m creating a factory object here that acts as a proxy to the .NET object that you specified. I’ll come back to this in a minute. For now let’s look at the actual dynamic invocation code:
/// <summary>
/// This class manages executing a .NET method in a .NET assembly
/// </summary>
[ClassInterface(ClassInterfaceType.AutoDual)]
[ProgId("wwReflection.AddinExecution")]
public class AddinExecution : MarshalByRefObject
{
public string ErrorMessage = "";
public bool Error = false;
/// <summary>
/// The Actual worker method that dynamically invokes the assembly, type and method.
/// </summary>
public bool Execute(string AssemblyFile, string Typename,
string Method, object Parameter)
{
Assembly ass = null;
try
{
ass = Assembly.LoadFrom(AssemblyFile);
}
catch(Exception ex)
{
this.Error = true;
this.ErrorMessage = ex.Message;
return false;
}
Type TypeRef = null;
try
{
TypeRef = ass.GetType(Typename,true,true);
}
catch(Exception ex)
{
this.ErrorMessage = ex.Message;
return false;
}
try
{
object Instance = Activator.CreateInstance(TypeRef);
// *** Convert object into strongly typed object
wwHelpForm Form = new wwHelpForm(Parameter);
bool Result = (bool) Instance.GetType().InvokeMember(
Method,BindingFlags.InvokeMethod | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic,null,Instance,new object[1] { Form } );
return Result;
}
catch(Exception ex)
{
this.Error = true;
this.ErrorMessage = ex.Message;
return false;
}
return true;
}
}
This class consists of a single method that makes a pass-through call to the add-in assembly by using reflection to load the assembly. The process of dyamic invocation starts by loading the assembly, then retrieving the type and creating an instance of it. Finally a call is made on the type to call the method specified. Straight forward. From Fox the straight code looks like this:
ai = CREATEOBJECT("wwReflection.AddinExecution")
Result = ai.Execute(TRIM(loAddin.Filename),TRIM(loAddin.ObjName),;
TRIM(loAddin.ObjMethod),goHelp)
So far so good, but there’s a problem with this approach. If this method is called directly as is, it would cause the Add-In DLL to be loaded into the primary AppDomain and get locked there which probably is not a great idea. Why? Well, if you wanted to debug your add-in or compile it after you have loaded it into Help Builder, you wouldn’t be able to because Help Builder would have it locked. The problem is that once an assembly loads into an AppDomain it can NEVER be unloaded individually. You can only unload the AppDomain which releases the assembly with it.
Soooo… since I figure you want to debug the thing, the way to do this right is to load the Add-in a new AppDomain, make the call, then shut the AppDomain down. Notice that the .NET class is marked as MarshalByRefObject, which means this object can be activated via .NET Remoting which is required to execute code in another AppDomain. To do this I’m going to create the Assembly in another AppDomain and do the loading and calling in that AppDomain by using a Factory that creates the AppDomain and passes a back the created instance as Proxy via Remoting. As far as the calling code is concerned it works the same as before, but there’s a call to the factory to get the proxy instance first. The new code with AppDomain loading looks like this:
aiFactory = CREATEOBJECT("wwReflection.AddinFactory")
ai = aiFactory.CreateAddin()
ai.ExecuteAddin(…)
aiFactory.UnloadAddin()
The AddinFactory class is a small class who’s only task is create a new appdomain and create an instance of our class there and the return the pointer of this class to our calling code. Note that .NET automatically fixes up this remoting Proxy reference to be returned back to Visual FoxPro. When we receive this object in Fox we’re actually using Remoting from FoxPro!
[ClassInterface(ClassInterfaceType.AutoDual)]
[ProgId("wwReflection.AddinFactory")]
public class AddinFactory : MarshalByRefObject
{
/// <summary>
/// Reference to the AppDomain that the TypeParser
/// is loaded into.
/// </summary>
AppDomain LocalAppDomain = null;
/// <summary>
/// TypeParser Factory method that loads the TypeParser
/// object into a new AppDomain so it can be unloaded.
/// Creates AppDomain and creates type.
/// </summary>
/// <returns></returns>
public object CreateAddin()
{
if (!CreateAppDomain(null))