One technical aspect of creating the Measurement Utility that continues to prove to be challenging is building plugins in order to ensure they can be called correctly in the run-time engine (RTE). To be clear, I’m referring specifically to plugins that are loaded dynamically by a LabVIEW-built executable using a factory pattern. I thought I’d take a few minutes to document some of the lessons I’ve learned through this process.
Calling a plugin in the RTE requires that compiled copies of all linked dependencies are found when he plugin is loaded into memory. This is something that we all take for granted in the development environment, as any deeply nested libraries you use can typically be found beneath vi.lib, user.lib, etc…, which are included in the default search paths that the development environment uses. However, except for a core group of supporting libraries, the run-time engine does not contain copies of many of these libraries, requiring a set of processes for deployment that ensure their presence.
I expect and recommend that plugins be developed independently of the calling framework using agreed upon interfaces, which I would expect to be defined by a base class from which the plugins inherit. The framework should be built, managed and deployed using a separate Project than the measurements – both should have unique build specifications and potentially, unique installers.
After experimenting with different options and mechanisms, my general recommendation is to use source distributions as a mechanism to deploy plugins that will be loaded dynamically by a factory pattern. PPLs (packed project libraries) have their own set of uses cases, but are generally better suited for statically linked libraries that will need to be replaced or updated independently of the calling code. For dynamic dependencies I prefer source distributions as I have more explicit control over what they can include and how they are formatted and organized.
The following are recommendations (in no particular order) that are specific to the definition of plugin build specifications and the management of their dependencies:
1) Place the source files for your plugin inside a virtual folder – this makes it possible to apply settings to ‘all contained items,’ which cannot be done if a library or class is the highest-level item inside the Project (you then have to do it for each individualitem). When deployed, for example, you may want to disable debugging (which improves performance) and strip block diagrams (which protects against unauthorized modifications) – both settings can most easily be applied to the virtual folder that contains everything.
2) Identify which dependencies of a plugin should already be loaded by the calling executable versus the plugin. Parent classes of a plugin are a great example of something that is a dependency of the plugin, but should be loaded by the executable. As a result, we can exclude items are built into the calling framework. To simplify this, I recommend creating a virtual folder in the Project Explorer of a plugin specifically to contain the items to be excluded – if, as an example, you create a new plugin for the Measurement Utility from a template, you’ll notice that the Project it creates includes an ‘Exclude from Build’ folder containing all the libraries that are included in the framework EXE. This are all ideally located in an ‘installed’ directory in the development environment, such as user.lib. In the source distribution build spec, this folder is marked as ‘Exclude from Build.
3) Place everything you’ve developed for your specific plugin within a Project Library (or at least a class). This helps avoid namespace collisions that could occur as a result of user-developed dependencies for a plugin sharing a name with dependencies of another plugin. This can be especially problematic in the run-time, as the first copy of a dependency that gets called will be loaded into memory, so you can see plugins fail to load only in a scenario in which a conflicting items has already been loaded by another plugin. Again, you’ll see that that Measurement Utility plugin demonstrates the use of Project Libraries to illustrate this.
4) Remove un-used members of libraries AND modify the library file. As an example, almost every measurement is going to use some form of math, likely from the ‘Advanced Analysis Library’ in vi.lib, which is quite large. Plugins should be small and atomic, so they should not be transporting large libraries that can a lot of superfluous functionality. However, removing unused components introduces a new challenge: If library FOO contains functions A and B, and Plugin X uses A but not B, and Plugin Y uses B but not A, the build steps will create two new copies of the FOO library file, one which thinks it only contains A, and one which thinks it only contains B. As a result, whichever plugin is loaded first will define which functions can belong to it and if Plugin X is loaded first, Plugin Y will not be able to load correctly. To address this, see number
5) Apply a pre-fix to items within the ‘Dependencies.’ Everything you developed for a plugin should be within the library mentioned in number 3. Dependencies that the executable will load should’ve been excluded as noted in number 2. This leaves a number of dependencies that are neither excluded nor developed by the user for the specific behavior of the plugin – as mentioned in number 4, a great example would be the Advanced Analysis Library. Leave these items and their dependencies under the ‘Dependencies’ section of the project. In the Build Specifications for a Source Distribution, you can apply a prefix to anything contained under dependencies. Doing so avoids the scenario described in 4 by creating two unique namespaces for each instance of the library.
6) Do not place source files directly into the destination directory of the source distribution. Instead, the destination of the source distribution should be the name of the common folder that all plugins will inhabit, and a specific destination folder should be defined under the ‘Destinations’ category. The output of building the source distribution will be identical using either approach; however, it will make defining an installer easier to follow this recommendation. An installer replicates the hierarchy of a source distribution’s default destination directory in whichever folder it is told to use. If you have followed this recommendation, the installer prompt will just show \Measurements as the destination directory, but it will place any plugins it includes into appropriate sub-folders. Otherwise, you will need to manually create installer destinations for every plugin the installer contains.