Click or drag to resize

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 anotomy 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

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:

C++
auto status = VI_SUCCESS;
Caution note Caution

The above line of code must appear as-is in each IVI-C function or attribute implementation if the function or attribute is being implemented automatically or if it is being implemented manually but has opted to use the standard preamble by selecting the Use auto-generated preamble option in the Function General Editor or the Attribute General Editor. If the above line of code is modified, then Nimbus will not be able to roundtrip the function and the driver developer will need to manually implement all of the code in the function.

The code below illustrates how an IVI-C function implementation is divided into preamble and main body sections:

C++
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:

C++
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.

Tip Tip

For details on functions and macros appearing in the preamble code, see the topics in the Nimbus Runtime Library.

For further information on the VISA library functions that Nimbus generates for auto-implemented functions and attributes, see your VISA vendor's documentation.

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.

Important note Important

Nimbus always roundtrips the function signature (the line of code containing the function name and parameters) as you add, remove, rename and otherwise modify the function definition. Never manually modify the function declaration line, even when manually implementing a function or attribute.

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.

Caution note Caution

It is crucial to understand what Nimbus roundtrips when using the IVI-C items editors. When Nimbus is roundtripping the preamble (as is typically the case), then any custom code placed in the preamble section will be overwritten by Nimbus. Similarly, if a function is being fully automatically implemented (as with message-based drivers using VISA), then any custom code placed (nearly) anywhere in the function implementation will be overwritten by Nimbus. Note that there is an exception to this policy related to simulation, which is dicussed later in this topic.

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.

Note Note

Note that the roundtripping behavior for Manual and Manual with instrument command is identical. These two options differ only in how Nimbus generates help for the function or attribute. When Manual is selected, Nimbus generates no Instrument Command section in the help page for the function or attribute, which is appropriate when the function or attribute is not message-based. However, for functions and attributes that are manually implemented but ultimately still use an instrument command in the manually supplied code, Nimbus allows the driver developer to still specify the instrument command they are using so that it appears in the driver help pages.

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:

C++
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.

Important note Important

It is crucial that the simulation block be structured exactly as above -- specifically, with a single if-statement (including the curly braces) that calls the SimulationEnabled function. Any arbitrary logic may be placed within the simulation block, but the outermost structure must be exactly as generated by Nimbus.

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:

C++
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 in the maintained in the driver's Attributes.cpp file.

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:

C++
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:

C++
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.

C++
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.

Download a complete CHM version of this documentation here.