AoIterate()

Updated: April 19, 2023

Find the next control for an interface

Synopsis:

#include <aoi.h>

const AOICtrl_t *AoIterate(const char *iname,
                           const int32_t version,
                           int32_t* const cookie);

Arguments:

iname
The name of an interface that any returned control should contain, or NULL if you want to get the next control in the global list.
version
The minimum version of the interface specified with the iname argument; this is ignored if iname is NULL.
cookie
An opaque variable used to iterate through all available AOI controls to find matching interfaces. For the first call to this function, pass in a pointer to a zero value. For subsequent calls, pass in the same pointer. You must not modify the value it points to between calls.

Library:

libaoi.so

Description:

This function iterates through all available AOI controls, returning each one that has an interface with the given name and minimum version. When you first call this function, you should set cookie to point to a zero value. You can then keep calling this function until it returns the control you're looking for, or it returns NULL, which means there are no more controls.

If name is NULL, AoIterate() returns the next control in the global list, without considering the name or version of any of its interfaces.

Returns:

For the first call, a pointer to an AOICtrl_t structure for the first control containing an interface that matches the name and version values, or NULL if there are no addons to return. Subsequent calls return the next control with such an interface, until there are no more controls, at which point the function returns NULL.

Example:

Suppose you want to write an application that translates text between natural languages. Your application will allow end users to pick the source (input) and destination (output) languages, and then will pick an addon to do the actual translation (e.g., English to French). Each addon can support one or more source languages and one or more destination languages. The application can iterate over the available controls and for each that contains the translator interface, check if the addon supports the requested source and destination languages and if so, obtain a rating for it. Then, after it's finished iterating over the controls, the application can use the highest rated addon to do the translation.

Note: The code shown here is spread over multiple files; a common header file defining the interface that an addon can implement, an addon source file with a specific implementation, and an application source file that uses the addon. We don't show the entire contents of each file; we show just the parts relevant to our example of using AoIterate() and other API functions.
The interface header file exposes the functions that an addon must implement and the strings that it must publish:
typedef struct AcmeTranslatorInterface {
    int (*RateLanguages)(const char *srclang, const char *dstlang);
    struct AcmeTranslatorHandle *(*Create)(const char *srclang, const char *dstlang);
    void (*Destroy)(struct AcmeTranslatorHandle *trh);
    size_t (*Translate)
            (struct AcmeTranslatorHandle *trh, const char *srctxt, char *dstbuf, size_t dstsize);
} AcmeTranslatorInterface;

// Strings; their values must be lists of language codes (e.g., "en", "fr").
#define ACME_TRANSLATOR_SRCLANGS_STRING "AcmeTranslatorSrcLanguages"
#define ACME_TRANSLATOR_DSTLANGS_STRING "AcmeTranslatorDstLanguages"
The strings are referred to by the ACME_TRANSLATOR_SRCLANGS_STRING and ACME_TRANSLATOR_DSTLANGS_STRING constants and their values contain the lists of supported source and destination languages. There are functions for rating the addon's ability to translate between a particular source and destination language, for creating and destroying translator handles, and for doing the translation. Showing the code for these functions is beyond the scope of this example, though, so we show only the structure that defines the AcmeTranslatorInterface interface and contains pointers to these functions, and the addon's strings and its list of interfaces:
static AcmeTranslatorInterface translator_interface = {
    my_RateLanguages, my_CreateTranslator, my_Destroy, my_Translate
};

static const char srclanguages[] = ACME_TRANSLATOR_SRCLANGS_STRING "=fr,en",
                  dstlanguages[] = ACME_TRANSLATOR_DSTLANGS_STRING "=fr,en",
                  *strings_interface[] = { srclanguages, dstlanguages, NULL };

AOInterface_t interfaces[] = {
    { "Name", 1, "my_acme_translator" },
    { "Strings", 1, strings_interface },
    { "AcmeTranslatorInterface", ACME_TRANSLATOR_VERSION, &translator_interface },
    { 0,0,0 }
};
To check if an addon can be used for a particular translation, the application will search the addon's strings to see if they contain the right language codes. For this, we write a helper function, findlang(), for looking up each of the source and destination languages:
// Check if the string named by "stringname" mentions the language "lang".
static inline bool findlang(const char *const *strings, 
                            const char *stringname, 
                            const char *lang, 
                            size_t langlen) {
    const char *list;
    if ((list = AoFindString(strings, stringname)) == NULL) {
        return false;
    } else {
        return AoSearchString(list, lang, langlen, AO_STRING_IGNORE_CASE) > 0;
    }
}

In this example, we provide a function called AcmeTranslatorCreate() that finds an addon to translate from one language to another. Your application would call this function after the user specifies the source (input) and destination (output) languages.

This function handles the process of obtaining addon ratings and picking the best addon for the translation, which proceeds as follows:
  1. Use AoIterate() to find the next control containing the AcmeTranslatorInterface interface.
  2. Retrieve the addon's strings and use findlang() to check if the addon supports the requested source and destination languages.
  3. If the addon supports these languages, load the DLL into memory, then obtain its rating for the requested translation (through the AcmeTranslatorInterface interface).

    The rating could depend on the specific combination of languages; for instance, an addon could list Spanish and English as its inputs and French and English as its outputs, but support translating only between English and the other language, and not from Spanish to French. It could depend also on the language variants; for instance, the addon could support Canadian French better than Belgium French.

  4. If the current addon is the highest rated one so far, note its name and rating, because it's the new top pick for doing the translation. Otherwise, release the control because the addon won't be used.
  5. Repeat the previous steps until there are no more controls containing the translator interface. Then, if the highest rating is zero, meaning no suitable addon was found, report the unsuccessful outcome. Otherwise, try to create a translator handle and report either the successful outcome, including the name of the chosen addon, or the failure to create a handle.
AcmeTranslator_t *AcmeTranslatorCreate(const char *srclang, const char *dstlang) {
    // Allocate memory for a translator structure, which is a private data type defined elsewhere
    AcmeTranslator_t *tr;
    if ( (tr = calloc(1, sizeof(*tr))) == NULL ) {
        AO_LOG(AO_LOG_ERROR, "AcmeTranslatorCreate: out of memory");
    } else {
        size_t srcllen = strcspn(srclang, "/"); // Count language code only, ignore country suffix
        size_t dstllen = strcspn(dstlang, "/"); // Count language code only, ignore country suffix
        int bestrating = 0;
        int32_t cookie = 0;
        const AOICtrl_t *ctrl;
        // 1. Find the next control containing the translator interface
        while ( (ctrl = AoIterate(
                        "AcmeTranslatorInterface", ACME_TRANSLATOR_VERSION, &cookie) ) != NULL) {
            // 2. We have an addon. Does it support the languages we want?
            const char *const *strings = AoGetStrings(ctrl);
            bool tryit = findlang(strings, ACME_TRANSLATOR_SRCLANGS_STRING, srclang, srcllen)
                      && findlang(strings, ACME_TRANSLATOR_DSTLANGS_STRING, dstlang, dstllen);
            // We don't need this pointer to the strings anymore
            AoUngetStrings(strings);
            if (tryit) {
                // 3a. Hold the control.
                if ( AoHold(ctrl) ) {
                    AO_LOG(AO_LOG_WARNING, 
                            "AcmeTranslatorCreate: AoHold failed, skipping this addon");
                } else {
                    // 3b. Get the addon's name for logging purposes.
                    const char *name;
                    const AcmeTranslatorInterface *iface;
                    if ( (name = AoGetInterface( ctrl, "Name", 0, 0 )) == NULL ) {
                        name = "<unnamed>";
                    }
                    // 3c. Get the addon's rating for the source and destination languages.
                    if ( (iface = AoGetInterface(ctrl,
                                                 "AcmeTranslatorInterface", 
                                                 ACME_TRANSLATOR_VERSION, 
                                                 0)) == NULL ) {
                        // AoIterate() was wrong about this DLL's interfaces -- 
                        // maybe it had old info (old config file or DLL has just been updated)
                        AO_LOG(AO_LOG_WARNING, 
                        "AcmeTranslatorCreate: interface has disappeared from addon %s, ignoring",
                                name);
                    } else {
                        int rating = iface->RateLanguages(srclang, dstlang);
                        if (rating < bestrating) {
                            AO_LOG(AO_LOG_DEBUG1, 
                                    "We got a rating of %d from %s, out best so far is %d", 
                                    rating, name, bestrating);
                        } else {
                            // 4a. The current addon has the highest rating so far, so we note its
                            // name and rating.
                            AO_LOG(AO_LOG_DEBUG1, 
                                    "We got a rating of %d from %s, this is our new best", 
                                    rating, name);
                            if (bestrating > 0) {
                                AoRelease(tr->aoictrl);
                            }
                            bestrating  = rating;
                            tr->aoictrl = ctrl;
                            tr->iface   = iface;
                            tr->aoiname = name;
                            continue;
                        }
                    }
                    // 4b. If we haven't taken the "continue" above, the current addon won't
                    // be used, so we can release its control.
                    AoRelease(ctrl);
                }
            }
        }
        if (bestrating == 0) {
            // 5a. The highest rating is 0, so we must report that no suitable addon was found
            AO_LOG(AO_LOG_ERROR, 
                    "AcmeTranslatorCreate: we have not found an addon that supports %s to %s", 
                    srclang, dstlang);
        } else {
            // 5b. Try to create a translator handle. If successful, report the name of the addon
            // chosen for the translation; otherwise, report the failure and release the control.
            if ((tr->trh = tr->iface->Create(srclang, dstlang)) != NULL) {
                AO_LOG(AO_LOG_DEBUG1, 
            "AcmeTranslatorCreate: successfully created instance of %s for %s to %s translation",
                        tr->aoiname, srclang, dstlang);
                return tr;
            }
            AO_LOG(AO_LOG_ERROR, 
                    "AcmeTranslatorCreate: Create() call of %s failed!", tr->aoiname);
            AoRelease(tr->aoictrl);
        }
        free(tr);
    }
    return NULL;
}

Classification:

QNX Neutrino