Affix
view release on metacpan or search on metacpan
infix/src/core/loader.c view on Meta::CPAN
/**
* Copyright (c) 2025 Sanko Robinson
*
* This source code is dual-licensed under the Artistic License 2.0 or the MIT License.
* You may choose to use this code under the terms of either license.
*
* SPDX-License-Identifier: (Artistic-2.0 OR MIT)
*
* The documentation blocks within this file are licensed under the
* Creative Commons Attribution 4.0 International License (CC BY 4.0).
*
* SPDX-License-Identifier: CC-BY-4.0
*/
/**
* @file loader.c
* @brief Implements cross-platform dynamic library loading.
* @ingroup internal_core
*
* This module provides a platform-agnostic API for opening shared libraries
* (`.dll`, `.so`, `.dylib`), looking up symbols within them, and reading or
* writing to exported global variables. It abstracts away the differences
* between the Windows API (`LoadLibrary`, `GetProcAddress`) and the POSIX API
* (`dlopen`, `dlsym`).
*
* The functions `infix_read_global` and `infix_write_global` combine this dynamic
* loading capability with the `infix` type system to safely interact with global
* variables of any type described by a signature string.
*/
#include "common/infix_internals.h"
#if defined(INFIX_OS_WINDOWS)
#include <windows.h>
#else
#include <dlfcn.h>
#endif
/**
* @brief Opens a dynamic library and returns a handle to it.
*
* This function is a cross-platform wrapper around `LoadLibraryA` (Windows) and
* `dlopen` (POSIX). On failure, it sets the thread-local error state with
* detailed system-specific information using `_infix_set_system_error`.
*
* @param[in] path The file path to the library (e.g., `"./mylib.so"`, `"user32.dll"`).
* @return A pointer to an `infix_library_t` handle on success, or `nullptr` on failure.
* The returned handle must be freed with `infix_library_close`.
*/
INFIX_API INFIX_NODISCARD infix_library_t * infix_library_open(const char * path) {
infix_library_t * lib = infix_calloc(1, sizeof(infix_library_t));
if (lib == nullptr) {
_infix_set_error(INFIX_CATEGORY_ALLOCATION, INFIX_CODE_OUT_OF_MEMORY, 0);
return nullptr;
}
#if defined(INFIX_OS_WINDOWS)
// On Windows, passing NULL to GetModuleHandleA returns a handle to the main executable file of the current process.
if (path == nullptr) {
lib->handle = GetModuleHandleA(path);
lib->is_pseudo_handle = true; // Mark this as a special, non-freeable handle.
}
else {
lib->handle = LoadLibraryA(path);
lib->is_pseudo_handle = false; // This is a regular, ref-counted handle.
}
#else
// Use RTLD_LAZY for performance (resolve symbols only when they are first used).
// Use RTLD_LOCAL to keep symbols isolated within this handle. This prevents
// symbol pollution if multiple plugins link against different versions of the same
// dependency. Dependencies must be explicitly linked or loaded.
// Use RTLD_GLOBAL to make symbols from this library available for resolution
// by other libraries that might be loaded later. This is important for complex
// dependency chains.
// On POSIX, passing NULL to dlopen returns a handle to the main executable.
lib->handle = dlopen(path, RTLD_LAZY | RTLD_LOCAL);
#endif
if (lib->handle == nullptr) {
#if defined(INFIX_OS_WINDOWS)
// On Windows, GetLastError() provides the specific error code.
_infix_set_system_error(INFIX_CATEGORY_GENERAL, INFIX_CODE_LIBRARY_LOAD_FAILED, GetLastError(), nullptr);
#else
// On POSIX, dlerror() returns a human-readable string.
_infix_set_system_error(INFIX_CATEGORY_GENERAL, INFIX_CODE_LIBRARY_LOAD_FAILED, 0, dlerror());
#endif
infix_free(lib);
return nullptr;
}
return lib;
}
/**
* @brief Closes a dynamic library handle and frees associated resources.
*
* This is a cross-platform wrapper around `FreeLibrary` (Windows) and `dlclose` (POSIX).
*
* @param[in] lib The library handle to close. It is safe to call this function with `nullptr`.
*/
INFIX_API void infix_library_close(infix_library_t * lib) {
if (lib == nullptr)
return;
if (lib->handle) {
#if defined(INFIX_OS_WINDOWS)
// Only call FreeLibrary on real handles obtained from LoadLibrary.
// Never call it on a pseudo-handle from GetModuleHandle.
if (!lib->is_pseudo_handle)
FreeLibrary((HMODULE)lib->handle);
#else
dlclose(lib->handle);
#endif
}
infix_free(lib);
}
/**
* @brief Retrieves the address of a symbol (function or variable) from a loaded library.
*
* This is a cross-platform wrapper around `GetProcAddress` (Windows) and `dlsym` (POSIX).
*
* @note On POSIX, `dlsym` returning `NULL` is not a definitive error condition, as a
( run in 1.287 second using v1.01-cache-2.11-cpan-39bf76dae61 )