从 C++ 中的动态库访问静态 class 成员

Access static class members from dynamic library in c++

首先,这是我的第一个问题,我通常会找到答案,如果有错误,请见谅。

我正在尝试从动态库访问静态 class 的成员。问题是,我不知道为什么,这个静态class成员的内存是不一样的,这取决于我如何访问它。

我有一个主程序执行多项操作并调用多个动态库(使用 dlopen / dlsym)。这些动态库应该访问一些属于主程序的变量。如果这些变量不是静态的,我可以共享一个指针,所以我对这些没有问题。但是,当这些变量是静态的时候,我发现了一些问题。

我 运行 这个程序在 linux 下,g++ 7.5.0 和 glibc 2.27。

这是一个小例子,展示了我遇到的问题: 主程序main.cpp修改静态变量value,调用动态库libShare.so创建一个ShareDerived对象。此对象访问静态变量 value 以打印它。

/*** Main.cpp ***/
#include <dlfcn.h>
#include "ShareBase.hpp"

int StaticClass::value = 1;

int main() {
    StaticClass::value = 3;
    void* hdl = dlopen("./lib/libShare.so", RTLD_LAZY | RTLD_GLOBAL);
    ShareBase*(*obj)() = (ShareBase*(*)())dlsym(hdl,"createObject");
    obj()->BaseValue();
    obj()->DerivedValue();
    delete (obj());
    dlclose(hdl);
    return 0;
}

/*** ShareBase.hpp ***/
#pragma once

class StaticClass {
    public:
    StaticClass() = delete;
    ~StaticClass() = delete;
    static int value;
};
class ShareBase {
    public:
    ShareBase();
    virtual ~ShareBase();
    void BaseValue();
    virtual void DerivedValue() = 0;
};

/*** ShareBase.cpp ***/
#include "ShareBase.hpp"
#include <stdio.h>

ShareBase::ShareBase() {}
ShareBase::~ShareBase() {}
void ShareBase::BaseValue() {
    printf("Base: %d --> %p\n", StaticClass::value, (void*)&StaticClass::value);
}

/*** ShareDerived.hpp ***/
#pragma once
#include "ShareBase.hpp"

class ShareDerived:public ShareBase {
    public:
    ShareDerived();
    virtual ~ShareDerived();
    void DerivedValue();
};

extern "C" {
    ShareBase* createObject() {
        auto *m = new ShareDerived();
        return (ShareBase*)m;
    }
}

/*** ShareDerived.cpp ***/
#include "ShareDerived.hpp"
#include <stdio.h>

ShareDerived::ShareDerived() {}
ShareDerived::~ShareDerived() {}
void ShareDerived::DerivedValue() {
    printf("Derived: %d --> %p\n", StaticClass::value, (void*)&StaticClass::value);
    printf("Derived: ");
    BaseValue();
}

这是我用来编译这个例子的 makefile:

CXX         := g++
CXX_FLAGS   := -Wall -Wextra -std=c++17 -ggdb

BIN         := bin
SRC         := src
INC         := include
LIB         := lib

EXE         := test

all: main lib

main: $(BIN)/$(EXE)
    ar rcs $(LIB)/libMain.a $(BIN)/Main.o $(BIN)/ShareBase.o

lib: $(LIB)/libShare.so

$(BIN)/$(EXE): $(BIN)/Main.o $(BIN)/ShareBase.o
    $(CXX) $(CXX_FLAGS) -I$(INC) -L$(LIB) $^ -o $@ -ldl

$(LIB)/libShare.so: $(BIN)/ShareDerived.o
    $(CXX) $(CXX_FLAGS) -I$(INC) -L$(LIB) -shared $^ -o $@ -lMain

$(BIN)/%.o: $(SRC)/%.cpp
    $(CXX) $(CXX_FLAGS) -I$(INC) -L$(LIB) -fPIC -c -o $@ $<

clean:
    -rm $(BIN)/* $(LIB)/*

这是我获得的输出:

Base: 3 --> 0x55e8df87f010 <-- Calling base function from main, static member value is OK.
Derived: 1 --> 0x7f40b5918090 <-- Why this value (and pointer) is not the same as previous one?
Derived: Base: 1 --> 0x7f40b5918090 <-- Accessing to this value calling Base function from Derived class is neither OK.

所以,我的错在哪里?我可以这样做吗?为什么行为会因您访问静态成员的方式而有所不同?使用相同的指针,调用任何派生的 class 函数,静态变量的值是调用 main 之前保存的值。但是,调用基函数,静态变量的值是你在执行过程中修改的。

如果问题是概念性的;如何让主程序执行多项操作并调用需要访问某些主程序变量的动态库?

这是我的第一个post,所以可能不是很清楚。在这种情况下,请告诉我,我会编辑以使其更清楚。

编辑: 之后,@sam 回答,我修改了 makefile,现在它按预期工作了:

CXX         := g++
CXX_FLAGS   := -Wall -Wextra -std=c++17 -ggdb

BIN         := bin
SRC         := src
INC         := include
LIB         := lib

EXE         := test

all: main lib exec

exec: $(BIN)/$(EXE)

main: $(LIB)/libMain.so

lib: $(LIB)/libShare.so

$(BIN)/$(EXE): $(BIN)/Main.o
    $(CXX) $(CXX_FLAGS) -I$(INC) -L$(LIB) -Wl,-rpath,$(LIB) $^ -o $@ -ldl -lMain

$(LIB)/libMain.so: $(BIN)/ShareBase.o
    $(CXX) $(CXX_FLAGS) -I$(INC) -L$(LIB) -shared $^ -o $@

$(LIB)/libShare.so: $(BIN)/ShareDerived.o
    $(CXX) $(CXX_FLAGS) -I$(INC) -L$(LIB) -Wl,-rpath,$(LIB) -shared $^ -o $@ -lMain

$(BIN)/%.o: $(SRC)/%.cpp
    $(CXX) $(CXX_FLAGS) -I$(INC) -L$(LIB) -fPIC -c -o $@ $<

clean:
    -rm $(BIN)/* $(LIB)/*
$(CXX) $(CXX_FLAGS) -I$(INC) -L$(LIB) -shared $^ -o $@ -lMain

您正在 Main.cpp 直接链接到共享库。

并且您 运行 Main.cpp 作为可执行文件的一部分。

因此,存在静态 class 成员的重复实例。这违反了单一定义规则,并导致未定义的行为。这是对您观察到的行为的解释。

您这样做很可能是因为没有链接 -lMain 显示的程序会出现段错误。发生这种情况的原因完全无关——内联 C-linkage 函数永远不会在共享库中实例化,因为编译器在编译共享库时没有理由实例化它。您可能无法弄清楚原因,但发现链接 Main 解决了段错误,但导致了这种行为。这是一个 classical XY 问题。您的问题本来应该是为什么会出现段错误。