Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 33 additions & 0 deletions .github/workflows/CI.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ on:
- include/*
- tests/*
- src/*
- example/*

pull_request:
paths:
Expand All @@ -16,6 +17,7 @@ on:
- include/*
- tests/*
- src/*
- example/*

defaults:
run:
Expand All @@ -38,6 +40,37 @@ jobs:
- name: Check formatting
run: find src include tests -name '*.cpp' -o -name '*.hpp' | xargs clang-format --dry-run --Werror

example:
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]

name: example / ${{ matrix.os }}
runs-on: ${{ matrix.os }}

steps:
- uses: actions/checkout@v4

# FETCHCONTENT_SOURCE_DIR_DYLIB makes FetchContent use the checked out
# sources instead of the released tag, so that the example is built
# against the code of the current branch.
- name: Generate project files
run: cmake example -B example/build -DFETCHCONTENT_SOURCE_DIR_DYLIB="${{ github.workspace }}"

- name: Build example
run: cmake --build example/build --config Release

# Multi config generators place the binaries in a per config subdirectory,
# and the example loads its dynamic library from the working directory.
- name: Run example
working-directory: example/build
run: |
if [ -d Release ]; then
cd Release
fi
./dylib_example

windows_msvc:
strategy:
fail-fast: false
Expand Down
14 changes: 13 additions & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,6 +1,18 @@
cmake_minimum_required(VERSION 3.11...3.31)

project(dylib VERSION 3.0.1 LANGUAGES CXX)
file(READ "${CMAKE_CURRENT_SOURCE_DIR}/include/dylib.hpp" DYLIB_HEADER)

foreach(part MAJOR MINOR PATCH)
if(NOT DYLIB_HEADER MATCHES "#define DYLIB_VERSION_${part} ([0-9]+)")
message(FATAL_ERROR "Could not parse DYLIB_VERSION_${part} from include/dylib.hpp")
endif()
set(DYLIB_VERSION_${part} ${CMAKE_MATCH_1})
endforeach()

project(dylib
VERSION ${DYLIB_VERSION_MAJOR}.${DYLIB_VERSION_MINOR}.${DYLIB_VERSION_PATCH}
LANGUAGES CXX
)

include(GNUInstallDirs)

Expand Down
17 changes: 14 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# dylib

[![version](https://img.shields.io/badge/Version-3.0.1-blue.svg)](https://github.com/martin-olivier/dylib/releases/tag/v3.0.1)
[![version](https://img.shields.io/badge/Version-3.1.0-blue.svg)](https://github.com/martin-olivier/dylib/releases/tag/v3.1.0)
[![license](https://img.shields.io/badge/License-MIT-orange.svg)](https://github.com/martin-olivier/dylib/blob/main/LICENSE)
[![cpp](https://img.shields.io/badge/Compatibility-C++11-darkgreen.svg)](https://isocpp.org)
[![ci](https://github.com/martin-olivier/dylib/actions/workflows/CI.yml/badge.svg)](https://github.com/martin-olivier/dylib/actions/workflows/CI.yml)
Expand Down Expand Up @@ -28,7 +28,7 @@ vcpkg install dylib
```

```sh
conan install --requires=dylib/3.0.1
conan install --requires=dylib/3.1.0
```

### Using CMake Fetch
Expand All @@ -41,7 +41,7 @@ include(FetchContent)
FetchContent_Declare(
dylib
GIT_REPOSITORY "https://github.com/martin-olivier/dylib"
GIT_TAG "v3.0.1"
GIT_TAG "v3.1.0"
)

FetchContent_MakeAvailable(dylib)
Expand Down Expand Up @@ -219,6 +219,17 @@ try {
}
```

### Version

`dylib.hpp` exposes its version as macros, which lets you detect the availability of a feature at compile time:

```c++
#if DYLIB_VERSION_MAJOR > 3 || (DYLIB_VERSION_MAJOR == 3 && DYLIB_VERSION_MINOR >= 1)
for (auto &section : lib.sections())
std::cout << section << std::endl;
#endif
```

## Example

A full example about the usage of the `dylib` library is available [HERE](example)
Expand Down
2 changes: 1 addition & 1 deletion example/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ include(FetchContent)
FetchContent_Declare(
dylib
GIT_REPOSITORY "https://github.com/martin-olivier/dylib"
GIT_TAG "v3.0.1"
GIT_TAG "v3.1.0"
)

FetchContent_MakeAvailable(dylib)
Expand Down
2 changes: 1 addition & 1 deletion example/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ You will have the following result:

```sh
Hello World!
dylib - v3.0.1
dylib - v3.1.0
pi value: 3.14159
magic value: cafebabe
10 + 10 = 20
Expand Down
2 changes: 1 addition & 1 deletion example/lib.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ namespace example {

namespace dylib {
LIB_EXPORT std::string info() {
return "dylib - v3.0.1";
return "dylib - v3.1.0";
}
}

Expand Down
32 changes: 18 additions & 14 deletions include/dylib.hpp
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/**
* @file dylib.hpp
* @version 3.0.1
* @version 3.1.0
* @brief C++ cross-platform wrapper around dynamic loading of shared libraries
* @link https://github.com/martin-olivier/dylib
*
Expand All @@ -12,6 +12,21 @@

#pragma once

#define DYLIB_VERSION_MAJOR 3
#define DYLIB_VERSION_MINOR 1
#define DYLIB_VERSION_PATCH 0

#ifdef _WIN32
#define DYLIB_WIN_MAC_OTHER(win_def, mac_def, other_def) win_def
#define DYLIB_WIN_OTHER(win_def, other_def) win_def
#elif defined(__APPLE__)
#define DYLIB_WIN_MAC_OTHER(win_def, mac_def, other_def) mac_def
#define DYLIB_WIN_OTHER(win_def, other_def) other_def
#else
#define DYLIB_WIN_MAC_OTHER(win_def, mac_def, other_def) other_def
#define DYLIB_WIN_OTHER(win_def, other_def) other_def
#endif

#include <cstdint>
#include <stdexcept>
#include <string>
Expand Down Expand Up @@ -42,17 +57,6 @@
#endif
#endif

#ifdef _WIN32
#define DYLIB_WIN_MAC_OTHER(win_def, mac_def, other_def) win_def
#define DYLIB_WIN_OTHER(win_def, other_def) win_def
#elif defined(__APPLE__)
#define DYLIB_WIN_MAC_OTHER(win_def, mac_def, other_def) mac_def
#define DYLIB_WIN_OTHER(win_def, other_def) other_def
#else
#define DYLIB_WIN_MAC_OTHER(win_def, mac_def, other_def) other_def
#define DYLIB_WIN_OTHER(win_def, other_def) other_def
#endif

namespace dylib {

using native_handle_type = DYLIB_WIN_OTHER(HINSTANCE, void *);
Expand Down Expand Up @@ -316,12 +320,12 @@ class library {
/**
* @return the dynamic library handle
*/
native_handle_type native_handle() noexcept;
native_handle_type native_handle() const noexcept;

protected:
native_handle_type m_handle{nullptr};
#ifndef _WIN32
int m_fd{-1};
std::string m_path;
#endif
};

Expand Down
63 changes: 48 additions & 15 deletions src/dylib.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,6 @@ struct internal_symbol_info {

std::vector<internal_symbol_info> get_symbols(native_handle_type handle, int fd);
std::vector<std::string> get_sections(native_handle_type handle, int fd);
std::string demangle_symbol(const char *symbol);

static native_handle_type open_lib(const char *path) noexcept {
#ifdef _WIN32
Expand Down Expand Up @@ -90,18 +89,42 @@ static std::string get_error_description() noexcept {
#endif
}

#ifndef _WIN32
class scoped_fd {
public:
explicit scoped_fd(const std::string &path) : m_fd(open(path.c_str(), O_RDONLY)) {
if (m_fd < 0)
throw std::runtime_error("Could not open file '" + path + "': " + strerror(errno));
}

scoped_fd(const scoped_fd &) = delete;
scoped_fd &operator=(const scoped_fd &) = delete;

~scoped_fd() {
close(m_fd);
}

int get() const noexcept {
return m_fd;
}

private:
int m_fd;
};
#endif

library::library(library &&other) noexcept {
std::swap(m_handle, other.m_handle);
#ifndef _WIN32
std::swap(m_fd, other.m_fd);
std::swap(m_path, other.m_path);
#endif
}

library &library::operator=(library &&other) noexcept {
if (this != &other) {
std::swap(m_handle, other.m_handle);
#ifndef _WIN32
std::swap(m_fd, other.m_fd);
std::swap(m_path, other.m_path);
#endif
}
return *this;
Expand Down Expand Up @@ -141,9 +164,7 @@ library::library(const char *lib_path, dylib::decorations decorations) {
throw load_error("Could not load library '" + lib + "':\n" + get_error_description());

#ifndef _WIN32
m_fd = open(lib.c_str(), O_RDONLY);
if (m_fd < 0)
throw load_error("Could not open file '" + lib + "':\n" + strerror(errno));
m_path = lib;
#endif
}

Expand All @@ -158,10 +179,6 @@ library::library(const std::filesystem::path &lib_path, decorations decorations)
library::~library() {
if (m_handle)
close_lib(m_handle);
#ifndef _WIN32
if (m_fd > -1)
close(m_fd);
#endif
}

native_symbol_type library::get_symbol(const char *symbol_name) const {
Expand All @@ -186,10 +203,14 @@ native_symbol_type library::get_symbol(const char *symbol_name) const {
initial_error = get_error_description();

for (const auto &sym : symbols()) {
if (!sym.loadable)
/*
* Only C++ symbols are considered here: a C symbol is not mangled, so it would
* already have been resolved by the locate_symbol call above.
*/
if (!sym.loadable || sym.type != symbol_type::CPP)
continue;

std::string demangled = demangle_symbol(sym.name.c_str());
const std::string &demangled = sym.demangled_name;

if (demangled.find(symbol_name) == 0 &&
(demangled.size() == symbol_name_len || demangled[symbol_name_len] == '('))
Expand All @@ -215,7 +236,7 @@ native_symbol_type library::get_symbol(const std::string &symbol_name) const {
return get_symbol(symbol_name.c_str());
}

native_handle_type library::native_handle() noexcept {
native_handle_type library::native_handle() const noexcept {
return m_handle;
}

Expand All @@ -227,7 +248,13 @@ std::vector<symbol_info> library::symbols() const {
throw std::logic_error("Attempted to use a moved library object");

try {
internal_symbols = get_symbols(m_handle, DYLIB_WIN_MAC_OTHER(-1, m_fd, -1));
#ifdef __APPLE__
scoped_fd fd(m_path);

internal_symbols = get_symbols(m_handle, fd.get());
#else
internal_symbols = get_symbols(m_handle, -1);
#endif

symbols.reserve(internal_symbols.size());

Expand All @@ -251,7 +278,13 @@ std::vector<std::string> library::sections() const {
throw std::logic_error("Attempted to use a moved library object");

try {
return get_sections(m_handle, DYLIB_WIN_MAC_OTHER(-1, m_fd, m_fd));
#ifdef _WIN32
return get_sections(m_handle, -1);
#else
scoped_fd fd(m_path);

return get_sections(m_handle, fd.get());
#endif
} catch (const std::runtime_error &e) {
throw section_collection_error(e.what());
}
Expand Down
21 changes: 16 additions & 5 deletions src/symbols.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -385,13 +385,24 @@ std::vector<internal_symbol_info> get_symbols(void *handle, int fd) {
if (!symtab || !strtab || symentries == 0)
return symbols_list;

size = strtab - (char *)symtab;
/*
* The dynamic section does not record the size of the symbol table, so it is
* deduced from the fact that the string table usually directly follows it.
* Bail out instead of underflowing if a linker lays them out the other way.
*/
if (strtab <= (const char *)symtab)
return symbols_list;

size = (unsigned long)(strtab - (char *)symtab);

for (int i = 0; i < size / symentries; ++i) {
ElfSym *sym = &symtab[i];
for (unsigned long i = 0; i < size / symentries; ++i) {
unsigned char type = DYLIB_ELF_ST_TYPE(symtab[i].st_info);

if (DYLIB_ELF_ST_TYPE(symtab[i].st_info) == STT_FUNC) {
const char *name = &strtab[sym->st_name];
/*
* Collect functions (STT_FUNC) and global variables (STT_OBJECT)
*/
if (type == STT_FUNC || type == STT_OBJECT) {
const char *name = &strtab[symtab[i].st_name];

add_symbol(symbols_list, name, !!dlsym(handle, name));
}
Expand Down
2 changes: 2 additions & 0 deletions tests/lib.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@ LIB_EXPORT void list_add_string(std::vector<std::string> &cont, std::string elem
}

namespace tools {
LIB_EXPORT double pi_value = 3.14159;

LIB_EXPORT double adder() {
return 0;
}
Expand Down
Loading
Loading