VxWorks Static Libraries: Packaging Drivers as .a and .o Files
When developing drivers or applications for VxWorks, there are two common software-delivery models.
The first is source-based development, where the complete source tree is provided to the development environment. Project configuration, compilation, linking, and VxWorks image generation are all performed directly from source code.
The second is library-based development, where implementation code is delivered as precompiled object files or static libraries. This approach is common when software is distributed as a commercial component or when source code must remain private for intellectual-property protection.
For VxWorks driver development, packaging implementation code into a .a static library or .o object file provides a straightforward way to distribute reusable functionality without exposing the underlying source.
This article demonstrates the basic workflow using Tornado 2.2, including project configuration, header-file design, library linking, and Makefile integration.
๐งฉ Source-Based vs. Library-Based VxWorks Development #
The fundamental difference is where compilation takes place.
Source-based development #
In a conventional source-based project, the developer provides:
- C or C++ source files
- Header files
- Build configuration
- BSP configuration
- Compiler and linker settings
The development environment compiles the source files and links the resulting objects into the final application or VxWorks system image.
The general flow is:
Source โ Compilation โ Object Files โ Linking โ Application/System Image
This model is convenient during active development because implementation code can be modified and rebuilt immediately.
Library-based development #
With library-based development, the implementation is compiled before delivery.
The consumer receives:
- Header files
.astatic libraries.oobject files, when required- Required build configuration
- API documentation
The consumer then compiles application code against the exposed interfaces and links the precompiled implementation into the final image.
The flow becomes:
Private Source โ Compilation โ .a / .o โ Consumer Project โ Final Image
This approach is useful when a driver needs to be reused across multiple projects while keeping the implementation source code private.
๐๏ธ Creating a VxWorks Static Library #
One of the simplest approaches is to create a new downloadable application project and add the driver source files to that project.
Using Tornado 2.2 as an example, the project management tree provides three primary views:
- Files
- Builds
- VxWorks
The Builds configuration determines how the project is compiled and what type of output is generated.
Selecting the output format #
- Open the project management tree.
- Select the Builds tab.
- Right-click the active build mode.
- Select Properties.
- Open the Rules tab.
- Select the desired output format.
Two relevant output types are available:
| Output Format | File Type | Typical Use |
|---|---|---|
| Archive | .a |
Static library containing multiple object modules |
| Object | .o |
Individual precompiled object module |
For a driver containing multiple source files, Archive is generally the more convenient choice because the resulting .a file packages the compiled object modules into a single library.
๐ฆ Generating the .a Static Library
#
For the typical driver-library workflow, select:
Builds โ Properties โ Rules โ Archive
After the project is compiled, Tornado generates the static library.
The resulting library filename normally follows the project name.
For example, if the project is named:
cpciDriver
the generated archive will typically be:
cpciDriver.a
The library contains the compiled implementation while allowing the consumer project to access only the interfaces exposed through the corresponding header files.
This provides a clean separation between the driver’s public API and its private implementation.
๐ Linking .a and .o Files into Another VxWorks Project
#
Once the library has been generated, it can be linked into the target project that uses the driver.
The exact configuration depends on the type of VxWorks project being built.
Standard application project #
For a standard application project:
- Open Builds โ Properties.
- Select the Macros tab.
- Locate:
PRJ_LIBS
- Add the full path to the required
.aor.ofile. - Save the project configuration.
- Rebuild the application.
The resulting build will link the precompiled driver implementation into the application.
VxWorks system image project #
When building a VxWorks system image project, the corresponding macro is:
EXTRA_MODULES
The general workflow is:
- Open Builds โ Properties.
- Select Macros.
- Locate
EXTRA_MODULES. - Add the full path to the required
.aor.ofile. - Rebuild the VxWorks image.
This allows the precompiled driver code to become part of the resulting system image.
๐ Creating the Public Header File #
Packaging a driver into a library requires a header file containing the interfaces that the consumer application is allowed to use.
The header should expose the driver’s public data structures, constants, and function declarations without exposing unnecessary implementation details.
For example:
#ifndef __CPCIDRIVER_H__
#define __CPCIDRIVER_H__
#ifdef __cplusplus
extern "C" {
#endif
typedef struct
{
DEV_HDR devHdr; /* Required */
} CPCI16IO_DEV;
/* Other public definitions */
IMPORT STATUS cpci32ioInit(void);
IMPORT STATUS cpci32ioDrv(void);
IMPORT STATUS cpci32ioDevCreate(char *devName);
#ifdef __cplusplus
}
#endif
#endif /* __CPCIDRIVER_H__ */
The header becomes the interface contract between the application and the precompiled driver.
The implementation remains inside the .a library or .o object file.
๐ VxWorks Driver Entry Points #
The example exposes three driver-related functions:
IMPORT STATUS cpci32ioInit(void);
IMPORT STATUS cpci32ioDrv(void);
IMPORT STATUS cpci32ioDevCreate(char *devName);
These declarations allow application code to call the corresponding functions without requiring access to their implementation.
The exact driver initialization sequence depends on the driver architecture, but a typical VxWorks device-driver design separates responsibilities between:
Driver Initialization
โ
Driver Installation
โ
Device Creation
โ
Application Access
For example, the application might first initialize the driver:
cpci32ioInit();
followed by driver registration:
cpci32ioDrv();
and then create a device instance:
cpci32ioDevCreate("/cpci0");
The actual function names and initialization order should follow the driver’s implementation and device model.
๐งฑ Why DEV_HDR Matters
#
The example structure contains:
typedef struct
{
DEV_HDR devHdr;
} CPCI16IO_DEV;
The DEV_HDR member is important when implementing a VxWorks device driver using the traditional VxWorks I/O subsystem.
A device structure commonly embeds DEV_HDR so that the VxWorks I/O system can associate the device instance with the appropriate driver infrastructure.
Additional driver-specific state can then be appended to the structure.
For example:
typedef struct
{
DEV_HDR devHdr;
int channel;
int irq;
void *baseAddress;
} CPCI16IO_DEV;
The exact fields depend on the hardware and driver architecture.
๐งฎ Static Library vs. Object File #
Both .a and .o files can be used to distribute precompiled VxWorks code, but they have different characteristics.
.o object file
#
An object file represents a compiled translation unit.
For example:
cpciInit.o
cpciDrv.o
cpciDevice.o
Individual .o files can be linked directly into the target project.
This provides relatively fine-grained control over which compiled modules are included.
.a static library
#
A static archive combines multiple object files:
cpciInit.o
cpciDrv.o
cpciDevice.o
โ
cpciDriver.a
The consumer then links a single library instead of managing every object file individually.
For a multi-source driver, .a is generally easier to distribute and integrate.
๐ ๏ธ Modifying the Makefile #
For projects using a Makefile-based build system, the corresponding library can be added through LIB_EXTRA.
For example:
LIB_EXTRA += /path/to/cpciDriver.a
Depending on the existing build structure, the library may instead be added to an existing LIB_EXTRA definition:
LIB_EXTRA = \
/path/to/cpciDriver.a \
/path/to/anotherLibrary.a
The important point is that the VxWorks build system must know where the precompiled library is located and include it during the link stage.
The header files must also be available through the compiler’s include path.
A typical library integration therefore has two separate requirements:
Header files
โ
Compiler include path
.a / .o files
โ
Linker/library configuration
The header allows the compiler to understand the API.
The library provides the actual implementation during linking.
๐ Common Integration Errors #
Precompiled driver integration introduces several failure modes that are less visible in source-based projects.
Missing symbols #
If the application compiles successfully but linking fails with an undefined symbol such as:
undefined reference to cpci32ioInit
the compiler has found the declaration in the header, but the linker cannot find the implementation.
Check:
- Whether the
.aor.ofile is included - The library path
PRJ_LIBSEXTRA_MODULESLIB_EXTRA- Symbol naming
- Build architecture compatibility
Header/library mismatch #
The header file and compiled library must expose compatible APIs.
For example, changing:
IMPORT STATUS cpci32ioInit(void);
to an incompatible declaration without rebuilding the library can result in ABI problems.
The public header should therefore be versioned together with the corresponding binary library.
Architecture mismatch #
A library compiled for one CPU architecture cannot necessarily be linked into a different VxWorks target.
For example, a library generated for one target architecture or toolchain configuration may be incompatible with another.
The following should remain aligned:
CPU Architecture
VxWorks Version
Compiler/ABI
BSP
Library
Application
This becomes particularly important when maintaining legacy VxWorks systems.
C and C++ name mangling #
The example uses:
#ifdef __cplusplus
extern "C" {
#endif
This is important when a C-based driver API is consumed from C++ code.
Without extern "C", a C++ compiler may apply name mangling to function symbols, causing the linker to fail to match the application’s function references with the symbols exported by the C implementation.
For mixed C/C++ VxWorks projects, keeping the public C API wrapped with extern "C" is therefore a useful compatibility practice.
๐ Protecting Driver Intellectual Property #
One of the main reasons to package VxWorks drivers as static libraries is to distribute functionality without distributing source code.
A typical delivery package might contain:
cpciDriver/
โโโ include/
โ โโโ cpciDriver.h
โโโ lib/
โ โโโ cpciDriver.a
โโโ examples/
โโโ testCpci.c
The customer receives the API and binary implementation but does not receive the original driver source.
This model is useful for:
- Commercial drivers
- Proprietary hardware interfaces
- Vendor-specific middleware
- Board-support components
- Reusable internal libraries
- Third-party VxWorks components
However, a static library is still a compiled binary and should be treated as architecture- and toolchain-specific software rather than a universally portable package.
๐งช Recommended Development Workflow #
A practical workflow for packaging a VxWorks driver is:
1. Develop the driver normally #
Initially, keep the source code accessible so that driver functionality can be implemented and debugged efficiently.
2. Define the public API #
Identify which functions, structures, constants, and configuration interfaces need to be exposed to consumers.
3. Create the public header #
Move the required declarations into a dedicated .h file.
4. Build the library project #
Create a downloadable application project and add the driver source files.
5. Select Archive output #
Configure:
Builds โ Properties โ Rules โ Archive
6. Generate the .a file
#
Build the project and verify that the expected archive is produced.
7. Integrate the library #
For a standard application project, configure:
PRJ_LIBS
For a VxWorks system image project, configure:
EXTRA_MODULES
For Makefile-based integration, add the library to:
LIB_EXTRA
8. Verify symbols #
Check that the expected public symbols exist in the resulting library and that the final target links successfully.
9. Test on the target #
Deploy the resulting application or VxWorks image to the actual hardware and verify initialization, device creation, interrupt handling, I/O operations, and shutdown behavior.
๐ง Understanding the Complete Build Flow #
The complete VxWorks static-library workflow can be summarized as follows:
Driver Source Code
โ
โผ
VxWorks Downloadable Application Project
โ
โผ
Compiler
โ
โผ
.o Object Files
โ
โผ
Archive Rule
โ
โผ
.a Static Library
โ
โโโโโโโโโโโโโโโโบ Public .h Header
โ
โผ
Consumer VxWorks Project
โ
โโโ PRJ_LIBS
โโโ EXTRA_MODULES
โโโ LIB_EXTRA
โ
โผ
Compiler + Linker
โ
โผ
Application / VxWorks System Image
โ
โผ
Target Hardware
This architecture cleanly separates implementation, interface, and integration.
The driver developer controls the implementation and generates the binary library. The application developer consumes the public header and links against the library without requiring access to the original source.
๐ Final Takeaway #
Packaging VxWorks drivers into .a or .o files is a straightforward way to create reusable and source-independent software components.
With Tornado 2.2, the simplest approach is to create a downloadable application project containing the driver sources and select Archive under:
Builds โ Properties โ Rules
This produces a .a static library based on the project.
The resulting library can then be integrated into another VxWorks project through:
PRJ_LIBS
for standard application projects, or:
EXTRA_MODULES
for VxWorks system image projects.
For Makefile-based builds, the library can be included through:
LIB_EXTRA
The corresponding .h file provides the public API, while the .a or .o file contains the compiled implementation.
For proprietary CPCI drivers and other reusable VxWorks components, this approach provides a practical balance between code reuse, binary distribution, intellectual-property protection, and integration simplicity. The most important requirement is to keep the header, library, VxWorks version, compiler ABI, BSP, and target architecture synchronized so that the binary component remains compatible with the consuming project.