• Registering a Visual Studio .Net AddIn without an Installer


    Registering a Visual Studio .Net Add-In without an Installer

    I’ve recently created an Add-In for Help Builder for Visual Studio .Net. The Add-In is a connector between the VS.Net environment and my un-managed Help Builder application that is basically standalone Windows application written in a non-CLR language. The Add-In interfaces with Help Builder through a simple but effective COM interface that drives both the Help Builder engine and the user interface.

     

    The Visual Studio .Net integration is only a small part of Help Builder, although I’ve found myself using it extensively since I added it myself with the ability to round trip documentation between source code and Help Builder and to easily stuff Help Context Ids (HelpString Topic values really) into controls at design time in the form editor. It all works great.

     

    But when it comes to installation, as always Microsoft provides you a pretty canned solution using the ever so nasty Microsoft Installer technology. Besides the fact that installing Add-Ins this way has been prone with problems (paths and versions being hardcoded) and having little control over the process, in this case the issue is simply that the main application doesn’t use the Microsoft Installer. There are lots of reason I went this route not the least of which was an installation that ends up about half the size of MS Installer installs.

     

    The bottom line is this: I can’t use an MSI package to install the Add-In. So, in order to install the Add-In I decided it should be an option that is enablable  (<g>) from within the Help Builder IDE via the Configuration options available. Click a checkbox and Help Builder installs itself as an Add-In in VS.Net 2003. Sounds easy, but what’s really involved in this? It turns out there are three main things that are required:

     

    • Registering the COM component as COM Interop Assembly (RegAsm)
    • Creating the Add-In registry keys
    • Creating a locale specific resource directory and copying any satellite assemblies there

     

     

    Registering the Add-In for COM Interop

    Add-Ins in VS.Net are primarily COM components, so the Add-In I created in C# is compiled into .Net Assembly DLL which then must be registered for COM interop. To do this one has to run RegAsm.exe from the Framework directory with the /CodeBase extension to register the Assembly as an Interop Assembly with a fixed Codebase location on the disk (meaning the path is written into the registry – the other alternative would be GAC installation).

     

    Help Builder is written in Visual FoxPro, so the code below is the high level registration routine that shows how to do this:

     

    ************************************************************************

    * RegisterHelpBuilderAddin

    ****************************************

    FUNCTION  RegisterHelpBuilderAddin(lcError,llUnregister) as Boolean

    LOCAL lcFrameworkPath, lcVersion

     

    IF !llUnregister AND ISCOMOBJECT("HelpBuilder.vsAddin")

       lcError = ""

       RETURN .T.

    ENDIF

     

    *** Try to register

    lcFrameworkPath = ""

    lcVersion = ""

     

    IF !IsDotNet(@lcFrameworkPath)

       lcError = "DotNet Framework not installed or path not found."

       RETURN .F.

    ENDIF

     

    lcRun = ShortPath(ADDBS(lcFrameworkPath) + "regasm.exe")

    IF EMPTY(lcRun)  && File doesn't exist

       lcError = "Couldn't find RegAsm.exe at:" + CHR(13) +;

               lcFrameworkPath + "regasm.exe"

       RETURN .F.

    ENDIF

     

    lcRun = lcRun +;

            [ "] +  FULLPATH("vsAddin\HelpBuilderVsAddin.dll") + ;

            IIF(llUnregister,[" -unregister],[" /codebase])

    _cliptext = lcRun

     

    WAIT WINDOW "Hang on. Trying to register HelpBuilderVsAddin.dll..." + CHR(13) +;

                "This may take a few seconds..." NOWAIT

     

    TRY

    RUN  &lcRun

    CATCH

    ENDTRY

     

    WAIT CLEAR

     

    IF llUnRegister

       RETURN .T.

    ENDIF

     

    llResult = IsComObject("HelpBuilder.vsAddin")

    IF !llResult

       lcError = "Registration of the Addin failed." + CHR(13) + CHR(13)+ ;

                "Command Line:" + CHR(13) + ;

                "RUN " + lcRun + CHR(13) + CHR(13) +;

                "Full deduced RegAsm Path:" + CHR(13) + ;

                lcFrameworkPath + "regasm.exe" + CHR(13) + CHR(13) +;

                "You can manually register HelpBuilderVsAddIn.dll by running REGASM.EXE" + CHR(13) + ;

                 "from the framework BIN directory with the following command line: " + CHR(13)+;

                 "<.Net framework bin path>\RegAsm /codebase HelpBuilderVsAddin.dll" + CHR(13) + CHR(13) + ;

                 "The command line to register the component has been pasted into your ClipBoard"

               

    ENDIF

     

    RETURN llResult

     

    There are a couple of helper routines – IsComObject and IsDotNet – that check to see if the COM object is already registered (in which case we don’t have to re-register) and if not if the .Net Framework is actually installed:

     

    ************************************************************************

    * wwUtils :: IsDotNet

    ****************************************

    ***  Function: Returns whether .Net is installed

    ***            Optionally returns the framework path and version

    ***            of the highest installed version.

    ************************************************************************

    FUNCTION IsDotNet(lcFrameworkPath,lcVersion)

    LOCAL loAPI as wwAPI

     

    lcVersion = ""

    lcFrameworkPath = ""

     

    loAPI = CREATEOBJECT("wwAPI")

    lcWinDir = loAPI.getSystemdir(.t.)

    lcVersion = loAPI.Readregistrystring(HKEY_LOCAL_MACHINE,"Software\Microsoft\ASP.Net","RootVer")

    IF ISNULL(lcFrameworkpath)

       RETURN .F.

    ENDIF

     

    lnAt = AT(".",lcVersion,3)

    IF lnAt > 0

       lcVersion = SUBSTR(lcVersion,1,lnAt) + "0"

    ENDIF

     

    lcFrameworkPath = loAPI.Readregistrystring(HKEY_LOCAL_MACHINE,"Software\Microsoft\ASP.Net\"+lcVersion,"PATH")

    IF ISNULL(lcFrameworkPath)

       lcFrameworkPath = ""

    ELSE

       lcFrameworkPath = ADDBS(lcFrameworkPath)

    ENDIF  

     

    RETURN .T.

    ENDFUNC

    *  wwUtils :: IsDotNet

     

    Note that this routing also returns the path to the Framework directory which is required in order to be able to call RegAsm.exe directly from the application. The check for the framework is pretty important – we don’t want to allow registration of the component if .Net is not installed. In fact, the startup code of the Config form checks for this first and disables the checkbox if .Net is not installed in the first place.

     

    Registering the Add-In in the Registry

    VS.Net Add-Ins are registered in VS through the registry. Specifically through the following key:

     

    HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\VisualStudio\7.1\AddIns

     

    As you can see keys are version specific – Version 7.1 is Visual Studio 2003 in particular. Underneath this key sit all registered Add-Ins with their ProgId (HelpBuilder.vsAddIn for example) as the key name. Underneath this key are a few key settings that determine how the Add-In is displayed in VS.Net (Name, Description, Icon) how it loads (LoadBehavior) and so on.

     

    Registering this information is pretty straight forward using standard registry tools, but because the information might change in the future in this case I decided to use a .Reg file with a template to make it easy to modify the content external to the application in the future. I exported the Reg file after I had initially installed the Add-In in VS.Net and modified it slightly. The problem is that VS.Net creates a Reg file for you when you originally create your Add-In project, but it doesn’t update it after you change settings or worse change the name of your add-in. As most .Net Wizards it’s a one way tool (as is the installer script that’s generated BTW which is yet one more reason I didn’t want to use the built in installer project).

     

    The Reg file I used looks like this:

     

    Windows Registry Editor Version 5.00

     

    [HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\VisualStudio\7.1\AddIns\HelpBuilder.vsAddin]

    "FriendlyName"="Help Builder Addin"

    "SatelliteDllPath"="<% lcSatelliteDllPath %>"

    "Description"="Help Builder Addin"

    "AboutBoxIcon"=hex:00,00,01,00,02,00,20,20,10,00,00,00,00,00,e8,02,00,00,26,00,\

     

      00,00,03,00,00,10,03,00,00,f0,03,00,00,f0,03,00,00,ff,7f,00,00,fe,3f,00,00,\

      fe,3f,00,00,fe,3f,00,00

    "SatelliteDllName"="hbres.dll"

    "CommandLineSafe"=dword:00000001

    "AboutBoxDetails"="For more information about West Wind Technologies, see the West Wind Technologies web site at http://www.west-wind.com/wwhelp/."

    "LoadBehavior"=dword:00000007

    "CommandPreload"=dword:00000001

     

    Notice that I needed to customize the SatelliteDllPath. This path is used for any external resources that the Add-In needs – in my case some custom icons for the Command Bars. This folder must point at the Add-In directory under which then there must be a locale specific folder with the actual DLL. More on this in the next section.

     

    The following code demonstrates reading the Reg script and merging it into the registry,  along with the high level code that performs the COM/COM Interop registration:

     

    ************************************************************************

    * wwHelp_Routines :: InstallVsAddin

    ****************************************

    FUNCTION InstallVsAddin(llUninstall)

     

    *** Read in the .Reg File and expand the Satellite DLL path into it

    lcAddInPath = SYS(5) + CURDIR() + "vsAddin\"

    lcRegFile = lcAddInPath + [vsaddin.reg]

    lcSatelliteDllPath = LOWER(STRTRAN( lcAddinPath,"\","\\" ))

    lcContent = FILETOSTR(lcRegFile)

    lcContent = TEXTMERGE(lcContent,.f.,"<%","%>")

     

    IF llUninstall

       *** Prefix reg key with a minus sign

       lcContent = STRTRAN(lcContent,"HKEY","-HKEY")

       lcError = ""

       RegisterHelpBuilderAddin(@lcError,.T.)

    ELSE

       DECLARE INTEGER GetLocaleInfo IN WIN32API ;

          INTEGER, INTEGER,STRING@,INTEGER @

      

       lcLocaleId = SPACE(4)

       GetLocaleInfo(0,1,@lcLocaleid,4)

       lnLocale = EVAL("0x" + lcLocaleId)

         

       *** Must copy the Satellite Resource DLL into locale specific dir

       IF lnLocale != 1033  && 1033 is default and pre-installed

          IF !ISDIR(JUSTPATH(lcRegFile) + "\" + TRANSFORM(lnLocale) )

             MD (JUSTPATH(lcRegFile) + "\" + TRANSFORM(lnLocale) )

          ENDIF

          try

             COPY FILE (lcAddinPath + "1033\hbres.dll") TO ;

                       (lcAddinPath + TRANSFORM(lnLocale) + "\hbres.dll")

          CATCH

             *** Ignore error

             MESSAGEBOX("Couldn't copy the icon resources." + CHR(13) + CHR(13) +;

                        "The add-in will work without them, but it's recommended" + CHR(13) +;

                        "that you shut down VS.Net and retry setting this option.",;

                        0 + 48,WWHELP_APPNAME)

          ENDTRY

       ENDIF

      

       *** Register the DLL for COM interop

       lcError = ""

       IF !RegisterHelpbuilderAddin(@lcError)

          MESSAGEBOX("Error registering the Add-in" + CHR(13) + CHR(13) +;

                     lcError,48,WWHELP_APPNAME)

       ENDIF

    ENDIF

     

    STRTOFILE(lcContent,lcRegFile+".install")

     

    lcRun =  [regedit /s "] + lcRegFile + [.install"]

    RUN /N2 &lcRun

     

    ENDFUNC

    *  wwHelp :: InstallVsAddin

     

    This code acts as the high level Add-In installer routine. It deals with opening the .Reg file, updating the script path, copying the SatelliteDll into the appropriate locale directory and then updating a temporary RegFile with the new DLL path. It then runs RegEdit against this Reg file to merge it into the registry (there were problems using just the .Reg extendended file with ShellExecute).

     

    This code also allows uninstallation. It does this by using the same .Reg file and changing the installation option of the key to install to an uninstall by using the – prefix. The following line accomplishes this:

     

     lcContent = STRTRAN(lcContent,"HKEY","-HKEY")

     

    which effectively uninstalls the key in question.

     

     

    Installing theSatellite Dlls

    Satellite DLLs are used to hold external resources – in my case I needed a couple of icons for the CommandBar menus. These resources must be contained in an external DLL. There’s no easy way to create these resources in a .Net based project, but it’s fairly easy to do using a C++ project and by adding a Resource to it, then adding the images or other resources. You simply add the resources to the Resource View and compile into a plain jane Windows DLL. Your Resource IDs will be the ids you use inside of the Add-In (for example for a CommandBar ID).

     

    Installing these resource DLLs is also tricky because according to the docs at least you need to install them into a locale specific directory. This means for the US the resource DLL must go into the HelpBuilder/vsAddIn/1033 directory with StatelliteDllPath pointing at the vsAddIn directory. If I install with a German Locale I need to install to a 1031 directory and so on. Note that SatelliteDllPath points at the base directory, but the DLL actually doesn’t live in that path, but instead in one of the locale specific paths.

     

    The install code needs to therefore be aware which locale it’s installing itself for and create and copy the DLL into the appropriate directory. By default I install a 1033 directory for US, and during installation check the Locale. If the Locale is other than 1033 I create a new directory and copy the resource from the 1033 directory into it.

     

    This is messy and seems like a real hack – especially for a DLL that has nothing that really is locale specific – but it works effectively.

     

     

    All of this ended up being a lot more work than I anticipated, but I’m happy with the way this has turned out. In addition to providing a clean interface to my application, this interface is also nice to quickly enable and disable the plug in for debug sessions. I can disable, start VS.Net with my Add-In project, then re-enable once the project is start to start debugging my test project using the add-in…

  • 相关阅读:
    boost.property_tree的高级用法(你们没见过的操作)
    MFC- OnIdle空闲处理
    华为代码质量军规 (1) 数组访问,必须进行越界保护
    WinSocket 编程
    【C/C++】链表的理解与使用
    单链表
    C++ lambda表达式 (二)
    C++ lambda表达式 (一)
    C++11 volatile 类型
    关于结构体内存对齐方式的总结(#pragma pack()和alignas())
  • 原文地址:https://www.cnblogs.com/yuxiang9999/p/207741.html
Copyright © 2020-2023  润新知