7 n__module_8c
gullradriel edited this page 2026-10-29 20:54:40 +01:00

src/n_module.c

Load a shared library at runtime and find its symbols. More...

Structures

Name
struct N_MODULE
an opened shared library

Functions

Name
void _n_module_set_error(const char * what, const char * subject)
helper: record why what failed on subject, in the system's words

Attributes

Name
__thread char[512] _n_module_last_error
the last failure on this thread, as the system worded it

Detailed Description

Load a shared library at runtime and find its symbols.

Author: Castagnier Mickael

Version: 1.0

Date: 24/09/2026

Functions Documentation

function _n_module_set_error

static void _n_module_set_error(
    const char * what,
    const char * subject
)

helper: record why what failed on subject, in the system's words

Attributes Documentation

variable _n_module_last_error

static __thread char[512] _n_module_last_error = "";

the last failure on this thread, as the system worded it

Source code

/*
 * Nilorea Library
 * Copyright (C) 2005-2026 Castagnier Mickael
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
 * implied. See the License for the specific language governing
 * permissions and limitations under the License.
 *
 * SPDX-License-Identifier: Apache-2.0
 */


#include <stdio.h>
#include <string.h>

#include "nilorea/n_module.h"

#ifdef __windows__
#include <windows.h>
#else
#include <dlfcn.h>
#endif

struct N_MODULE {
    void* handle;
};

static __thread char _n_module_last_error[N_MODULE_ERROR_MAX] = "";

static void _n_module_set_error(const char* what, const char* subject) {
#ifdef __windows__
    /* half the message, so the system's text leaves room for what and on what */
    char sys[N_MODULE_ERROR_MAX / 2] = "";
    DWORD code = GetLastError();
    DWORD n = FormatMessageA(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, NULL, code, 0, sys, (DWORD)sizeof(sys), NULL);

    /* FormatMessage ends its text with a line break */
    while (n > 0 && (sys[n - 1] == '\r' || sys[n - 1] == '\n' || sys[n - 1] == ' ')) sys[--n] = '\0';
    snprintf(_n_module_last_error, sizeof(_n_module_last_error), "%s %s: error %lu, %s", what, subject, (unsigned long)code, n > 0 ? sys : "no description");
#else
    /* dlerror() clears itself when read, and is NULL when nothing failed */
    const char* sys = dlerror();

    snprintf(_n_module_last_error, sizeof(_n_module_last_error), "%s %s: %s", what, subject, sys ? sys : "no description");
#endif
    n_log(LOG_DEBUG, "%s", _n_module_last_error);
}

N_MODULE* n_module_open(const char* path) {
    N_MODULE* module = NULL;
    void* handle = NULL;

    __n_assert(path, return NULL);
    _n_module_last_error[0] = '\0';
#ifdef __windows__
    {
        /* The wide call, so a path outside the system code page opens; the
         * ANSI one if the text is not valid UTF-8, which is what the caller
         * would have got before. */
        wchar_t wpath[MAX_PATH * 4];
        int wlen = MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, path, -1, wpath, (int)(sizeof(wpath) / sizeof(wpath[0])));

        if (wlen > 0)
            handle = (void*)LoadLibraryW(wpath);
        else
            handle = (void*)LoadLibraryA(path);
    }
#else
    /* RTLD_LAZY: a driver library pulls in far more than a caller uses.
     * RTLD_LOCAL: a plugin must not shadow a symbol the program already has. */
    handle = dlopen(path, RTLD_LAZY | RTLD_LOCAL);
#endif
    if (!handle) {
        _n_module_set_error("cannot open", path);
        return NULL;
    }
    Malloc(module, N_MODULE, 1);
    if (!module) {
#ifdef __windows__
        FreeLibrary((HMODULE)handle);
#else
        dlclose(handle);
#endif
        snprintf(_n_module_last_error, sizeof(_n_module_last_error), "cannot open %s: out of memory", path);
        return NULL;
    }
    module->handle = handle;
    return module;
}

void* n_module_symbol(N_MODULE* module, const char* name) {
    void* symbol = NULL;

    __n_assert(module, return NULL);
    __n_assert(module->handle, return NULL);
    __n_assert(name, return NULL);
    _n_module_last_error[0] = '\0';
#ifdef __windows__
    {
        /* GetProcAddress returns a function pointer and the caller wants a
         * void pointer, which C does not convert between; memcpy is the
         * defined way across. */
        FARPROC proc = GetProcAddress((HMODULE)module->handle, name);

        if (proc) memcpy(&symbol, &proc, sizeof(symbol));
    }
#else
    /* A symbol can legitimately be NULL; clearing dlerror first is how the
     * two cases are told apart, and nothing here wants a NULL symbol. */
    (void)dlerror();
    symbol = dlsym(module->handle, name);
#endif
    if (!symbol) _n_module_set_error("no symbol", name);
    return symbol;
}

int n_module_close(N_MODULE** module) {
    int ok = TRUE;

    if (!module || !*module) return TRUE;
    if ((*module)->handle) {
#ifdef __windows__
        if (!FreeLibrary((HMODULE)(*module)->handle)) {
            _n_module_set_error("cannot close", "module");
            ok = FALSE;
        }
#else
        if (dlclose((*module)->handle) != 0) {
            _n_module_set_error("cannot close", "module");
            ok = FALSE;
        }
#endif
    }
    Free(*module);
    return ok;
}

const char* n_module_error(void) {
    return _n_module_last_error;
}

const char* n_module_extension(void) {
#if defined(__windows__)
    return ".dll";
#elif defined(__APPLE__)
    return ".dylib";
#else
    return ".so";
#endif
}