【问题标题】:dlopen() of a shared library results in multiple singletons共享库的 dlopen() 导致多个单例
【发布时间】:2022-10-06 21:17:31
【问题描述】:

我有静态链接到库 B 和 C 的应用程序 A。

我有动态库 D,它静态链接到库 B 和 C 以及动态库 E。

A 使用dlopen() 成功加载 D。

正如预期的那样,当库打开时,作为类的 lib D 中的文件范围变量会运行其构造函数。这些构造函数将自己注册到 lib B 中的单例工厂,他们使用 Instance() 方法找到该工厂。

然后应用程序 A 使用工厂查找这些对象,但没有找到它们。

事实证明,lib D 内的 lib B 中的单例位于应用程序 A 内的 lib B 的不同地址。

换句话说,它不再是单例了。

但是,如果我从动态库 D 的链接行中删除库 B,则库 D 链接正常,但 dlopen() 失败,dlerror() 报告:

 libD.so: undefined symbol: _ZN9Foo312Bar12MyFuncEd

这个符号是在 lib B 中定义的 C++ 方法。

问题:应该很明显,但是,我可以以某种方式构建应用程序,以便它的 lib B 副本对 dlopen()\'d lib D 可见吗?

我正在使用 Fedora31 的 Intel 64 位 CPU 上运行。

一旦在 Linux 上运行,我将在 Win10/11 上面临完全相同的问题。

  • 好像是设计问题。为什么应用程序中包含静态库和.dll该应用程序使用的是什么?
  • 库 B 是具有例如日志记录功能的通用库。该应用程序需要记录状态以及警告和错误。 .dll D 也是如此。库 C 是应用程序 B 和 .dll D 再次需要的特定于应用程序的功能。例如,它包括一个对象工厂。该应用程序要求工厂从配置文件中创建具有字符串名称的对象。 .dll 需要向该工厂注册它可用的对象,因此还需要访问该工厂,这就是必须是单例的特定部分。相反,dll 的对象向他们的工厂注册,然后应用程序看到一个空的。
  • 我认为这种行为是正常的。有 2 个工厂(一个在应用程序中,一个在.dll)。如果记录器本身在一个单独的.dll,没关系,因为每个进程只有一个实例。
  • 不仅仅是记录器(lib B),还有这个生态系统中大多数代码使用的功能(lib C)。你是说如果我把它们都做成了,那么问题就会消失,我只会有一个单身人士吗?或者在链接我的二进制文件时我可以为链接器提供任何其他选项,以便他可以允许 lib D 加载并链接到应用程序中的 B 和 C,而不是需要链接到它自己的副本?需要明确的是,即使它是“正常的”,我的应用程序也无法运行,我需要修复它:-D 我不能只是告诉我的老板它无法运行,而是它\' s \"正常。\"
  • 我不知道有一种方法可以“欺骗”链接器来做你想做的事情(但这并不意味着它不是)。这就是为什么我建议有一个MCVE:一个具有依赖于静态变量(作为静态(巧合)库)的(虚拟)函数的文件,以及 2 个其他文件(.dll和应用程序)使用静态库。重现问题并尝试各种标志以查看是否有任何变化会更容易。也很容易发现使用动态库而不是静态库是否可以解决您的问题。

标签: dll linker-errors dlopen


【解决方案1】:

存在来自静态库的功能独立地在里面:

  • .dll- 从另一个加载时它会起作用应用程序

  • 应用程序- 它在不加载时工作.dll

所以,我发现你遇到的行为很正常(更多:每个人都会发生这种情况.dll包含来自静态的代码进程正在加载)。
这是MCVE([SO]: How to create a Minimal, Reproducible Example (reprex (mcve))) 我在说。

lib00.h

#pragma once

#if defined(_WIN32)
#  if defined(LIB00_STATIC)
#    define LIB00_EXPORT_API
#  else
#    if defined(LIB00_EXPORTS)
#      define LIB00_EXPORT_API __declspec(dllexport)
#    else
#      define LIB00_EXPORT_API __declspec(dllimport)
#    endif
#  endif
#else
#  define LIB00_EXPORT_API
#endif


LIB00_EXPORT_API int libFunc();

lib00.c

#include <inttypes.h>
#include <stdio.h>

#define LIB00_EXPORTS
#include "lib00.h"

static int gVar = 0;

int libFunc()
{
    printf("  %s - var (0x%016lX): %d\n", __FUNCTION__, (uintptr_t)(&gVar), gVar);
    return gVar++;
}

dll00.c

#include <stdio.h>

#include "lib00.h"

#if defined(_WIN32)
#  define DLL00_EXPORT_API __declspec(dllexport)
#else
#  define DLL00_EXPORT_API
#endif


#if defined(__cplusplus)
extern "C" {
#endif

DLL00_EXPORT_API void dllFunc();

#if defined(__cplusplus)
}
#endif


void dllFunc()
{
    printf("Call libFunc from .DLL: %d\n", libFunc());
}

main00.c

#include <stdio.h>

#if defined (_WIN32)
#  include <windows.h>
#  define DLLPTR HMODULE
#  define dlsym GetProcAddress
#  define dlclose FreeLibrary
#else
#  include <dlfcn.h>
#  define DLLPTR void*
#endif

#include "lib00.h"


typedef int (*DllFuncPtr)();


int main(int argc, char **argv)
{
    DLLPTR pDll00 = NULL;
    DllFuncPtr pDllFunc = NULL;
    const int count = 3;
    if (argc > 1) {
#if defined (_WIN32)
        pDll00 = LoadLibrary(argv[1]);
#else
        int dlopen_flags = RTLD_NOW;  //RTLD_LAZY;
        //dlopen_flags |= RTLD_GLOBAL;
        pDll00 = dlopen(argv[1], dlopen_flags);
#endif
        if (pDll00) {
            pDllFunc = (DllFuncPtr)dlsym(pDll00, "dllFunc");
        } else {
            printf("Error loading .dll\n");
#if defined (_WIN32)
            printf("Error: %d\n", GetLastError());
#else
            printf("%s\n", dlerror());
#endif
        }
    }
    for (int i = 0; i < count; ++i) {
        if (pDllFunc)
            pDllFunc();
        printf("Call libFunc from .EXE: %d\n", libFunc());
    }
    if (pDll00)
        dlclose(pDll00);
    printf("\nDone.\n\n");
    return 0;
}

别介意#定义s,他们(大部分)是为了.

输出

(qaic-env) [cfati@cfati-5510-0:/mnt/e/Work/Dev/StackOverflow/q073944078]> ~/sopr.sh
### Set shorter prompt to better fit when pasted in StackOverflow (or other) pages ###

[064bit prompt]> ls
dll00.c  lib00.c  lib00.h  main00.c
[064bit prompt]>
[064bit prompt]> # Build static lib00
[064bit prompt]>
[064bit prompt]> gcc -c -DLIB00_STATIC -o lib00s.o lib00.c
[064bit prompt]> ar rcs lib00s.a lib00s.o
[064bit prompt]> gcc -DLIB00_STATIC -fPIC -shared -o dll00s.so dll00.c lib00s.a
[064bit prompt]> gcc -DLIB00_STATIC -o app00s main00.c -ldl lib00s.a
[064bit prompt]> ls
app00s  dll00.c  dll00s.so  lib00.c  lib00.h  lib00s.a  lib00s.o  main00.c
[064bit prompt]>
[064bit prompt]> ./app00s
  libFunc - var (0x00005580D94BA014): 0
Call libFunc from .EXE: 0
  libFunc - var (0x00005580D94BA014): 1
Call libFunc from .EXE: 1
  libFunc - var (0x00005580D94BA014): 2
Call libFunc from .EXE: 2

Done.

[064bit prompt]> ./app00s ./dll00s.so
  libFunc - var (0x00007F4559918034): 0
Call libFunc from .DLL: 0
  libFunc - var (0x000056083959F014): 0
Call libFunc from .EXE: 0
  libFunc - var (0x00007F4559918034): 1
Call libFunc from .DLL: 1
  libFunc - var (0x000056083959F014): 1
Call libFunc from .EXE: 1
  libFunc - var (0x00007F4559918034): 2
Call libFunc from .DLL: 2
  libFunc - var (0x000056083959F014): 2
Call libFunc from .EXE: 2

Done.

[064bit prompt]>
[064bit prompt]> # Build dynamic lib00
[064bit prompt]>
[064bit prompt]> gcc -fPIC -shared -o lib00.so lib00.c
[064bit prompt]> gcc -fPIC -shared -o dll00.so dll00.c lib00.so
[064bit prompt]> gcc -o app00 main00.c -ldl lib00.so
[064bit prompt]> ls
app00  app00s  dll00.c  dll00.so  dll00s.so  lib00.c  lib00.h  lib00.so  lib00s.a  lib00s.o  main00.c
[064bit prompt]>
[064bit prompt]> ./app00
./app00: error while loading shared libraries: lib00.so: cannot open shared object file: No such file or directory
[064bit prompt]> LD_LIBRARY_PATH=${LD_LIBRARY_PATH}:. ./app00
  libFunc - var (0x00007FDCC6DC202C): 0
Call libFunc from .EXE: 0
  libFunc - var (0x00007FDCC6DC202C): 1
Call libFunc from .EXE: 1
  libFunc - var (0x00007FDCC6DC202C): 2
Call libFunc from .EXE: 2

Done.

[064bit prompt]> LD_LIBRARY_PATH=${LD_LIBRARY_PATH}:. ./app00 ./dll00.so
  libFunc - var (0x00007FE562D1E02C): 0
Call libFunc from .DLL: 0
  libFunc - var (0x00007FE562D1E02C): 1
Call libFunc from .EXE: 1
  libFunc - var (0x00007FE562D1E02C): 2
Call libFunc from .DLL: 2
  libFunc - var (0x00007FE562D1E02C): 3
Call libFunc from .EXE: 3
  libFunc - var (0x00007FE562D1E02C): 4
Call libFunc from .DLL: 4
  libFunc - var (0x00007FE562D1E02C): 5
Call libFunc from .EXE: 5

Done.

因此,在.dll解决问题。
有了这两种构建,我想看看组合事物时会发生什么:

[064bit prompt]> _LD_LIBRARY_PATH=${LD_LIBRARY_PATH}
[064bit prompt]> LD_LIBRARY_PATH=${_LD_LIBRARY_PATH}:.
[064bit prompt]> for g in app00s app00; do for i in dll00s.so dll00.so; do echo ./${g} ./${i}; ./${g} ./${i}; done done
./app00s ./dll00s.so
  libFunc - var (0x00007F5608ED4034): 0
Call libFunc from .DLL: 0
  libFunc - var (0x000055C0AFBCD014): 0
Call libFunc from .EXE: 0
  libFunc - var (0x00007F5608ED4034): 1
Call libFunc from .DLL: 1
  libFunc - var (0x000055C0AFBCD014): 1
Call libFunc from .EXE: 1
  libFunc - var (0x00007F5608ED4034): 2
Call libFunc from .DLL: 2
  libFunc - var (0x000055C0AFBCD014): 2
Call libFunc from .EXE: 2

Done.

./app00s ./dll00.so
  libFunc - var (0x00007FB3DBAB102C): 0
Call libFunc from .DLL: 0
  libFunc - var (0x000055BA0A2C5014): 0
Call libFunc from .EXE: 0
  libFunc - var (0x00007FB3DBAB102C): 1
Call libFunc from .DLL: 1
  libFunc - var (0x000055BA0A2C5014): 1
Call libFunc from .EXE: 1
  libFunc - var (0x00007FB3DBAB102C): 2
Call libFunc from .DLL: 2
  libFunc - var (0x000055BA0A2C5014): 2
Call libFunc from .EXE: 2

Done.

./app00 ./dll00s.so
  libFunc - var (0x00007F52A75A302C): 0
Call libFunc from .DLL: 0
  libFunc - var (0x00007F52A75A302C): 1
Call libFunc from .EXE: 1
  libFunc - var (0x00007F52A75A302C): 2
Call libFunc from .DLL: 2
  libFunc - var (0x00007F52A75A302C): 3
Call libFunc from .EXE: 3
  libFunc - var (0x00007F52A75A302C): 4
Call libFunc from .DLL: 4
  libFunc - var (0x00007F52A75A302C): 5
Call libFunc from .EXE: 5

Done.

./app00 ./dll00.so
  libFunc - var (0x00007FE3C8C9602C): 0
Call libFunc from .DLL: 0
  libFunc - var (0x00007FE3C8C9602C): 1
Call libFunc from .EXE: 1
  libFunc - var (0x00007FE3C8C9602C): 2
Call libFunc from .DLL: 2
  libFunc - var (0x00007FE3C8C9602C): 3
Call libFunc from .EXE: 3
  libFunc - var (0x00007FE3C8C9602C): 4
Call libFunc from .DLL: 4
  libFunc - var (0x00007FE3C8C9602C): 5
Call libFunc from .EXE: 5

Done.

对我来说,结果有点奇怪,正如我所期待的 3rd运行与前 2 个相同。我一定遗漏了有关符号解析的内容。最有可能的是,事情可以通过改变来改变打开标志或通过修改静态象征 (库函数) 可见性(这将需要部分或全部重建)。
我做了同样的事情

[cfati@CFATI-5510-0:e:\Work\Dev\StackOverflow\q073944078]> sopr.bat
### Set shorter prompt to better fit when pasted in StackOverflow (or other) pages ###

[prompt]> "c:\Install\pc032\Microsoft\VisualStudioCommunity\2019\VC\Auxiliary\Build\vcvarsall.bat" x64 > nul

[prompt]> dir /b
app00
app00s
dll00.c
dll00.so
dll00s.so
lib00.c
lib00.h
lib00.so
lib00s.a
lib00s.o
main00.c

[prompt]>
[prompt]> cl -c /nologo /DLIB00_STATIC /MD lib00.c /Folib00s.obj
lib00.c
lib00.c(11): warning C4477: 'printf' : format string '%016lX' requires an argument of type 'unsigned long', but variadic argument 2 has type 'uintptr_t'
lib00.c(11): note: consider using '%llX' in the format string
lib00.c(11): note: consider using '%IX' in the format string
lib00.c(11): note: consider using '%I64X' in the format string

[prompt]> lib /NOLOGO /OUT:lib00s.lib lib00s.obj

[prompt]> cl /nologo /MD /DDLL lib00.c  /link /NOLOGO /DLL /OUT:lib00.dll
lib00.c
lib00.c(11): warning C4477: 'printf' : format string '%016lX' requires an argument of type 'unsigned long', but variadic argument 2 has type 'uintptr_t'
lib00.c(11): note: consider using '%llX' in the format string
lib00.c(11): note: consider using '%IX' in the format string
lib00.c(11): note: consider using '%I64X' in the format string
   Creating library lib00.lib and object lib00.exp

[prompt]>
[prompt]> cl /nologo /MD /DDLL /DLIB00_STATIC dll00.c  /link /NOLOGO /DLL /OUT:dll00s.dll lib00s.lib
dll00.c
   Creating library dll00s.lib and object dll00s.exp

[prompt]> cl /nologo /MD /DDLL dll00.c  /link /NOLOGO /DLL /OUT:dll00.dll lib00.lib
dll00.c
   Creating library dll00.lib and object dll00.exp

[prompt]>
[prompt]> cl /nologo /MD /W0 /DLIB00_STATIC main00.c  /link /NOLOGO /OUT:app00s.exe lib00s.lib
main00.c

[prompt]> cl /nologo /MD /W0 main00.c  /link /NOLOGO /OUT:app00.exe lib00.lib
main00.c

[prompt]>
[prompt]> dir /b
app00
app00.exe
app00s
app00s.exe
dll00.c
dll00.dll
dll00.exp
dll00.lib
dll00.obj
dll00.so
dll00s.dll
dll00s.exp
dll00s.lib
dll00s.so
lib00.c
lib00.dll
lib00.exp
lib00.h
lib00.lib
lib00.obj
lib00.so
lib00s.a
lib00s.lib
lib00s.o
lib00s.obj
main00.c
main00.obj

[prompt]>
[prompt]> for %g in (app00s.exe app00.exe) do (for %i in (dll00s.dll dll00.dll) do (echo %g %i && %g %i))

[prompt]> (for %i in (dll00s.dll dll00.dll) do (echo app00s.exe %i   && app00s.exe %i ) )

[prompt]> (echo app00s.exe dll00s.dll   && app00s.exe dll00s.dll )
app00s.exe dll00s.dll
  libFunc - var (0x000000009D343080): 0
Call libFunc from .DLL: 0
  libFunc - var (0x00000000DFD430D0): 0
Call libFunc from .EXE: 0
  libFunc - var (0x000000009D343080): 1
Call libFunc from .DLL: 1
  libFunc - var (0x00000000DFD430D0): 1
Call libFunc from .EXE: 1
  libFunc - var (0x000000009D343080): 2
Call libFunc from .DLL: 2
  libFunc - var (0x00000000DFD430D0): 2
Call libFunc from .EXE: 2

Done.


[prompt]> (echo app00s.exe dll00.dll   && app00s.exe dll00.dll )
app00s.exe dll00.dll
  libFunc - var (0x0000000099BE3060): 0
Call libFunc from .DLL: 0
  libFunc - var (0x00000000DFD430D0): 0
Call libFunc from .EXE: 0
  libFunc - var (0x0000000099BE3060): 1
Call libFunc from .DLL: 1
  libFunc - var (0x00000000DFD430D0): 1
Call libFunc from .EXE: 1
  libFunc - var (0x0000000099BE3060): 2
Call libFunc from .DLL: 2
  libFunc - var (0x00000000DFD430D0): 2
Call libFunc from .EXE: 2

Done.


[prompt]> (for %i in (dll00s.dll dll00.dll) do (echo app00.exe %i   && app00.exe %i ) )

[prompt]> (echo app00.exe dll00s.dll   && app00.exe dll00s.dll )
app00.exe dll00s.dll
  libFunc - var (0x0000000099BE3080): 0
Call libFunc from .DLL: 0
  libFunc - var (0x000000009D343060): 0
Call libFunc from .EXE: 0
  libFunc - var (0x0000000099BE3080): 1
Call libFunc from .DLL: 1
  libFunc - var (0x000000009D343060): 1
Call libFunc from .EXE: 1
  libFunc - var (0x0000000099BE3080): 2
Call libFunc from .DLL: 2
  libFunc - var (0x000000009D343060): 2
Call libFunc from .EXE: 2

Done.


[prompt]> (echo app00.exe dll00.dll   && app00.exe dll00.dll )
app00.exe dll00.dll
  libFunc - var (0x000000009D343060): 0
Call libFunc from .DLL: 0
  libFunc - var (0x000000009D343060): 1
Call libFunc from .EXE: 1
  libFunc - var (0x000000009D343060): 2
Call libFunc from .DLL: 2
  libFunc - var (0x000000009D343060): 3
Call libFunc from .EXE: 3
  libFunc - var (0x000000009D343060): 4
Call libFunc from .DLL: 4
  libFunc - var (0x000000009D343060): 5
Call libFunc from .EXE: 5

Done.

在这里,事情看起来和我期望的一样。

【讨论】:

    猜你喜欢
    • 2011-03-22
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-06-30
    • 2019-08-23
    • 1970-01-01
    • 1970-01-01
    • 2018-03-23
    相关资源
    最近更新 更多