Functionality from the static library exists independently in the:
So, I find the behavior you're experiencing normal (more: this would happen for each .dll containing the code from the static lib that is being loaded by the process).
Here's the MCVE ([SO]: How to create a Minimal, Reproducible Example (reprex (mcve))) that I was talking about (gVar (from lib00.c) could play the Singleton's RefCount role).
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
#if defined(__cplusplus)
extern "C" {
#endif
LIB00_EXPORT_API int libFunc();
#if defined(__cplusplus)
}
#endif
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); // @TODO - cfati: technically, 1st arg yields UB (on Win)
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;
}
Don't mind the #defines, they are (mostly) for Win.
Output:
(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.
So, moving functionality in a .dll solves the problem.
Having both builds, I wanted to see what happens when combining things:
[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.
For me, results are a bit odd, as I was expecting the 3rd run to be identical to the previous 2. I must be missing something about symbol resolution (although the Update #0 section (at the end) sheds some light). Most likely, things could be altered by:
Changing dlopen flags
Modifying the static lib symbol (libFunc) visibility
Other ways (again, check Update #0 section at the end)
but most of them would require partial or full rebuild.
I did the same thing on Win:
[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.
Here, things look as I would expect them to be.
AFAIC this is a PoC for .dll (.so) existence. Code (and data) resides in a (shared) location where it's accessed from by all its clients. No surprises.
An use-case that comes into my mind is upgrading (or modifying) the .lib:
static: recompile everything that links to it (and it might go further, recursively) - which is a nightmare
dynamic: simply replace the binary (assuming that it's backward compatible (API / ABI))
Update #0
[SO]: Multiple instances of singleton across shared libraries on Linux (@EmployedRussian's answer) (that this question was marked as a duplicate of) solves your problem too. I wonder how come I missed -rdynamic ([GNU.GCC]: Options for Linking, --export-dynamic ([SourceWare]: Command Line Options)), as I've used it in the past X( .
[064bit prompt]> gcc -DLIB00_STATIC -o app00srdyn main00.c -ldl -rdynamic lib00s.a
[064bit prompt]>
[064bit prompt]> ./app00srdyn ./dll00s.so
libFunc - var (0x0000560504168014): 0
Call libFunc from .DLL: 0
libFunc - var (0x0000560504168014): 1
Call libFunc from .EXE: 1
libFunc - var (0x0000560504168014): 2
Call libFunc from .DLL: 2
libFunc - var (0x0000560504168014): 3
Call libFunc from .EXE: 3
libFunc - var (0x0000560504168014): 4
Call libFunc from .DLL: 4
libFunc - var (0x0000560504168014): 5
Call libFunc from .EXE: 5
Done.
[064bit prompt]> ./app00srdyn ./dll00.so
libFunc - var (0x000056162E6C2014): 0
Call libFunc from .DLL: 0
libFunc - var (0x000056162E6C2014): 1
Call libFunc from .EXE: 1
libFunc - var (0x000056162E6C2014): 2
Call libFunc from .DLL: 2
libFunc - var (0x000056162E6C2014): 3
Call libFunc from .EXE: 3
libFunc - var (0x000056162E6C2014): 4
Call libFunc from .DLL: 4
libFunc - var (0x000056162E6C2014): 5
Call libFunc from .EXE: 5
Done.