Stellen Sie sich vor, Sie entwickeln eine Anwendung, die eine Art gemeinsam genutzte Bibliothek verwendet. Die Bibliothek folgt sorgfältig den Prinzipien der Abwärtskompatibilität, ohne die alte Schnittstelle zu ändern und nur eine neue hinzuzufügen. Es stellt sich heraus, dass selbst in diesem Sinne das Aktualisieren der Bibliothek ohne direkte Verknüpfung der Anwendung zu unerwarteten Effekten führen kann.
. 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 , . , , , . , !
- Das Hinzufügen neuer Methoden am Ende löst das Problem nicht bei allen ABIs. Es ist daher besser, sich nicht darauf zu verlassen oder es mit Vorsicht anzuwenden.
- Die Versionierung der Schnittstelle durch Vererbung löst das Problem. dempwarf einen guten Artikel zu diesem Thema https://accu.org/index.php/journals/1718 .