Using the library

This chapter shows how a program reads the data through the libopendmi library. Every function is described in full in the part of this manual about the C library.

1. Finding the library

The installed library is found with CMake, by the package it installs:

find_package(OpenDMI 0.7 REQUIRED)

Binary compatibility is not kept between the minor versions before 1.0, so the package is compatible with the same minor version only.

The library is found with pkg-config too, by the opendmi package.

A library installed outside the directories CMake and pkg-config look in, such as the default /opt/opendmi prefix of build.sh, is found through the prefix given to CMake, and the directory of the package given to pkg-config:

$ cmake -B build -DCMAKE_PREFIX_PATH=/opt/opendmi
$ export PKG_CONFIG_PATH=/opt/opendmi/lib/pkgconfig

The directory of the libraries is lib in the examples of this chapter, and is lib64 on some systems.

2. Linking

The library is built both as a shared library and as a static one, and a program is linked with either.

2.1. Linking with the shared library

The shared library is what a program is linked with by default:

target_link_libraries(example PRIVATE OpenDMI::opendmi)

OpenDMI::opendmi and OpenDMI::shared are the same library. Without CMake, the flags come from pkg-config:

$ cc example.c $(pkg-config --cflags --libs opendmi)

The library is looked up again when the program starts. A library installed outside the directories the system looks in is found by the path the program is linked with:

$ cc example.c $(pkg-config --cflags --libs opendmi) \
    -Wl,-rpath,/opt/opendmi/lib

CMake gives that path to the programs in its build tree by itself, and to the installed ones through the INSTALL_RPATH property. The shared library is named after the minor version before 1.0, e.g. libopendmi.so.0.7 on Linux and libopendmi.0.7.dylib on macOS, so that a program is not started with a library it is incompatible with.

2.2. Linking with the static library

The static library makes a program which needs no OpenDMI installed to run:

target_link_libraries(example PRIVATE OpenDMI::static)

The target brings along everything the static library needs, which the other ways of linking give by hand.

The static library is libopendmi.a, and opendmi-static.lib with MSVC. It is linked by its path, because -lopendmi is resolved to the shared library whenever one is installed next to it. The libraries it depends on are linked along with it: none on Linux and the BSDs, the CoreFoundation and IOKit frameworks on macOS, and ntdll on Windows, along with ICU4C when the library is built with the translations. pkg-config --static --libs opendmi lists them, after the -lopendmi the path stands for:

$ cc $(pkg-config --cflags opendmi) example.c /opt/opendmi/lib/libopendmi.a
$ cc $(pkg-config --cflags opendmi) example.c /opt/opendmi/lib/libopendmi.a \
    -framework CoreFoundation -framework IOKit

The first command links the program on Linux, and the second on macOS.

On Windows, the program linked with the static library defines DMI_STATIC, so that the functions of the library are not taken for the ones a DLL imports. OpenDMI::static defines it by itself, and pkg-config --static --cflags opendmi gives it there too.

3. Reading the data

A program creates a context, which holds the data and everything decoded from it, opens the data of the system, and looks up the structures it needs in the registry of the context. Decoded structures are plain C types, one for each structure type:

#include <stdio.h>

#include <opendmi/context.h>
#include <opendmi/registry.h>
#include <opendmi/entity.h>
#include <opendmi/entity/system.h>

int main(void)
{
    dmi_context_t *context = dmi_create(DMI_CONTEXT_FLAG_LINK);

    if (context == nullptr)
        return 1;

    if (dmi_open(context, nullptr)) {
        dmi_registry_t *registry = dmi_get_registry(context);
        dmi_entity_t   *entity   = dmi_registry_lookup_first(registry,
                                                             DMI_TYPE(system),
                                                             false);
        dmi_system_t   *system   = dmi_entity_info(entity, DMI_TYPE(system));

        if (system != nullptr)
            printf("%s %s\n", system->vendor, system->product);
    }

    dmi_destroy(context);

    return 0;
}

dmi_open reads the data of the system, and requires the same privileges the command line tool does. dmi_load reads a dump file instead, which is how a program is tried out on the data of another machine:

if (dmi_load(context, "smbios.bin")) {
    ...
}

4. Handling errors

Functions which fail return false or nullptr, and leave the reason in the error queue of the context, where dmi_error_get_last takes it from:

if (!dmi_open(context, nullptr)) {
    const dmi_error_t *error = dmi_error_get_last(context);

    if (error != nullptr)
        fprintf(stderr, "%s\n", dmi_error_message(error->reason));
}

The last error is the one the failed call has raised itself. The errors raised on the way to it, such as the file which could not be opened, precede it in the queue, and are taken from its beginning with dmi_error_get_first.