Libraries

Library: A library (sometimes called an archive) is a collection of pre-compiled object files that promote code reuse across many projects. Each object file in the library is simply a compiled version of a C or assembly source file from the original library project. Libraries provide a form of encapsulation such that the end user can take full advantage of the library's functions without being provided with the source code.

Background

To understand how libraries are created and may be used in a project, it is important to understand some details about how a project is built [Figure 1]. In a typical application, source code is compiled into object files (one object file for each source file). Object files contain compiled code where none of the variables or code blocks have been assigned addresses in the device's memory map. In other words, an object file is relocatable because in theory, all of its items may be located anywhere in the device's memory. The job of assigning addresses falls to the linker. It takes the information in the object file and device memory map information from a linker script file and assigns variables to specific addresses and arranges code blocks to best fit in the available memory. The linker script provides all of the fixed addresses of a device's registers and program memory regions. The linker is the tool that creates the hex file that will ultimately be programmed into the device.

TypicalProject.png

Figure 1 - Typical project compile and link process


All of the above is true for assembly programming too, as long as you write relocatable assembly code which uses a different set of directives from absolute assembly code. Absolute assembly code specifies addresses in the code itself (using directives like org and equ or cblock) and therefore is never passed to the linker. This means that absolute assembly projects cannot use libraries generated as outlined below or be used to implement libraries. Relocatable assembly uses directives like code and res which are handled in much the same way as C variables and functions, making it possible for code written in this fashion to be used for and with libraries.

When you create a library project [Figure 2], the build process is slightly different. After the compiler generates the object files, they are not passed to the linker. Instead, they are passed to the librarian (sometimes called the archiver). The librarian collects all of the object files generated by the compiler and puts them into a single "container" file called a library (or archive). None of these object files in the library have been through the link step, so all of their objects (variables, functions, etc.) have not been assigned an address in memory. This means that these objects can easily be mixed with another project's code where both that project's objects and the library's objects will be assigned addresses when the project is built and they are all passed through the linker.

LibraryCreation.png

Figure 2 - Typical library project compile and archive process

Once you have created a library file, it is ready to be used in a project [Figure 3]. In a typical project, you will have some code specific to that project that calls functions included in the library. The "glue" that makes this possible is a header file associated with the library that contains function prototypes for any functions in the library that you wish to make available to users of the library. These function prototypes "expose" the functions to the outside world by telling the compiler what the function looks like (declaration), even though the implementation (definition) of the function is in the library and not seen by the compiler. The compiler generates its object files with names referring to these library objects, and the linker cleans up everything in the end by tying all the appropriate items together.

LibraryUse.png

Figure 3 - Typical library use in a standalone project

Everything above describes the mechanics of how the compiler, linker and librarian interact with each other. Next, lets look at a real example of how to create a library and use it in a project.


How to create a library

There are two main parts of a library:

  1. The library project
  2. The header file that you will provide to those who will use the pre-compiled library in their own projects

Example Library Project

1

Launch the new project wizard

Toolbar:
Main_New_Project.png
Keyboard: Ctrl+Shift+N
Menu: File ▶ New Project…

2

Choose Project Type

Choose Library Project from the list on the right and click Next >.

This will create a project that will generate a library file (*.lib or *.a depending on which compiler you use) instead of a hex file. This project will not be able to run on its own as it will not have a main() function. The resulting library will be used in another project that has its own main() function.

mylib_1.png
Click image to enlarge.

3

Select Device

Select the device family and device that are in the same family for which you wish to use this library. Click Next >.

As a general rule, libraries may be used on any device that shares the architecture of its original target. In this example, we are choosing a PIC24FJ128GA010 because it is a fairly generic "superset" device that has a little bit of everything that the PIC24F family has to offer. This doesn't mean that we can only use this library with this device. On the contrary, we should be able to use it with any PIC24F device as long as we don't use some feature that is unique to the PIC24FJ128GA010. The key is that if our library references any on chip hardware by its register names, this library may only be used with devices whose registers share the same names. The reason this works is because this library project will not be linked. So, register names will NOT be equated to their hardware addresses. Rather they are left "floating" until this library is used in a different, standalone project where the linker will finally connect the names used here to the hardware addresses of the standalone project's target device.

mylib_2.png
Click image to enlarge.

4

Select Header

Leave this checkbox unchecked. This only affects debugging, which cannot be done directly with a library project. Click Next >.

mylib_3.png
Click image to enlarge.

5

Select Tool

Since a library project cannot be debugged on its own, choose nothing or choose the simulator. It really doesn't matter. Click Next >.

mylib_4.png
Click image to enlarge.

6

Select Compiler

Choose the appropriate compiler or assembler for your target architecture. Click Next >.

The choice of compiler here not only determines which tool will build the object files for the library, but also determines which compiler this library may be used with when included in other projects. However, you are not tied down to a particular version number in most circumstances. For example, if I choose the XC16 compiler, I cannot use this library with Hi-Tech, CCS, nor any other compiler that supports this device family, but I should be able to use it with subsequent versions of the XC16 compiler.

Libraries may also be written in and used in assembly language, but they must be written as relocatable code. If you wish to mix assembly and C, then assembly libraries must be written to be both relocatable and C callable. This requires a thorough understanding of your compiler's parameter passing conventions.

mylib_5.png
Click image to enlarge.

7

Select Project Name and Folder

Choose a name and location for your project. Click Finish.

The name you give your project will be the name of the library file it generates. In this example the project name is MyLib and the library file it generates will be MyLib.x.a. The ".a" suffix is the library suffix used by XC16 and XC32. The ".x" is automatically inserted to indicate that this library was built using the MPLAB® X IDE. The name isn't critical and you can always change the name of the generated library file in your file manager before using it in another project.

mylib_6.png
Click image to enlarge.

8

Add Source Files

To add blank source files to the project, right click on the Source Files folder of the project tree and select New ▶ Empty File… from the pop-up menu. This will prompt you to provide a filename and location for the files. Do this twice to create two new source files named add.c and sub.c. In both cases, keep the default location, which is inside the project directory.

Each of these files will contain a single function. When the project is compiled, the library will contain two object files: add.o and sub.o. Putting each function into a separate file will make the library more efficient for the end user. If the end user only uses the function inside add.o, then only the code from add.o will be compiled into the hex file. This means you can create huge libraries of related functions, but only the code from the functions that are actually used will be compiled into the hex file, so memory will not be wasted on code that never gets called.

mylib_7.png
Click image to enlarge.

mylib_8.png

Once you have added the source files, your project will look like the image to the left.

mylibtest_1.png
Click image to enlarge.

9

Add Code

Add the following code to the appropriate source files in your project:

add.c

// This function takes two parameters, adds them together and returns the result
int add(int a, int b)
{
    return (a + b);
}


sub.c

// This function takes two parameters, subtracts the second from the first and returns the result
int sub(int a, int b)
{
    return (a - b);
}

10

Build the Library

Once you have added all the code to the sources files, the project is ready to be built. Click on the Build button Main_Build_Project.png on the toolbar.
mylib_9.png

If the build was successful, click on the Files tab in the project window and navigate down the tree to MyLib ▶ dist ▶ default. You should see your library file mylib.x.a here. This is the file you will include in other projects.

mylibtest_2.png
Click image to enlarge.

Example Library Header File

The header file is not used in the library itself, but rather in any project that will use the library. This is a file you must create to expose the functions in your library that you want to make available to your library's users. An example of a function you might not want to make available via the header file is one that is itself called by one of the library's functions, but you don't want the end user of the library to call directly. In the simple library we created above, there are only two functions available, so our header file will have two function prototypes. The function prototype is nothing more than the function header (first line) of each function in the library terminated with a semicolon. Although not required, it is customary to give the header file the same name as the library file. Since our library is called MyLib.x.a, we will call the header MyLib.h (or MyLib.x.h if you prefer).

MyLib.h

int add(int a, int b);
int sub(int a, int b);

Your library's users must include this header file (For example: #include "MyLib.h") in every file of their project where they make calls to one of the library's functions.


How to test a library

When you create a library, you will need to test it in a standalone project. The easiest way to do this is to create a test project that is open at the same time as the library project in MPLAB X. This makes it very easy to switch back and forth as you make changes to the library and then test those changes. So, without closing the library project, create a new project to test the library:

1

Toolbar:
Main_New_Project.png
Keyboard: Ctrl+Shift+N
Menu: File ▶ New Project…

2

Choose Project Type

Choose Standalone Project from the list on the right and click Next >.

mylibtest_4.png
Click image to enlarge.

3

Specify Project Parameters

For all intents and purposes, this is just an ordinary standalone project. So, simply create the project as you normally would by completing the steps of the project wizard. For this example, the following parameters were used:

  • Family: PIC24
  • Device: PIC24FJ128GA010 (other PIC24F devices should work too)
  • Debug Header: Only check the box if you are using one.
  • Tool: Choose simulator unless you have a hardware debug tool (i.e. MPLAB ICD 3, REAL ICE™, etc.) and appropriate target hardware.
  • Compiler: Choose the C30 compiler.
  • Project Name: MyLibTest
  • Project Location: Same location as MyLib (i.e. MyLib.X's parent directory).

4

Add Files

  1. Right click on Header Files and select New ▶ Empty File… from the pop-up menu. Name the file MyLib.h.
  2. Right click on Source Files and select New ▶ Empty File… from the pop-up menu. Name the file main.c.
  3. Right click on Library Files and select Add Existing Item…. In the file dialog that pops up, navigate to and select your library file from the MyLib project. In this example, it is at: ..\MyLib.X\dist\default\mylib.x.a (go to wherever your MyLib.X folder was created from the library project above).
mylibtest_5.png
Click image to enlarge.

By adding the library file from the location where it is generated by the MyLib project, we can switch back and forth between these projects to tweak the library very easily. Every time the MyLib project is built, a new mylib.x.a is generated. Because the MyLibTest project references this file's location, MyLibTest will always have the latest build of mylib.x.a.

If this weren't a test project, it would be better to copy the mylib.x.a file into this project's directory. Then add the copy in this project's directory to the project tree instead. Once the library is tested, there is no need to always reference the original project. All you need is a copy of mylib.x.a and the header file MyLib.h that we are about to write.

mylibtest_3.png

Once you have added the files, your project will look like the previous image

AddLibrary.png

5

Add Code

Add the following code to their respective files in your project:

MyLib.h

int add(int a, int b);
int sub(int a, int b);

main.c

#include "MyLib.h"

int sum, difference;  // Variables to hold results of function calls

int main(void)
{
    sum = add(5, 3);          // sum = 5 + 3
    difference = sub(5, 3);   // difference = 5 - 3
    while(1);                 // loop forever
}

The first line of main.c, #include "MyLib.h" and the file it references are critical. Without these, the compiler will not recognize add() and sub() as valid functions. Remember, the library contains pre-compiled code, so the compiler doesn't know anything about it. This header file is essentially telling the compiler, "You will see this program make calls to add() and sub() which aren't implemented here. All you need to do is generate the appropriate parameter passing code based on the template of these function prototypes. The linker will be made aware of where to find the implementations of these functions so that it can make the appropriate connections between these function calls and their implementations."

LibraryUse.png

If this project only made a call to one of the functions, only the code that implemented that function would be added to the hex file. It works this way because all of the functions in the library were implemented in their own source files. If the library code were implemented as a single source file, then a call to just one function would result in the code for all of the functions being added to the hex file.

6

Build Project

Make sure that you have selected the MyLibTest project by clicking on it in the project tree. Then click the Debug Project button Main_Debug_Project.png on the toolbar to build the project and run it in the debugger you selected in step 2.

7

View Output

  1. Halt the program by clicking on the Pause button Debug_Pause.png on the toolbar.
  2. Open a Watches window by selecting from the menu: Window ▶ Debugging ▶ Watches.
  3. Add the two result variables sum and difference to the watches window (highlight one in the editor, then click and drag it to the watches window).
You should see the following results as shown in the screenshot at left:
sum = 0x0008 (0x0008 = 810)
difference = 0x0002 (0x0002 = 210)

Clearly, the library worked right the first time. However, if changes are required all you would have to do is:

  1. End the current debug session by clicking on the Finish Debugger Session button (Debug_Finish_Debugger_Session.png) on the toolbar.
  2. Change the library code as needed
  3. Click on the MyLib project to ensure it is selected and build it (Main_Build_Project.png)
  4. Click on the MyLibTest project to ensure it is selected and debug it again (Main_Build_Project.png)

You don't need to remove the "old" mylib.x.a from MyLibTest and then add the "new" one after it was rebuilt because MyLibTest has a reference that points to the mylib.x.a file generated by the MyLib project. So, whenever MyLib is rebuilt, MyLibTest automatically "gets" the rebuilt mylib.x.a. So, when you rebuild MyLibTest, it uses the newly built mylib.x.a.

Once you are satisfied that your library works, you can add the library file (e.g. mylib.x.a) to any project that uses the same processor family and include the header file (e.g. MyLib.h) in any source file that calls one of the library's functions. You do not need to have the library project open nor do you need to provide the library's source code to its users. The library file and header file are all that is needed to take advantage of all the code in the library.

© 2016 Microchip Technology, Inc.
Information contained on this site regarding device applications and the like is provided only for your convenience and may be superseded by updates. It is your responsibility to ensure that your application meets with your specifications. MICROCHIP MAKES NO REPRESENTATIONS OR WARRANTIES OF ANY KIND WHETHER EXPRESS OR IMPLIED, WRITTEN OR ORAL, STATUTORY OR OTHERWISE, RELATED TO THE INFORMATION, INCLUDING BUT NOT LIMITED TO ITS CONDITION, QUALITY, PERFORMANCE, MERCHANTABILITY OR FITNESS FOR PURPOSE. Microchip disclaims all liability arising from this information and its use. Use of Microchip devices in life support and/or safety applications is entirely at the buyer's risk, and the buyer agrees to defend, indemnify and hold harmless Microchip from any and all damages, claims, suits, or expenses resulting from such use. No licenses are conveyed, implicitly or otherwise, under any Microchip intellectual property rights.