How should I go about making a function from a library be defined by a user?

Issue

To put some context, I trying to make a library, which defines an entry point and would then call a user-defined main for the execution of the actual user-application.

extern int app_main();
mylib_main(struct android_app* app)
{
...
    // call app_main()
...
}

In the user code you would define app_main(){...}, and just linking to the library you could compile to it and do whatever necessary without user interaction to it and get it running.

The issue right now is that it can run if all code is compiled together as a single library, as it does find the extern function in the sources. But it does not if I compile “my_library” into an actual shared library and the user-app into another shared library or executable that links against the former.

I don’t think this a “good” way to go about things but I also don’t really know which other way to go about it.


EDIT: Minimal Reproducible Example (I did not know about this, thanks for pointing out as a good practice):

My Lib provides the android entry point and calls for a user main() setup as an extern right now. MyLib.h

// Export Markers
#ifndef MyLib
#   ifdef _WIN32
#       if defined(FL_BUILD_SHARED) /* build dll */
#           define MyLib __declspec(dllexport)
#       elif !defined(FL_BUILD_STATIC) /* use dll */
#           define MyLib __declspec(dllimport)
#       else /* static library */
#           define MyLib
#       endif
#   else
#       define MyLib
#   endif
#endif

MyLib bool MyLibTest();
MyLib void PrintLog (int lt, const char* log);

MyLib.cpp

#include "mylib.h"

#include <android_native_app_glue.h>
#include <android/log.h>

void handle_android_cmd(struct android_app* app, int32_t cmd)
{}
int32_t handle_android_input(struct android_app* app, AInputEvent* ev)
{return 0;}

extern int main();

void PrintLog(int lt, const char* log)
{
    __android_log_print(lt, "MyAppTest", log);
}

void android_main(struct android_app* app)
{
    PrintLog(4, "Android Flylib Start"); //4 is Info Log

    app->onAppCmd  handle_android_cmd;
    app->onInputEvent  handle_android_input;

    main(); // Call the user main
}

bool MyLibTest() {return true;}

MyApp.cpp

#include <mylib.h>

int main()
{
    bool test  false;
    test  MyLibTest();
    if(test  false)
        return 1;
    else
        return 0;
}

CMakeLists.txt

cmake_minimum_required(VERSION 3.1) #require a minimum cmake version to build

project(MyApp LANGUAGES C CXX)

set(CMake_Modules ${CMAKE_CURRENT_SOURCE_DIR}/cmake_modules)
include(${CMake_Modules}/Helpers.cmake)
include(${CMake_Modules}/FindAndroidSDKVars.cmake)
include(${CMake_Modules}/SetupAndroidEnv.cmake) # Sets up compiler, flags, and directory variables for android NDK

add_library(MyApp SHARED ${CMAKE_CURRENT_SOURCE_DIR}/MyApp/MyApp.cpp)
file(GLOB MyLib_SRC ${CMAKE_CURRENT_SOURCE_DIR}/MyLib/*.cpp)

set(APP_GLUE_SRC "${NDK}/sources/android/native_app_glue/android_native_app_glue.c")
set(NDKINCLUDE ${NDK}/toolchains/llvm/prebuilt/linux-x86_64/sysroot/usr/include)

if(SEPARATE_BUILD)
    add_library(MyLib SHARED "${MyLib_SRC}" ${APP_GLUE_SRC})
    target_include_directories(MyLib PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/MyLib)

    target_include_directories(MyLib PRIVATE 
        ${NDKINCLUDE} 
        ${NDKINCLUDE}/android
        ${NDKINCLUDE}/${ANDROID_PLATFORM}
        ${NDK}/sources/android/native_app_glue
        ${NDK}/sources/android/cpufeatures
    )
    target_link_libraries(MyLib PRIVATE
        ${LIBLINK}/libm.so
        ${LIBLINK}/libandroid.so 
        ${LIBLINK}/liblog.so
        ${LLVM_LIBC++}
    )
    target_link_libraries(MyApp PUBLIC MyLib)
else()
    target_sources(MyApp PUBLIC "${MyLib_SRC}" ${APP_GLUE_SRC})
    target_include_directories(MyApp PRIVATE 
        ${NDKINCLUDE} 
        ${NDKINCLUDE}/android
        ${NDKINCLUDE}/${ANDROID_PLATFORM}
        ${NDK}/sources/android/native_app_glue
        ${NDK}/sources/android/cpufeatures
    )
    target_link_libraries(MyApp PRIVATE
        ${LIBLINK}/libm.so
        ${LIBLINK}/libandroid.so 
        ${LIBLINK}/liblog.so
        ${LLVM_LIBC++}
    )
endif()
message(STATUS ${APP_GLUE_SRC})

target_include_directories(MyApp PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/MyLib)

When compiling the code together it both builds and runs as expected. But when compiling as separate libs, linking MyApp to MyLib then function MyLibTest cannot be located (I believe this would happen to any function called from main really).

java.lang.UnsatisfiedLinkError: Unable to load native library "/data/app/org.MyAppOrg.MyApp-vqyEUo8t0onsU89Q6ScbNw/lib/arm64/libMyApp.so": dlopen failed: cannot locate symbol "_Z9MyLibTestv" referenced by "/data/app/org.MyAppOrg.MyApp-vqyEUo8t0onsU89Q6ScbNw/lib/arm64/libMyApp.so"...

In order to make it less dense (which I feel might be), I make some use of external modules for finding directories, defining other things, if you find necessary I will shortly edit and link to somewhere.

Hope this makes it easier to understand my approach and what I am trying to accomplish, which is to call a “User defined Main” from my defined android_main or a similar approaches that I am for sure missing.


2nd Edit: Just in case I have tried the same approach for Linux (gcc.g++ 9.3.0) and it works as I intended but it may not be a good approach.

MyLib.h

// Export Markers
#ifndef MyLib
#   ifdef _WIN32
#       if defined(FL_BUILD_SHARED) /* build dll */
#           define MyLib __declspec(dllexport)
#       elif !defined(FL_BUILD_STATIC) /* use dll */
#           define MyLib __declspec(dllimport)
#       else /* static library */
#           define MyLib
#       endif
#   else
#       define MyLib
#   endif
#endif

MyLib bool MyLibTest();

MyLib.cpp

#include "mylib.h"

#include <iostream>

extern int myapp_main();

int main()
{
    std::cout << "MyLib Start";

    myapp_main(); // Call the user main
}

bool MyLibTest() {return true;} MyApp.cpp

#include <mylib.h>
#include <iostream>

int myapp_main()
{
    std::cout << "MyApp Test";
    bool test  false;
    test  MyLibTest();
    if(test  false)
        return 1;
    else
        return 0;
}

CMakeLists.txt

cmake_minimum_required(VERSION 3.1) #require a minimum cmake version to build

project(MyApp LANGUAGES C CXX)

set(CMake_Modules ${CMAKE_CURRENT_SOURCE_DIR}/cmake_modules)
include(${CMake_Modules}/Helpers.cmake)

add_executable(MyApp ${CMAKE_CURRENT_SOURCE_DIR}/MyApp/main.cpp)
file(GLOB MyLib_SRC ${CMAKE_CURRENT_SOURCE_DIR}/MyLib/*.cpp)

if(SEPARATE_BUILD)
    add_library(MyLib SHARED "${MyLib_SRC}")
    target_include_directories(MyLib PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/MyLib)

    target_link_libraries(MyApp PUBLIC MyLib)
else()
    target_sources(MyApp PUBLIC "${MyLib_SRC}")
endif()
message(STATUS ${APP_GLUE_SRC})

target_include_directories(MyApp PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/MyLib)

Solution

Answering own question for learning purposes: For the specifics of building for android, I was setting up the environment in another makefile. Because I have been making it by learning from various sources, some of it have been copy-paste and not put much thought into it as if they weren’t explained they were things that were supposed to work.

What I tried was isolating all options that I did not know exactly why they were there for, which in my case were setting up specific flags, these ones in specific:

-ffunction-sections -fdata-sections -Wall -fvisibilityhidden -fPIC -Wl --gc-sections -s

After not specifying them, the build worked as intended but I did not know which of these flags did what and why would it cause not to work, so I got some info about these flags/options, take into account htis is what I understand which might be incorrect.

  • -ffunctions-sections/fdata-sections literally I don’t understand a thing about them.
  • -Wall turns all optional warnings, I may need that on debug builds
  • -fvisibilityhidden make things not available unless marked by the thing that marks them as exportable (in my case would be MyLib define), it affect linkage but I don’t really understand it.
  • -Wl for passing options to the linker separated by comma
  • --gc-section linker option passed with -Wl, can just wipe out symbols if they are undefined, which on MyLib myapp_main is undefined but defined on MyApp, could be a cause.
  • fPIC sets up position independent code, suitable for shared libraries, which I believe creates like a lookup table for the functions (Global Offset Table, making it easier for the target OS’ dynamic loader).
  • -s I didn’t find anything about it, could it be related to -static? if so that could be an issue as I am building into shared libraries.

In the end -fvisibilityhidden was the option that was affecting negatively to the behaviour intended. I would be grateful if someone could expand on what it actually is happening that impacts this way and any flag/option I butchered.

Thanks for providing the guidelines on making a Minimal Reproducible Example, it helped me out a lot on finding things to focus on!

Answered By – Marc Torres

Leave a Comment