Skip to content

External C libraries

Tebbe Ubben edited this page Jul 8, 2024 · 5 revisions

This project leverages two external C libraries:

  1. Telegram Database Library (tdlib), which is used for interfacing with the Telegram services. It communicates with their servers and provides a ready-to-use data structure, so that the main mission of this project becomes building a UI on top of the functionality provided by the TDLib. The TDLib allows for two operation modes: manually calling all the C functions or sending and receiving JSON objects instead. The latter simplifies the conversion between C and Smalltalk objects, which is the chosen approach here.
  2. WebP codec (libwebp): This library decodes WebP images into Forms, necessary for displaying static stickers.

These libraries are accessed via Squeak's Foreign Function Interface, which can be a tedious task to implement correctly since most of the functionality and behavior is documented sparsely, but here are a few things to be mindful of:

Computing Module Names

Instead of providing the module parameter in the cdecl pragma, you can also implement the moduleName message to return the appropriate string. This becomes especially handy when aiming for compatibility with different operating environments. The moduleName message must be implemented on class side, while the FFI calls must be instance messages. Otherwise, the moduleName message will not be utilized by FFI. This is a bit of an anti-pattern since FFI calls aren't instance specific. A sensible solution is using the Singleton pattern.

Dynamic Libraries only

FFI only supports dynamically linked shared libraries. For example, the standard LibWebP binaries provided by Google are statically linked, i.e. they are meant to be included in the final executable. Therefore, the library needs to be manually compiled targeting for a shared library. Using CMake, this can be achieved by including the -DBUILD_SHARED_LIBS=ON command line option when configuring the build directly. You can use dumpbin and nm to inspect the resulting object files.

Linker Shenanigans

  • Windows: The linker ignores full paths in moduleName message and searches in predefined folders only, including the Resources folder. Place your binaries there.
  • Unix-like Systems: The linker loads libraries from directories defined in the LD_LIBRARY_PATH environment variable (or DYLD_LIBRARY_PATH for Mac), which does not include the Resources folder by default. However, it will find the library if you provide the full path in moduleName.

To ensure compatibility across both Windows and Unix, place binaries in the Resources folder and return the full path in moduleName.

Dependencies of External Libraries

If your shared libraries have dependencies themselves (e.g. libwebp depends on libsharpyuv), they are subject to the linker shenanigans, too. On Windows, this can easily the solved by placing them in the Resources directory, too. However, Unix still won't load from that directory by default. To address this, include the rpath parameter in the object file during compilation to instruct the linker to search additional locations. Set rpath=$ORIGIN to make the linker search in the same directory as the shared library. Enable this functionality using CMake with the options -DCMAKE_INSTALL_RPATH=\$ORIGIN -DCMAKE_BUILD_RPATH_USE_ORIGIN=ON. Use ldd to verify that the linker resolves dependencies correctly.