Android NDK: CMake fails while linking static library (OpenSSL) - c++

I'm writing a simple proof of concept app that integrates OpenSSL using NDK. Unfortunately, it gives me undefined reference errors during build.
What I did:
Cross-compiled OpenSSL for Android (x86_64 is shown, and similarly for other ABIs):
openssl-1.1.1q $ ./Configure android-x86_64
openssl-1.1.1q $ make
openssl-1.1.1q $ cp libssl.a <path_to_project_cpp_dir>/libs/x86_64/
openssl-1.1.1q $ cp -r ./include/openssl <path_to_project_cpp_dir>/libs/include/
Added the following CMakeLists.txt into project's cpp dir:
cmake_minimum_required(VERSION 3.18.1)
project("ndk-poc")
add_library(
# Sets the name of the library.
ndk-poc
# Sets the library as a shared library.
SHARED
# Provides a relative path to your source file(s).
ndk-poc.cpp)
find_library(
# Sets the name of the path variable.
log-lib
# Specifies the name of the NDK library that you want CMake to locate.
log)
add_library(libssl STATIC IMPORTED)
set_target_properties(
# Specifies the target library.
libssl
# Specifies the parameter you want to define.
PROPERTIES IMPORTED_LOCATION
# Provides the path to the library you want to import.
${CMAKE_SOURCE_DIR}/libs/${ANDROID_ABI}/libssl.a )
include_directories(${CMAKE_SOURCE_DIR}/libs/include/)
target_link_libraries(
# Specifies the target library.
ndk-poc
# Links the target library to the log library
# included in the NDK.
libssl
${log-lib})
And this is my test ndk-poc.cpp:
#include <jni.h>
#include <string>
#include <openssl/bn.h>
#include <openssl/evp.h>
#include <openssl/sha.h>
extern "C" JNIEXPORT jstring JNICALL
Java_com_techyourchance_android_screens_home_HomeFragment_stringFromJNI(
JNIEnv* env,
jobject /* this */) {
/* Testing OPENSSL prime generation and BigNum. */
BIGNUM *prime1 = NULL;
int bits = 16; /* Number of bits for the generated prime. */
int safe = 0;
prime1 = BN_new();
if (prime1 == NULL) {
printf("Out of memory.\n");
} else if (BN_generate_prime_ex(prime1, bits, safe, NULL, NULL, NULL)) {
printf("Success!\n");
int len;
len = BN_num_bytes(prime1);
unsigned char* buffer;
buffer = static_cast<unsigned char*>(malloc(len));
if (!buffer) {
printf("Out of memory allocating buffer.\n");
} else {
int wlen;
wlen = BN_bn2bin(prime1, buffer);
printf("Wrote %d bytes.\n", wlen);
int i;
for(i=0;i<wlen;++i) {
printf("Byte %d of buffer = %d.\n", i, buffer[i]);
}
free(buffer);
char* st;
st = BN_bn2dec(prime1);
printf("Prime = %s.\n", st);
OPENSSL_free(st);
}
} else {
printf("Error generating prime.\n");
}
std::string result = "Test completed!";
return env->NewStringUTF(result.c_str());
}
Results:
I don't see any errors inside Android Studio, but when I try building the project, all usages of OpenSSL's APIs in my test code result in unresolved reference errors:
...
C:/Users/Vasiliy/projects/ndk-poc/app/src/main/cpp/ndk-poc.cpp:38: error: undefined reference to 'BN_bn2dec'
C:/Users/Vasiliy/projects/ndk-poc/app/src/main/cpp/ndk-poc.cpp:40: error: undefined reference to 'CRYPTO_free'
clang++: error: linker command failed with exit code 1 (use -v to see invocation)
ninja: build stopped: subcommand failed.
What did I miss?

OpenSSL consists of (at least) two libraries: libcrypto which has the general-purpose cryptographic functions; and libssl which is a TLS implementation built on top of libcrypto.
So in your case libcrypto would be the appropriate library to link against.

Related

Application using FFMPEG Library does not compile with CMAKE error avformat_alloc_context but I have imported the header files and libray

I compiled the FFMPEG source file myself and got the header and library files in an include and bin folder respectively, the target platform is Windows 10. I also setup my cmakelist.txt to find and include both the library and header files. The application finds the path or so it seems because during compilation I get a "LNK2019 error unresolved external symbol avformat_alloc_context referenced in function main". Below is an extract from my cmake list; I will like to note that I got the .lib and .dll versions of the library hence the approach below based on the book "professional cmake" and other stackflow examples.
ProjectDir/AudMan/cmakelist.txt
list(APPEND CMAKE_PREFIX_PATH ${CMAKE_PREFIX_PATH};PATH-TO-INCLUDES;PATH-TO-LIBRARY)
find_path(AVFORMAT_INCLUDE_DIR libavformat/avformat.h)
find_library(AVFORMAT_LIBRARY avformat)
add_library(ffmpegHeaders INTERFACE)
target_include_directories(ffmpegHeaders INTERFACE ${AVFORMAT_INCLUDE_DIR})
ProjectDir/cmakelist.txt
set(Rapid_Prefix PATH-TO-LIBRARY)
add_library(AVformat SHARED IMPORTED)
set_target_properties(AVformat PROPERTIES IMPORTED_LOCATION ${Rapid_Prefix}/avformat-59.dll IMPORTED_IMPLIB ${AVFORMAT_LIBRARY})
target_link_libraries(App_target PRIVATE AVformat)
A sample of the codes is this
extern "C" {
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
}
int main()
{
AVFormatContext* format = avformat_alloc_context();
if (avformat_open_input(&format, R"(\test.m4a)", NULL, NULL) != 0) {
fprintf(stderr, "Could not open file '%s'\n", R"(\test.m4a)");
return -1;
}
if (avformat_find_stream_info(format, NULL) < 0) {
fprintf(stderr, "Could not retrieve stream info from file '%s'\n", R"(test.m4a)");
return -1;
}
return 0;
}
I have been at it for about five days and will appreciate any help I can get.

How to create a single executable file with dependencies included? [duplicate]

I am writing an application in C++ which relies on various resources in my project. Right now, I have the relative path from the produced executable to each resource hard-coded in my sources, and that allows my program to open the files and read in the data in each resource. This works ok, but it requires that I start the executable from a specific path relative to the resources. So if I try to start my executable from anywhere else, it fails to open the files and cannot proceed.
Is there a portable way to have CMake embed my resources into the executables (or libraries) such that I can simply access them in memory at runtime instead of opening files whose paths are brittle? I have found a related question, and it looks like embedding resources can be done well enough with some ld magic. So my question is how do I do this in a portable, cross platform manner using CMake? I actually need my application run on both x86 and ARM. I am ok with supporting only Linux (Embedded), but bonus points if anyone can suggest how to do this for Windows (Embedded) as well.
EDIT:
I forgot to mention a desired property of the solution. I would like to be able to use CMake to cross-compile the application when I am building for ARM rather than have to compile it natively on my ARM target.
As an alternative to the answer of sfstewman, here's a small cmake (2.8) function to convert all files in a specific folder to C data and write them in wished output file:
# Creates C resources file from files in given directory
function(create_resources dir output)
# Create empty output file
file(WRITE ${output} "")
# Collect input files
file(GLOB bins ${dir}/*)
# Iterate through input files
foreach(bin ${bins})
# Get short filename
string(REGEX MATCH "([^/]+)$" filename ${bin})
# Replace filename spaces & extension separator for C compatibility
string(REGEX REPLACE "\\.| |-" "_" filename ${filename})
# Read hex data from file
file(READ ${bin} filedata HEX)
# Convert hex data for C compatibility
string(REGEX REPLACE "([0-9a-f][0-9a-f])" "0x\\1," filedata ${filedata})
# Append data to output file
file(APPEND ${output} "const unsigned char ${filename}[] = {${filedata}};\nconst unsigned ${filename}_size = sizeof(${filename});\n")
endforeach()
endfunction()
One of the easiest ways to do this is to include a small, portable C program in your build that reads the resource and generates a C file that contains the length of the resource data and the actual resource data as an array of constant character literals. This will be entirely platform independent, but should only be used for resources that are reasonably small. For larger resources, you probably don't want to embed the files in your program.
For resource "foo", the generated C file "foo.c" would contain:
const char foo[] = { /* bytes of resource foo */ };
const size_t foo_len = sizeof(foo);
To access the resource from C++, you declare the following two symbols in either a header or the cpp file where they're used:
extern "C" const char foo[];
extern "C" const size_t foo_len;
To generate foo.c in the build, you need a target for the C program (call it embedfile.c), and you need to use the add_custom_command command to call this program:
add_executable(embedfile embedfile.c)
add_custom_command(
OUTPUT foo.c
COMMAND embedfile foo foo.rsrc
DEPENDS foo.rsrc)
Then, include foo.c on the source list of a target that requires the "foo" resource. You now have access to the bytes of "foo".
The program embedfile.c is:
#include <stdlib.h>
#include <stdio.h>
FILE* open_or_exit(const char* fname, const char* mode)
{
FILE* f = fopen(fname, mode);
if (f == NULL) {
perror(fname);
exit(EXIT_FAILURE);
}
return f;
}
int main(int argc, char** argv)
{
if (argc < 3) {
fprintf(stderr, "USAGE: %s {sym} {rsrc}\n\n"
" Creates {sym}.c from the contents of {rsrc}\n",
argv[0]);
return EXIT_FAILURE;
}
const char* sym = argv[1];
FILE* in = open_or_exit(argv[2], "r");
char symfile[256];
snprintf(symfile, sizeof(symfile), "%s.c", sym);
FILE* out = open_or_exit(symfile,"w");
fprintf(out, "#include <stdlib.h>\n");
fprintf(out, "const char %s[] = {\n", sym);
unsigned char buf[256];
size_t nread = 0;
size_t linecount = 0;
do {
nread = fread(buf, 1, sizeof(buf), in);
size_t i;
for (i=0; i < nread; i++) {
fprintf(out, "0x%02x, ", buf[i]);
if (++linecount == 10) { fprintf(out, "\n"); linecount = 0; }
}
} while (nread > 0);
if (linecount > 0) fprintf(out, "\n");
fprintf(out, "};\n");
fprintf(out, "const size_t %s_len = sizeof(%s);\n\n",sym,sym);
fclose(in);
fclose(out);
return EXIT_SUCCESS;
}
I would like to propose another alternative. It uses the GCC linker to directly embed a binary file into the executable, with no intermediary source file. Which in my opinion is simpler and more efficient.
set( RC_DEPENDS "" )
function( add_resource input )
string( MAKE_C_IDENTIFIER ${input} input_identifier )
set( output "${CMAKE_ARCHIVE_OUTPUT_DIRECTORY}/${input_identifier}.o" )
target_link_libraries( ${PROJECT_NAME} ${output} )
add_custom_command(
OUTPUT ${output}
COMMAND ${CMAKE_LINKER} --relocatable --format binary --output ${output} ${input}
DEPENDS ${input}
)
set( RC_DEPENDS ${RC_DEPENDS} ${output} PARENT_SCOPE )
endfunction()
# Resource file list
add_resource( "src/html/index.html" )
add_custom_target( rc ALL DEPENDS ${RC_DEPENDS} )
Then in your C/C++ files all you need is:
extern char index_html_start[] asm( "_binary_src_html_index_html_start" );
extern char index_html_end[] asm( "_binary_src_html_index_html_end" );
extern size_t index_html_size asm( "_binary_src_html_index_html_size" );
Pure CMake function to convert any file into C/C++ source code, implemented with only CMake commands:
####################################################################################################
# This function converts any file into C/C++ source code.
# Example:
# - input file: data.dat
# - output file: data.h
# - variable name declared in output file: DATA
# - data length: sizeof(DATA)
# embed_resource("data.dat" "data.h" "DATA")
####################################################################################################
function(embed_resource resource_file_name source_file_name variable_name)
file(READ ${resource_file_name} hex_content HEX)
string(REPEAT "[0-9a-f]" 32 column_pattern)
string(REGEX REPLACE "(${column_pattern})" "\\1\n" content "${hex_content}")
string(REGEX REPLACE "([0-9a-f][0-9a-f])" "0x\\1, " content "${content}")
string(REGEX REPLACE ", $" "" content "${content}")
set(array_definition "static const unsigned char ${variable_name}[] =\n{\n${content}\n};")
set(source "// Auto generated file.\n${array_definition}\n")
file(WRITE "${source_file_name}" "${source}")
endfunction()
https://gist.github.com/amir-saniyan/de99cee82fa9d8d615bb69f3f53b6004
I'd say the most elegant way to have embedded resources in C++ is simply to use the Qt Resource System which is portable across different platforms, compatible with CMake, and essentially wraps up everything done in the answer above, besides providing compression, being fully tested and fool-proof, everything else.
Create a Qt resource file - an XML listing the files to be embedded:
<RCC>
<qresource prefix="/">
<file>uptriangle.png</file>
<file>downtriangle.png</file>
</qresource>
</RCC>
Call the file qtres.qrc. The resource file above will have the two png files (located in the same directory as qtres.qrc) embedded in the final executable. You can easily add/remove monitor resources to a qrc file using QtCreator (the Qt IDE).
Now in your CMakeLists.txt file add:
set(CMAKE_AUTOMOC ON)
find_package(Qt5Core)
qt5_add_resources(QT_RESOURCE qtres.qrc)
In your main.cpp, before you need to access the resource, add the following line:
Q_INIT_RESOURCE(qtres);
Now you can access any of the resources above using Qt classes compatible with Qt Resource System, such as QPixmap, QImage ... and mosty importantly maybe in general cases the QResource wrapper class which wraps an embedded Qt resource and enables access to it through a friendly interface. As an example, to access data within downtriangle.png in the above resources, the following lines will do the trick:
#include <QtCore>
#include <QtGui>
// ...
int main(int argc, char **argv)
{
// ...
Q_INIT_RESOURCE(qtres);
// ...
QResource res("://downtriangle.png"); // Here's your data, anyway you like
// OR
QPixmap pm("://downtriangle.png"); // Use it with Qt classes already
// ...
}
Here, res can be used to directly access the data using res.data(), res.size() ...
To parse the image content of the file use pm. Use pm.size(), pm.width() ...
And you're good to go.
I hope it helped.
There is a single-file CMake script that allows you to embed data easily that's called cmrc.
Example usage:
include(CMakeRC.cmake)
cmrc_add_resource_library(foo-resources
ALIAS foo::rc
NAMESPACE foo
shaders/trig.vert
shaders/trig.frag)
target_link_libraries(foo foo::rc)
#include <cmrc/cmrc.hpp>
CMRC_DECLARE(foo); // It should be the NAMESPACE property you specified
// in your CMakeLists.txt
int main() {
auto fs = cmrc::foo::get_filesystem();
auto vert_shader = fs.open("shaders/trig.vert");
auto frag_shader = fs.open("shaders/trig.frag");
...
glShaderSource(vertexShader, 1, &vert_shader.begin(), nullptr);
}
It's probably the easiest library to setup and use.
This is an improved version of #Itay Grudev's solution by adding null-terminated character to the file. So that it is possible to just use extern const char file[] asm("_binary_your_file_start") directly.
This work nice with text files. For binary files, it is better to stay with the original solution :)
set(RC_DEPENDS "")
# If you want to make the symbol name look prettier, just use relative path as the input
function(add_resource input)
string(MAKE_C_IDENTIFIER ${input} input_identifier)
set(res_intermediate_dir ${CMAKE_CURRENT_BINARY_DIR}/resources)
set(res_with_null_output "${res_intermediate_dir}/${input}")
set(output "${res_intermediate_dir}/${input_identifier}.o")
# Add null-terminated character to the file
add_custom_command(
DEPENDS ${input}
OUTPUT ${res_with_null_output}
COMMAND ${CMAKE_COMMAND} -E copy ${input} ${res_with_null_output};
COMMAND echo -n '\\0' >> ${res_with_null_output}
WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR}
)
add_custom_command(
DEPENDS ${res_with_null_output}
OUTPUT ${output}
COMMAND ${CMAKE_LINKER} --relocatable --format binary --output ${output} ${input}
WORKING_DIRECTORY ${res_intermediate_dir}
)
set(RC_DEPENDS ${RC_DEPENDS} ${output} PARENT_SCOPE)
endfunction()
# Resource file list
add_resource( "src/html/index.html" )
add_custom_target( rc ALL DEPENDS ${RC_DEPENDS} )

Slient crash when calls to ffmpeg's libav exist

I have a fairly simple example.
#include <iostream>
extern "C"
{
#include <libavutil/opt.h>
#include <libavutil/avutil.h>
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
}
int main(int, char **)
{
AVFormatContext *format = 0; // avformat_alloc_context();
// avformat_open_input(&format, "http://s5radio.ponyvillelive.com:8026/stream.mp3", NULL, NULL);
// avformat_find_stream_info(format, NULL);
std::cout << "Hello, world!" << std::endl;
return 0;
}
I this outputs "Hello, world!" and I can set break points but the moment I uncomment anything calling avformat code the program silently closes with no error and no break points are hit making it impossible to debug.
An example would be changing AVFormatContext *format = 0; to AVFormatContext *format = avformat_alloc_context(); or uncommenting avformat_open_input(&format, "http://s5radio.ponyvillelive.com:8026/stream.mp3", NULL, NULL);. Without breakpoints or errors how do I solve?
Update:
This is what I get from the debugger:
ERROR: Unable to start debugging. Unexpected GDB output from command "-exec-run". During startup program exited with code 0xc0000139.
The program 'path\to\my\project\LibavPlayground.exe' has exited with code 0 (0x00000000).
Update2:
this is my cmake:
cmake_minimum_required(VERSION 3.0.0)
project(LibavPlayground VERSION 0.1.0 LANGUAGES CXX C)
find_package(PkgConfig REQUIRED)
pkg_check_modules(LIBAV REQUIRED libavutil libavcodec libavformat)
include_directories(${LIBAV_INCLUDE_DIRS})
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -D__STDC_CONSTANT_MACROS -Wno-deprecated-declarations")
include_directories(include PUBLIC)
include_directories(src PRIVATE)
file(GLOB SRC_FILES ${PROJECT_SOURCE_DIR}/src/*.cpp)
add_executable(LibavPlayground ${SRC_FILES})
target_link_libraries(LibavPlayground ${LIBAV_LIBRARIES})
and all the libav* libraries were installed with a single pacman -S mingw-w64-x86_64-ffmpeg command
The current solution is to use WSL instead of MinGw as I don't duplicate the issue in WSL. Although I really hope someone out there has a real solution.

Use ngspice library in WebAssembly

I would need some help with using ngspice as a library in a webassembly (wasm) project.
I installed emsdk and newest version of emcc (1.39.20) and downloaded the source of ngspice version 32.
To my greatest surprise, I was able to compile ngspice to wasm target by following this guide:
emconfigure ./configure --with-ngshared --disable-debug
emmake make
(I had to patch configure a little to pass the checks by adding .out.js a.out.wasm to this line:)
# The possible output files:
ac_files="a.out a.out.js a.out.wasm conftest.exe conftest a.exe a_out.exe b.out conftest.*"
This produced a libngspice.so.0.0.0 file that I tried to link to from C++ code. However that failed with duplicate symbol: main. So it seemed that libngspice.so.0.0.0 contained a main function, that shouldn't have been there if I understand the purpose of the --with-ngshared of the configure script correctly.
So I manually removed the main function from main.c of ngspice and recomplied using the above method. This time I could successfully complie my own project, linking to ngspice. However when I call ngSpice_Init, I recieve the following runtime errors:
stderr Note: can't find init file.
exception thrown: RuntimeError: unreachable executed,#http://localhost:8001/sim.js line 1802 > WebAssembly.instantiate:wasm-function[67]:0x24e9
#http://localhost:8001/sim.js line 1802 > WebAssembly.instantiate:wasm-function[88]:0x423b
...
Minimal reproducible steps:
compile ngspice as above
compile the code below using em++ -o sim.html sim.cpp lib/libngspice.so
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include "sharedspice.h"
using namespace std;
int recieve_char(char * str, int id, void* p){
printf("recieved %s\n", str);
}
int recieve_stat(char* status, int id, void* p){
printf("status: %s\n", status);
}
int ngexit(int status, bool unload, bool exit, int id, void* p){
printf("exit: %d\n", status);
}
int recieve_data(vecvaluesall* data, int numstructs, int id, void* p){
printf("data recieved: %f\n", data->vecsa[0]->creal);
}
int recieve_init_data(vecinfoall* data, int id, void* p){
printf("init data recieved from: %d\n", id);
}
int ngrunning(bool running, int id, void* p){
if(running){
printf("ng is running\n");
}else{
printf("ng is not running\n");
}
}
int main(){
ngSpice_Init(&recieve_char, &recieve_stat, &ngexit,
&recieve_data, &recieve_init_data, &ngrunning, (void*)NULL);
char** circarray = (char**)malloc(sizeof(char*) * 7);
circarray[0] = strdup("test array");
circarray[1] = strdup("V1 1 0 1");
circarray[2] = strdup("R1 1 2 1");
circarray[3] = strdup("C1 2 0 1 ic=0");
circarray[4] = strdup(".tran 10u 3 uic");
circarray[5] = strdup(".end");
circarray[6] = NULL;
ngSpice_Circ(circarray);
ngSpice_Command("run");
return 0;
}
So could someone please help me correctly compiling ngspice library to wasm target?
(Before someone asks, yes, I've seen this question, but it didn't help much)
I was able to compile the library and my example code after making several changes to the ngspice source. The patch and a guide on how to compile ngspice to wasm, can be found here.
(The issue leading to the error shown in my question was with the example code, not returning anything from functions that by signature should return int. This is not tolerated in wasm.)

Properly setting up tinycc with visual studio 2019, library libtcc1-32.a not found

I'm using tcclib to compile and run C code on the fly in my C++ project.
I'm using the binaries provided here https://bellard.org/tcc/
I then open a vs2019 developer prompt and run both those command
lib /def:libtcc\libtcc.def /out:libtcc.lib
cl /MD examples/libtcc_test.c -I libtcc libtcc.lib
My code builds fine, I'm using this code. This code is similar to the one found in the tcclib example, which is this one : https://repo.or.cz/tinycc.git/blob/HEAD:/tests/libtcc_test.c (this is another repo, but it's the same code.
The code I run is this one. This is inside an extern "C" {}.
int tcc_stuff(int argc, const char** argv) {
TCCState* s;
int i;
int (*func)(int);
s = tcc_new();
if (!s) {
fprintf(stderr, "Could not create tcc state\n");
exit(1);
}
/* if tcclib.h and libtcc1.a are not installed, where can we find them */
for (i = 1; i < argc; ++i) {
const char* a = argv[i];
if (a[0] == '-') {
if (a[1] == 'B')
tcc_set_lib_path(s, a + 2);
else if (a[1] == 'I')
tcc_add_include_path(s, a + 2);
else if (a[1] == 'L')
tcc_add_library_path(s, a + 2);
}
}
/* MUST BE CALLED before any compilation */
tcc_set_output_type(s, TCC_OUTPUT_MEMORY);
{
const char* other_file = ReadFile2(argv[1]);
if (other_file == NULL)
{
printf("invalid filename %s\n", argv[1]);
return 1;
}
if (tcc_compile_string(s, other_file) == -1)
return 1;
}
/* as a test, we add symbols that the compiled program can use.
You may also open a dll with tcc_add_dll() and use symbols from that */
tcc_add_symbol(s, "add", add);
tcc_add_symbol(s, "hello", hello);
/* relocate the code */
if (tcc_relocate(s, TCC_RELOCATE_AUTO) < 0)
return 1;
/* get entry symbol */
func = (int(*)(int))tcc_get_symbol(s, "foo");
if (!func)
return 1;
/* run the code */
msg(func(32));
//msg(func2(4));
/* delete the state */
tcc_delete(s);
return 0;
}
When running my code, TCC had the error
tcc: error: library 'libtcc1-32.a' not found
I fixed it by placing this file in the lib/ directory next to my .exe
I also copied the include/ folder to include stdio.h etc.
My question is: why does it need this file in a lib/ folder, instead of the provided tcclib.dll file? Is it possible to "ship" certain headers like stdio.h?
The question has no answer but 360 views, so I thought I'd reply.
The library doesn't necessarily need to be in that folder. To quote the author's command line docs, which still apply to the library,
-Ldir
Specify an additional static library path for the -l option. The default library paths are /usr/local/lib, /usr/lib and /lib.
I inferred your program to be a modified main() of libtcc_test.c & fixed it to the point of functioning. Then I used VS2022 to retrace your steps, put the .a files into the same folder as my new tests_libtcc_test.exe, then I ran this:
tests_libtcc_test c:/lang/tcc/examples/fib.c -Ic:/lang/tcc/include -L.
The library issue appears if I don't -L anything, and disappears if I include at least the ".".
And of course, you can drop the include folder into your redistributable and include it by default right from the code.
Because the tcc DLL is just another interface to the same compiler, it needs the same things tcc.exe would to build an executable; in this case, it needs the same libraries.