vtable、またはライブラリを更新して自分の足を撃つ方法に注意してください

ある種の共有ライブラリを使用するアプリケーションを開発していると想像してください。ライブラリは、古いインターフェイスを変更したり、新しいインターフェイスを追加したりすることなく、下位互換性の原則に注意深く従っています。これを念頭に置いても、アプリケーションを直接リンクせずにライブラリを更新すると、予期しない影響が生じる可能性があることがわかりました。





. clang 10.0.0 Arch Linux, , , gcc, MSVC .



. , , - . , ( , , ). , , - : -, , , . , . .



: shared-, , -, , header . , :



Shared-



  • CMakeLists.txt


cmake_minimum_required(VERSION 3.5)

project(shared_lib LANGUAGES CXX)

set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

set(SOURCES lib.cpp)
set(HEADERS lib.h)

add_library(${PROJECT_NAME} SHARED ${SOURCES} ${HEADERS})


  • lib.h


#ifndef LIB_H
#define LIB_H

namespace my
{

class Interface
{
public:
    virtual ~Interface() = default;

    virtual void a() = 0;
    virtual void c() = 0;
};

class Implementation : public Interface
{
public:
    void a() override;
    void c() override;
};

} // namespace my

#endif // LIB_H


  • lib.cpp


#include "lib.h"

#include <iostream>

namespace my
{

void Implementation::a()
{
    std::cout << "Implementation::a()" << std::endl;
}

void Implementation::c()
{
    std::cout << "Implementation::c()" << std::endl;
}

} // namespace my


-



  • CMakeLists.txt


cmake_minimum_required(VERSION 3.5)

project(client LANGUAGES CXX)

set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

set(libshared_binary_dir "/path/to/libshared_lib.so")
set(libshared_source_dir "/path/to/shared_lib/source")

add_executable(${PROJECT_NAME} main.cpp)

add_library(shared_lib SHARED IMPORTED)
set_property(TARGET shared_lib PROPERTY IMPORTED_LOCATION ${libshared_binary_dir}/libshared_lib.so)

target_include_directories(${PROJECT_NAME} PRIVATE ${libshared_source_dir})
target_link_libraries(${PROJECT_NAME} PRIVATE shared_lib)


  • main.cpp


#include <lib.h>

#include <memory>

int main()
{
    std::unique_ptr<my::Interface> ptr = std::make_unique<my::Implementation>();
    ptr->a(); 
    ptr->c(); 
}


-, , :



Implementation::a()
Implementation::c()


, . :



  • lib.h


#ifndef LIB_H
#define LIB_H

namespace my
{

class Interface
{
public:
    virtual ~Interface() = default;

    virtual void a() = 0;
    virtual void b() = 0; // +
    virtual void c() = 0;
};

class Implementation : public Interface
{
public:
    void a() override;
    void b() override;    // +
    void c() override;
};

} // namespace my

#endif // LIB_H


  • lib.cpp


#include "lib.h"

#include <iostream>

namespace my
{

void Implementation::a()
{
    std::cout << "Implementation::a()" << std::endl;
}

void Implementation::b()                             // +
{                                                    // +
    std::cout << "Implementation::b()" << std::endl; // +
}                                                    // +

void Implementation::c()
{
    std::cout << "Implementation::c()" << std::endl;
}

} // namespace my


, . , -, , b(), , so- . , , , , , , . :



Implementation::a()
Implementation::b()


- : c(), b()! , . , .



, ? , Interface : a() c(). header-. , , , , ABI, ( , ). c() , vtable ( a()). ! b(), , c() , .



b() c() . b() , , ( ). , - . , b() , , , . , , - , : , , , , . , vtable.



, dlopen. :



  • CMakeLists.txt


cmake_minimum_required(VERSION 3.5)

project(dynamic_client LANGUAGES CXX)

set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

set(libshared_source_dir "SOURCE_DIR")

add_executable(${PROJECT_NAME} main.cpp)

target_include_directories(${PROJECT_NAME} PRIVATE ${libshared_source_dir})


  • main.cpp


#include <lib.h>

#include <dlfcn.h>

#include <cassert>

int main()
{
    void* handle = ::dlopen("/path/to/libshared_lib.so", RTLD_NOW);
    assert(handle != nullptr);
    using make_instance_t = my::Interface* ();
    make_instance_t* function = reinterpret_cast<make_instance_t*>(::dlsym(handle, "make_instance"));
    assert(function != nullptr);

    my::Interface* ptr = function();
    ptr->a(); // Implementation::a() with both old and new shared library
    ptr->c(); // Implementation::c() with old, Implementation::b() with new shared library

    delete ptr;
    ::dlclose(handle);
}


make_instance():



  • lib.h


#ifndef LIB_H
#define LIB_H

// ...

extern "C"
{
    my::Interface* make_instance();
}

#endif // LIB_H


  • lib.cpp


#include "lib.h"

// ...

my::Interface* make_instance()
{
    return new my::Implementation();
}

// ...


, , , , , : vtable , . , , , . , !



P.S. ilammy, aamonster demp:



  1. 最後に新しいメソッドを追加しても、すべてのABIの問題が解決するわけではないため、これに依存したり、注意して使用したりしないことをお勧めします。
  2. 継承によるインターフェースのバージョン管理は問題を解決します。 dempこのトピックに関する良い記事を捨てましたhttps://accu.org/index.php/journals/1718



All Articles