Hati-hati dengan vtable, atau cara memotret diri Anda sendiri dengan memperbarui perpustakaan

Bayangkan Anda sedang mengembangkan aplikasi yang menggunakan semacam perpustakaan bersama. Pustaka dengan hati-hati mengikuti prinsip kompatibilitas mundur, tanpa mengubah antarmuka lama dan hanya menambahkan yang baru. Ternyata meskipun dengan pemikiran ini, memperbarui pustaka tanpa langsung menautkan aplikasi dapat menyebabkan efek yang tidak terduga.





. 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. Menambahkan metode baru di akhir tidak menyelesaikan masalah di semua ABI, jadi lebih baik tidak mengandalkan ini atau menggunakannya dengan hati-hati.
  2. Versi antarmuka melalui pewarisan memecahkan masalah. dempmembuang artikel bagus tentang topik ini https://accu.org/index.php/journals/1718 .



All Articles