Fundamentals of Function And Attribute Implementation
IVI-C driver functions and attributes are implemented in a single file named \<prefix\>.cpp. For message-based devices that use the VISA library for instrument communication, Nimbus can generate all of the necessary implementation code by associating instrument commands with a function or attribute.
Because many IVI-C drivers are not message-based or do not use the VISA library at all, Nimbus also allows the developer to manually implement each function and attribute. Even with manually implemented functions and attributes, Nimbus provides support for automatically generating required code for performing important (and, indeed, IVI-required) operations, such as thread safe locking, parameter validation, repeated capability name management and more. Having a solid understanding of the basic anatomy of IVI-C function and attribute implementation code and how the Nimbus editors interact with this code is vital to any kind of IVI-C driver development with Nimbus.
Anatomy of an IVI-C Function
Section titled “Anatomy of an IVI-C Function”An IVI-C function implementation in Nimbus is divided into two main sections — the preamble and the main body. The preamble code performs a variety of important tasks such as parameter validation, thread safety checks, state cache management, range checking, and simulation. The main body appears after the preamble and performs the core logic of the function, which is typically sending an instrument command or calling into a helper DLL. The preamble is separated from the main body via a standard line of code that Nimbus always inserts in a function or attribute implementation and that Nimbus relies upon for all of its roundtripping support. The separator line of code is the following:
auto status = VI_SUCCESS;The code below illustrates how an IVI-C function implementation is divided into preamble and main body sections:
ViStatus _VI_FUNC acme4321_Configure(ViSession Vi, ViReal64 Frequency, ViReal64 Bandwidth){ // // Everything here is part of the preamble //
auto status = VI_SUCCESS;
// // Everything here is part of the main body //}As an example, consider a complete function implementation, such as the following:
ViStatus _VI_FUNC acme4321_Configure(ViSession Vi, ViReal64 Frequency, ViReal64 Bandwidth){ ValidateSession(Vi); ObtainLock(Vi);
if (SimulationEnabled(Vi)) { return VI_SUCCESS; }
auto status = VI_SUCCESS;
status = viPrintf(GetVisaSession(Vi), "SENS:FREQ %0.15lg; SENS:BAND %0.15lg\n", Frequency, Bandwidth); ReturnOnError(status);
status = PollInstrumentErrors(Vi);
return status;}In the above example, the ValidateSession macro call, the ObtainLock macro call, and the if-statement for simulation are all part of the function preamble. The main body consists of the viPrintf call and the call to the PollInstrumentErrors function.
The contents of the preamble will vary depending upon the number and type of parameters, whether the function is associated with a repeated capability, and whether the implementation is a function, an attribute getter, or an attribute setter.
Roundtripping IVI-C Functions and Attributes
Section titled “Roundtripping IVI-C Functions and Attributes”The reason that Nimbus divides an IVI-C function or attribute implementation into preamble and main body sections is so that it can roundtrip those sections independently. For message-based devices that use the VISA library, Nimbus can implement the preamble code and the main body, so it roundtrips the entire contents of the implementation. For manually implemented functions and attributes (such as those that are either not message-based or that do not use VISA at all), Nimbus allows the driver developer to manually implement the main body but still have Nimbus generate and roundtrip the contents of the preamble. This is very useful because the preamble code is almost always appropriate — even for manually implemented functions. If, however, the driver developer wishes to have full control over the entire function or attribute implementation, then Nimbus supports this as well.
The roundtripping that Nimbus performs in IVI-C function and attribute implementation code is governed by two simple settings that appear on the Function General Editor and the Attribute General Editor. The table below explains what Nimbus roundtrips based on these settings.
| Implementation combo box | Use auto-generated preamble | Roundtripping policy |
|---|---|---|
| Instrument command | (always checked) | Entire function is roundtripped. Any code anywhere in the implementation (outside of the simulation block) is overwritten. |
| Manual Manual with instrument command | checked | Only the preamble code is roundtripped. Any code in the preamble section (outside of the simulation block) is overwritten. |
| Manual Manual with instrument command | unchecked | No roundtripping is performed on the function implementation. All code appearing in the implementation is fully preserved. Driver developer is fully responsible for performing the IVI-required preamble tasks, such as thread locking and parameter validation. |
Supporting Simulation
Section titled “Supporting Simulation”Simulation is often automatically implemented for IVI-C attributes. However, IVI-C functions must always supply manual code for simulation. Nimbus does not support automatically generating and roundtripping functions as it does for attributes. Nimbus inserts a stub implementation into a method with a TODO comment inserted and an error code being returned by default. This portion of the implementation appears in the preamble and is referred to as the simulation block and typically looks like the following:
if (SimulationEnabled(Vi)){ return IVIC_ERROR_FUNCTION_NOT_SUPPORTED; // TODO;}Since the preamble code is almost always roundtripped for manually implemented and automatically implemented functions and attributes as explained above, one might reasonably expect the simulation block to be automatically roundtripped as well. However, since the contents of the simulation block in IVI-C functions always need to be supplied by the driver developer, Nimbus does not roundtrip the contents of this simulation block. Driver developers can safely place their simulation logic within this if-statement and Nimbus will not overwrite any of the custom code.
Anatomy of Attribute Implementation Code
Section titled “Anatomy of Attribute Implementation Code”Client applications manipulate IVI-C driver attribute values using the standard IVI-defined attribute accessor functions, such as GetAttributeViInt32 and SetAttributeViReal64. IVI-C attributes themselves are simply #define’d macro values in the driver’s \<prefix\>.h file. The client application simply passes the macro value (referred to as the attribute ID) to the appropriate attribute accessor based on the attribute’s data type (e.g. ViBoolean, ViInt32, ViReal64, etc).
With such an architecture, one might expect that a Nimbus IVI-C driver would implement the logic for all ViInt32 attributes, for example, within the body of the GetAttributeViInt32 function and/or the SetAttributeViInt32 function. Any such implementation of these attribute accessors would be unduly complex because it would essentially require use of a very large switch statement where each case statement contained all of the logic for an entire attribute (preamble, custom code, simulation and anything else required).
Rather than implementing attribute getter and setter logic within each attribute accessor, Nimbus generates separate getter and setter functions for each attribute. This allows the logic for the reading the attribute to be placed in one function, while the logic for writing the attribute is isolated in a separate function. Both the getter and setter functions are maintained in the driver project’s \<prefix\>.cpp file alongside the IVI-C functions. Nimbus uses a specific naming convention for these getter and setter functions, as follows:
\<prefix\>get\<attribute ID\>
\<prefix\>set\<attribute ID\>
For example, for a TRIGGER_SOURCE attribute, the function definitions for the getter and setter would be as follows:
ViStatus _VI_FUNC acme4321_get_TRIGGER_SOURCE(ViSession Vi, ViConstString RepCapIdentifier, ViInt32* AttributeValue)
ViStatus _VI_FUNC acme4321_set_TRIGGER_SOURCE(ViSession Vi, ViConstString RepCapIdentifier, ViInt32 AttributeValue)These getter and setter functions are invoked by the Nimbus-supplied implementation of the various attribute accessors. Those default implementations rely upon entries maintained in the driver’s Attributes.cpp file.
Supporting Repeated Capabilities
Section titled “Supporting Repeated Capabilities”IVI-C drivers support parameter-style and selector-style repeated capabilities. When a function or attribute is associated with a repeated capability, the client application supplies a repeated capability selector to the function or attribute to indicate which physical names should be operated upon. These repeated capability selectors can contain virtual names, comma-separated lists of names, and ranges of names. This complicates the implementation of repeated capability-based functions and attributes because a single invocation might need to trigger multiple operations, such as multiple instrument commands being sent to the device. Because of the inherent complications in processing these repeated capability selectors, Nimbus provides special classes to process these selectors and generates the code necessary to perform repeated capability-based operations.
Consider the following ConfigureGlitchArmSource function which is associated with the ArmSource repeated capability:
ViStatus _VI_FUNC acme4321_ConfigureGlitchArmSource(ViSession Vi, ViConstString Source, ViReal64 Level, ViReal64 Width, ViInt32 Polarity, ViInt32 Condition){ ValidateSession(Vi); ObtainLock(Vi);
PhysicalNameList<::ArmSource> physicalNames; ReturnOnError(ArmSource::ExpandSelector(Vi, Source, physicalNames));
// ...}The first thing the function implementation must do, after validating the parameters and obtaining the thread lock, is to apply the IVI-defined rules for expanding a user-supplied repeated capability selector. As mentioned previously, the Source parameter in the function above might contain virtual names that need to be translated to physical names via the IVI Configuration Store. It may also contain lists and ranges of names. This Source parameter must be expanded according to the rules laid out in IVI 3.1, Section 4.4.7. The ExpandSelector function above does exactly that and it stores the resulting PhysicalName objects in a PhysicalNameList so that the main body can perform the necessary operations on each repeated capability name specified.
Functions that are automatically implemented using the VISA library would take the resulting PhysicalNameList and iterate through each contained PhysicalName to compose a unique instrument command upon each iteration. The code below demonstrates how such a function would be implemented by Nimbus:
ViStatus _VI_FUNC acme4321_ConfigureGlitchArmSource(ViSession Vi, ViConstString Source, ViReal64 Level, ViReal64 Width, ViInt32 Polarity, ViInt32 Condition){ ValidateSession(Vi); ObtainLock(Vi);
PhysicalNameList<::ArmSource> physicalNames; ReturnOnError(ArmSource::ExpandSelector(Vi, Source, physicalNames));
if (SimulationEnabled(Vi)) { return VI_SUCCESS; }
auto status = VI_SUCCESS;
for (const auto& physicalName : physicalNames) { status = viPrintf(GetVisaSession(Vi), "CONFIG:GLITCH:SOUR:%s\n", physicalName.Name()); ReturnOnError(status); }
status = PollInstrumentErrors(Vi);
return status;}The processing for attribute setters is identical to the above. However, for attribute getters and for functions that have an output parameter, it is not valid to specify a repeated capability selector with more than one physical name. Read operations can only return a single result, so the Nimbus-generated preamble code uses the ExpandSingleSelector function to perform the virtual-to-physical name translation and to enforce the use of only a single physical name.
The code below shows how the getter for an ARM_COUPLING attribute would be implemented to ensure only single physical names were used. The code for a function with an output parameter would be structured similarly in its use of the ExpandSingleSelector function.
ViStatus _VI_FUNC acme4321_get_ARM_COUPLING(ViSession Vi, ViConstString RepCapIdentifier, ViInt32* AttributeValue){ ValidateSession(Vi); ObtainLock(Vi); CheckForNull(Vi, AttributeValue);
PhysicalName<ArmSource> physicalName; ReturnOnError(ArmSource::ExpandSingleSelector(Vi, RepCapIdentifier, physicalName));
// ... same code as non-repeated-capabilty-based attribute}In the code above, the ExpandSingleSelector function will return an error if the RepCapIdentifier parameter represents more than one physical name. If it succeeds, then the result is stored in a single PhysicalName object (as opposed to a list of PhysicalName objects, as was the case for attribute setters and functions with no output parameters). Since only a single physical name can be specified, the remainder of the implementation code is very much the same as in the non-repeated-capability case.