Bootstrapping a Package Loader

The folowing code is the content of an Independent Module used to load/unload a package. It also creates a menu item that changes between "Load Package" and "Unload Package" based on the existance of a global symbol when the module is compiled. This global symbol is created on package load and destroyed on package unload. The idea was to enable the automatic creation of the appropriate menu when a new experiment was created or when an old one was opened and the mechanics of the package loading/unloading could be completely invisible to the user, other than the menu item. The package loader is always loaded when Igor starts, via a shortcut in the Igor Procedures directory.

This works fine within an Igor session but does not work if Igor is shut down and an old experiment opened again. I thought that I had demonstrated to myself that this method of detecting whether the package was loaded or not worked when I devised it several months ago. The probem, as far as I can tell, is that the global symbol is not saved with the experiment. Is this the expected Igor behavior? Did I miss something in reading about symbols?

If the global symbol idea is not viable, what alternative methods are there? At the present time, the package does not require a package data folder and I wanted to avoid creating one just to remember the package load/unload state. This certainly could be done. A dummy procedure could be loaded as part of the package and that could be searched for to determine whether or not the package was loaded. Is it possible to determine whether user defined menus exist? I see there are several functions for working with built in menus, but haven't been able to find anything related to user defined menus. This might server as a means to detecte the package state. Any ideas or opinions?

System is Win7, Igor V6.22A

Thanks,

Jeff

#pragma rtGlobals=1     // Use modern global access method.
#pragma IndependentModule= AFM_PackageLoader

#ifndef AFM_PackageLoader
    Menu "jtgSpectroscopy"
        SubMenu "Packages..."  
            "Load AFM Package", /Q, LoadAFMPackage()
            help = {"Loads the set of procedures related to Atomic Force Microscopy."}
        End
    End
#else
    Menu "jtgSpectroscopy"
        SubMenu "Packages..."
            "Unload AFM Package", /Q, UnloadAFMPackage()
            help = {"Removes the set of procedures related to Atomic Force Microscopy."}
        End
    End
#endif
   
//package loader
//"SetIgorOption poundDefine=AFM_PackageLoader" defines a global symbol
//that the force curve analysis package uses to add a "remove package" menu item
Function LoadAFMPackage()
    Execute/P/Q/Z "INSERTINCLUDE \"AFM_ForceCurveAnalysis\""
    Execute/P/Q/Z "INSERTINCLUDE \"AFM_MultiFileLoader\""
    Execute "SetIgorOption poundDefine=AFM_PackageLoader"
    Execute/P/Q/Z "COMPILEPROCEDURES " 
End

//package unloader
//"SetIgorOption poundunDefine=AFM_PackageLoader" clears a global symbol
//that the force curve analysis package uses to add a remove package menu item
Function UnloadAFMPackage()
    Execute/P/Q/Z "DELETEINCLUDE \"AFM_ForceCurveAnalysis\""
    Execute/P/Q/Z "DELETEINCLUDE \"AFM_MultiFileLoader\""
    Execute "SetIgorOption poundunDefine=AFM_PackageLoader"
    Execute/P/Q/Z "COMPILEPROCEDURES "
End
I believe that global symbols cease to exist when Igor quits. That's how SetIgorOption generally behaves.

You might be able to use Exists. Here is an example that tests if a particular function in an independent module exists:
Function TestExists()
    String name = "WM_GrfBrowser#GetGrfBrowserGlobals"
    Print Exists(name)  // 6 means "function"
End


Or using a more modern function:
Function TestFunctionInfo()
    String functionName = "WM_GrfBrowser#GetGrfBrowserGlobals"
    String info = FunctionInfo(functionName)
    if (strlen(info) == 0)
        Print "Function not found"
    else
        Print info
    endif  
End

JJ -- Thanks for the info. This is my fall back position.

Howard -- Will your method work in a conditional compilation framework? As in

#if(strlen(FunctionInfo("Dummy", "AFM_PackageExists.ipf")) == 0)
    Menu "jtgSpectroscopy"
        SubMenu "Packages..."  
            "Load AFM Package", /Q, LoadAFMPackage()
            help = {"Loads the set of procedures related to Atomic Force Microscopy."}
        End
    End
#else
    Menu "jtgSpectroscopy"
        SubMenu "Packages..."
            "Unload AFM Package", /Q, UnloadAFMPackage()
            help = {"Removes the set of procedures related to Atomic Force Microscopy."}
        End
    End
#endif


So far my testing suggests not, but today IgorLevel(jtigor) < 0 evaluates as true.
jtigor wrote:
...So far my testing suggests not, but today IgorLevel(jtigor) < 0 evaluates as true.


I think such compile conditionals will not work for what you want to do because they may or may not be tested when an experiment is re-loaded. I went 'round and 'round with these same issues for my developments on PackageTools. In the end, I used globals and the "AfterCompileHook" function.

I'd be glad to talk more off-line about whether PackageTools could work for you.

--
J. J. Weimer
Chemistry / Chemical & Materials Engineering, UAHuntsville
NOTE: I updated this post after finding a behavior that I did not expect from FunctionInfo. I also tried using IndependentModuleList but it did not work. So I went back to FunctionInfo with a kludge relating to runtime errors. See the comments in the IsJTGSpectroscopyPackageLoaded function below for details.

jtigor wrote:
Howard -- Will your method work in a conditional compilation framework?


I'm not sure. But you need to know some things about FunctionInfo. See the comments in the IsJTGSpectroscopyPackageLoaded function below for details. You can try it using the IsJTGSpectroscopyPackageLoaded function.

However, it can be done without conditional compilation. Here is an example that you can paste into the procedure window of a new experiment and try. It uses the GraphBrowser package as a guinea pig.

NOTE: To try this you must first manually unload the GraphBrowser independent module as follows:
1. SetIgorOption IndependentModuleDev=1
2. Manually close GraphBrowser.ipf via the Windows->Procedure Windows menu.
You can now try it both with IndependentModuleDev=0 and IndependentModuleDev=1.

// This demonstrates using FunctionInfo to determine if an independent module
// is loaded or not. It uses the GraphBrowser module as a guinea pig.
// NOTE: To use this test you must first manually kill the GraphBrowser procedure file.

Function IsJTGSpectroscopyPackageLoaded()
    // If a runtime error has occurred, we can't procede because the code below
    // requires clearing the runtime error and we don't want to clear an error created
    // by other code.
    Variable err = GetRTError(0)   
    if (err != 0)
        return 0        // This may be incorrect
    endif

    // Use "YourModuleName#YourFunctionName".
    String functionName = "WM_GrfBrowser#GetGrfBrowserGlobals"
    String info = FunctionInfo(functionName)
   
    // FunctionInfo throws an error if the function does not exist. We don't want that so
    // here we suppress the error from FunctionInfo if the named function does not exist.
    err = GetRTError(1)
    if (err != 0)
        return 0        // Module not loaded
    endif

    // Print info       // For debugging only
    // FunctionInfo returns "Procedures Not Compiled" if the independent module is not loaded, even if procedures are compiled.
    // If the independent module IS loaded, it returns a long string.  

    if (strlen(info) > 100)
        return 1
    endif
   
    return 0
End

Function/S GetJTGLoadOrUnloadMenuItem()
    String menuItem
    if (IsJTGSpectroscopyPackageLoaded())
        menuItem = "Unload JTG Spectroscopy"
    else
        menuItem = "Load JTG Spectroscopy"
    endif
    return menuItem
End

Function LoadOrUnloadAFMPackage()
    // This is for a main file in Igor Procedures. For a main file in User Procedures, remove ":Igor Procedures:".
    String mainPackageFile = "\":Igor Procedures:GraphBrowser\""
    // String mainPackageFile = "\"MyMainPackageFile\"" // If file were in User Procedures 
   
    if (IsJTGSpectroscopyPackageLoaded())
        Execute /P /Q "DELETEINCLUDE "+mainPackageFile; Execute/P/Q/Z "COMPILEPROCEDURES "
        Printf "The package %s was unloaded\r", mainPackageFile
    else
        Execute /P /Q "INSERTINCLUDE "+mainPackageFile; Execute/P/Q/Z "COMPILEPROCEDURES "
        Printf "The package %s was loaded\r", mainPackageFile
    endif
End

Menu "jtgSpectroscopy", dynamic
    SubMenu "Packages..."
        GetJTGLoadOrUnloadMenuItem(), /Q, LoadOrUnloadAFMPackage()
        help = {"Loads or unloads the set of procedures related to Atomic Force Microscopy."}
    End
End


hrodstein wrote:
NOTE: I updated this post after finding a behavior that I did not expect from FunctionInfo. I also tried using IndependentModuleList but it did not work. So I went back to FunctionInfo with a kludge relating to runtime errors. See the comments in the IsJTGSpectroscopyPackageLoaded function below for details.


Is the problem that you found with IndependentModuleList that module names aren't purged from the list after they are unloaded? That has beem my finding as I continue to work on this. This is probably not the intended behavior?

JJ -- Thanks for the offer. I'll keep it in mind, but I am looking for a stand-alone solution that doesn't depend on another procedure.

Jeff
Quote:
Is the problem that you found with IndependentModuleList that module names aren't purged from the list after they are unloaded? That has been my finding as I continue to work on this. This is probably not the intended behavior?


Yes, that is what I found and no, it is not the intended behavior.

It should be fixed in the next nightly build. Look for a build dated 2012-05-05 which should be available Saturday morning, May 5, 2012.

With that version or later, this cleaner code should work:
// This demonstrates using FunctionInfo to determine if an independent module
// is loaded or not. It uses the GraphBrowser module as a guinea pig.
// NOTE: To use this test you must first manually kill the GraphBrowser procedure file.

Function IsJTGSpectroscopyPackageLoaded()
    // This requires Igor Pro 6.23B04 from 2012-05-05 or later 
    String moduleName = "WM_GrfBrowser"         // Your module name here
    String moduleList = IndependentModuleList(";")
    Variable whichItem = WhichListItem(moduleName, moduleList)
    if (whichItem < 0)
        return 0        // Module is not loaded
    endif
    return 1            // Module is loaded
End

Function/S GetJTGLoadOrUnloadMenuItem()
    String menuItem
    if (IsJTGSpectroscopyPackageLoaded())
        menuItem = "Unload JTG Spectroscopy"
    else
        menuItem = "Load JTG Spectroscopy"
    endif
    return menuItem
End

Function LoadOrUnloadAFMPackage()
    // This is for a main file in Igor Procedures. For a main file in User Procedures, remove ":Igor Procedures:".
    String mainPackageFile = "\":Igor Procedures:GraphBrowser\""
    // String mainPackageFile = "\"MyMainPackageFile\"" // If file were in User Procedures 
   
    if (IsJTGSpectroscopyPackageLoaded())
        Execute /P /Q "DELETEINCLUDE "+mainPackageFile; Execute/P/Q/Z "COMPILEPROCEDURES "
        Printf "The package %s was unloaded\r", mainPackageFile
    else
        Execute /P /Q "INSERTINCLUDE "+mainPackageFile; Execute/P/Q/Z "COMPILEPROCEDURES "
        Printf "The package %s was loaded\r", mainPackageFile
    endif
End

Menu "jtgSpectroscopy", dynamic
    SubMenu "Packages..."
        GetJTGLoadOrUnloadMenuItem(), /Q, LoadOrUnloadAFMPackage()
        help = {"Loads or unloads the set of procedures related to Atomic Force Microscopy."}
    End
End

hrodstein wrote:

It should be fixed in the next nightly build. Look for a build dated 2012-05-05 which should be available Saturday morning, May 5, 2012.

The nightly builds actually now run just before midnight, so you're looking for a build dated 2012-05-04 or later. If any of you eager beavers want to try this out on Saturday, go right ahead.
Howard,

Thanks for fixing the IndependentModuleList funtion and posting the dynamic menu solution. This is a much simpler approach than my conditional compilation method.

By the way, while working on this, I was also looking at the WinList function using the WIN:IndependentModule=1 option string. (So that the list of procedures includes those in independent modules.) This setting only shows independent modules if SetIgorOption independentModuleDev=1. My thought was that the option string should override the current SetIgorOption setting. Maybe some technical problem prevents this or mayby my interpretation is faulty. Just a thought.

Jeff
Quote:
By the way, while working on this, I was also looking at the WinList function using the WIN:IndependentModule=1 option string. (So that the list of procedures includes those in independent modules.) This setting only shows independent modules if SetIgorOption independentModuleDev=1. My thought was that the option string should override the current SetIgorOption setting.


I agree. We have changed it for the next minor release. The change is in the nightly build.