Search code examples
dynamic-linkingdynamic-library

Accessing an external variable from a C library


I am currently learning C and am trying to understand the possibilities of dynamic libraries. My current question is, if I have a simple "Hello World" application in C called "ProgA", and this program dynamically loads a shared library with some example code called "LibB", can LibB access a global variable in ProgA, which was declared as external?

Given is the following example code for demonstration of the problem:

file header.h

#ifndef TEST_H
#define TEST_H

typedef struct test_import_s {
    int some_field;
} test_import_t;

extern test_import_t newtestimport;

#endif

file prog_a.c

#include <stdio.h>
#include <windows.h>
#include "header.h"

test_import_t newtestimport = {
    .some_field = 42
};

int main()
{
    HINSTANCE hinstLib;
    typedef void (*FunctionPointer)();

    newtestimport.some_field = 42;

    hinstLib = LoadLibrary("lib_b.dll");
    if (hinstLib != NULL)
    {
        FunctionPointer initialize_lib_b;
        initialize_lib_b = (FunctionPointer)GetProcAddress(hinstLib, "initialize_lib_b");

        if (initialize_lib_b != NULL)
        {
            initialize_lib_b();
        }

        FreeLibrary(hinstLib);
    }

    return 0;
}

file lib_b.c

#include <stdio.h>
#include "header.h"

test_import_t *timp;

void initialize_lib_b() {
    timp = &newtestimport;
    int some_field = timp->some_field;
    printf("Result from function: %d\n", some_field);
}

file CMakeLists.txt

cmake_minimum_required(VERSION 3.24)
project(dynamic-library-2 C)

set(CMAKE_C_STANDARD 23)

add_library(lib_b SHARED lib_b.c)

set_target_properties(lib_b PROPERTIES PREFIX "" OUTPUT_NAME "lib_b")

add_executable(prog_a prog_a.c)

target_link_libraries(prog_a lib_b)

In the above example, the headerfile header.h defines the struct test_import_t and an external variable newtestimport using this struct. In the C file of the main program prog_a.c one property of this struct is assigned the value 42. It then dynamically loads the library lib_b.c using the Windows API and executes a function in it. The function then should access the variable newtestimport of the main program and print out the value of the variable (42).

This example does not work. The compiler throws the following error:

====================[ Build | prog_a | Debug ]==================================
C:\Users\user1\AppData\Local\JetBrains\Toolbox\apps\CLion\ch-0\223.8617.54\bin\cmake\win\x64\bin\cmake.exe --build C:\Users\user1\projects\learning-c\cmake-build-debug --target prog_a -j 9
[1/2] Linking C shared library dynamic-library-2\lib_b.dll
FAILED: dynamic-library-2/lib_b.dll dynamic-library-2/liblib_b.dll.a
cmd.exe /C "cd . && C:\Users\user1\AppData\Local\JetBrains\Toolbox\apps\CLion\ch-0\223.8617.54\bin\mingw\bin\gcc.exe -fPIC -g  -Wl,--export-all-symbols -shared -o dynamic-library-2\lib_b.dll -Wl,--out-implib,dynamic-library-2\liblib_b.dll.a -Wl,--major-image-version,0,--minor-image-version,0 dynamic-library-2/CMakeFiles/lib_b.dir/lib_b.c.obj  -lkernel32 -luser32 -lgdi32 -lwinspool -lshell32 -lole32 -loleaut32 -luuid -lcomdlg32 -ladvapi32 && cd ."
C:\Users\user1\AppData\Local\JetBrains\Toolbox\apps\CLion\ch-0\223.8617.54\bin\mingw\bin/ld.exe: dynamic-library-2/CMakeFiles/lib_b.dir/lib_b.c.obj:lib_b.c:(.rdata$.refptr.newtestimport[.refptr.newtestimport]+0x0): undefined reference to `newtestimport'
collect2.exe: error: ld returned 1 exit status
ninja: build stopped: subcommand failed.

How can the example be fixed to accomplish the described goal?


Solution

  • Windows DLLs are self-contained, and can not have undefined references similar to newtestimport, unless these references are satisfied by another DLL.

    How can the example be fixed to accomplish the described goal?

    The best fix is to pass the address of newtestimport into the function that needs it (initialize_lib_b() here).

    If for some reason you can't do that, your next best option is to define the newtestimport as a dllexport variable in another DLL, e.g. lib_c.dll.

    Then both the main executable and lib_b.dll would be linked against lib_c.lib, and would both use that variable from lib_c.dll.

    P.S. Global variables are a "code smell" and a significant source of bugs. You should avoid them whenever possible, and in your example there doesn't seem to be any good reason to use them.