Resolving SQLITE_API Export Issues When Building Custom SQLite DLL on Windows

Missing SQLite Function Exports in Custom Windows DLL Builds

Issue Overview: EntryPointNotFoundException Due to Unexported SQLite Functions

When attempting to build a custom SQLite DLL for Windows with extensions like SESSION and R-Tree, developers often encounter an EntryPointNotFoundException referencing sqlite3_libversion_number or other core API functions. This occurs when the compiled DLL fails to properly export SQLite’s public symbols, making them inaccessible to consuming applications like Unity projects or .NET wrappers. The root conflict stems from Windows’ strict symbol visibility rules conflicting with SQLite’s default build configuration.

Unlike Unix-like systems where global symbols are exported by default, Windows requires explicit marking of exported functions using __declspec(dllexport) attributes. SQLite’s source code uses the SQLITE_API macro to decorate API functions, but this macro remains undefined in typical Linux/Unix builds where symbol visibility works differently. When cross-compiling for Windows without specifying SQLITE_API=__declspec(dllexport), the resulting DLL contains all API functions but doesn’t expose them through the module’s export table. This leads to loader failures when applications attempt to dynamically link against these "hidden" functions.

The size discrepancy between official prebuilt DLLs (2.15MB) and custom builds (1.25MB) arises from differences in compilation flags controlling debug symbols, optimization levels, and cross-module inlining. Official binaries often retain symbolic debugging information and use less aggressive size optimization to maximize compatibility across Windows versions, while developer builds using -O2 optimization strip unnecessary metadata.

Possible Causes: Windows DLL Export Misconfiguration and Build Flag Omissions

Three primary factors contribute to the missing function exports:

  1. Undefined SQLITE_API Macro for Windows Exports
    The SQLite codebase uses conditional compilation to handle platform-specific symbol visibility. In sqlite3.h, the SQLITE_API macro is defined as:

    #if defined(_WIN32) && !SQLITE_API
    # define SQLITE_API __declspec(dllimport)
    #endif
    

    This default assumes the DLL is being consumed (hence dllimport), not built. When compiling the DLL itself, developers must override this with -DSQLITE_API=__declspec(dllexport) to mark exports.

  2. Incomplete Linker Configuration for Symbol Exposure
    Microsoft’s LINK.EXE requires either:

    • A module definition (.def) file explicitly listing exported functions
    • __declspec(dllexport) attributes on exported functions
    • /EXPORT linker directives
      Without any of these, the DLL’s export table remains empty. The official SQLite build system for Windows (Makefile.msc) uses a generated .def file containing all public API functions.
  3. Compiler Optimization Flags Stripping Metadata
    Aggressive optimization flags like /O1 (minimize size) or /O2 (maximize speed) can eliminate functions considered unused during static analysis. While SQLite’s source structure generally prevents this, combining /GL (Whole Program Optimization) with /LTCG (Link-time Code Generation) might inadvertently remove vital functions if the build process doesn’t properly mark API boundaries.

Troubleshooting Steps: Ensuring Proper Symbol Exportation in SQLite DLLs

Step 1: Verify Current DLL Export Table
Use Microsoft’s dumpbin utility to inspect exported functions:

dumpbin /EXPORTS sqlite3.dll

If the output lacks SQLite API functions like sqlite3_open or sqlite3_libversion_number, symbol exporting is misconfigured.

Step 2: Apply Correct SQLITE_API Macro During Compilation
Modify the build command to explicitly define symbol export attributes:

cl -O2 -DSQLITE_API=__declspec(dllexport) -DSQLITE_ENABLE_SESSION -DSQLITE_ENABLE_FTS4 -DSQLITE_ENABLE_RTREE -DSQLITE_DQS=0 -DSQLITE_ENABLE_COLUMN_METADATA sqlite3.c -link -dll -out:sqlite3.dll

This forces __declspec(dllexport) on all API functions through the SQLITE_API macro.

Step 3: Compare Against Official Build Configuration
Download the SQLite amalgamation source matching the prebuilt DLL version. The official Windows builds use Makefile.msc, which:

  • Generates sqlite3.def via mkkeywordhash.exe
  • Passes SQLITE_API=__declspec(dllexport) for DLL builds
  • Uses these linker flags:
    /DYNAMICBASE /NXCOMPAT /DEBUG /OPT:REF /OPT:ICF

Replicate this configuration by:

  1. Building mkkeywordhash.exe from tool/mkkeywordhash.c
  2. Generating sqlite3.def:
    mkkeywordhash > sqlite3.def
    
  3. Including the .def file in linking:
    cl ... sqlite3.c /link /DEF:sqlite3.def ...
    

Step 4: Address Size Discrepancies via Symbol Inclusion
To match the official DLL’s size characteristics:

  • Remove size optimization (/O2) for debug-compatible builds
  • Preserve debug symbols with /Z7 or /DEBUG:FASTLINK
  • Disable function inlining across modules with /Ob0

Example production build command:

cl -Od -DSQLITE_API=__declspec(dllexport) -DSQLITE_ENABLE_SESSION -DSQLITE_ENABLE_RTREE -DSQLITE_ENABLE_COLUMN_METADATA -Z7 -GS -guard:cf sqlite3.c -link -dll -def:sqlite3.def -out:sqlite3.dll

Step 5: Validate DLL Compatibility with Dependent Components
After rebuilding, test the DLL with a minimal C# wrapper:

using System.Runtime.InteropServices;

public class SQLiteTest {
    [DllImport("sqlite3.dll", CallingConvention = CallingConvention.Cdecl)]
    public static extern int sqlite3_libversion_number();
    
    public static void Main() {
        Console.WriteLine(sqlite3_libversion_number());
    }
}

Successful execution confirms proper exports. If Unity persists with errors, verify:

  • Architecture match (x64 DLL with x64 Unity build)
  • DLL placement in Assets/Plugins/x86_64
  • Absence of conflicting SQLite installations in system directories

Final Build Command Template
For production-ready DLLs with session and R-Tree support:

cl -Od -DSQLITE_API=__declspec(dllexport) -DSQLITE_ENABLE_SESSION -DSQLITE_ENABLE_PREUPDATE_HOOK -DSQLITE_ENABLE_RTREE -DSQLITE_ENABLE_JSON1 -DSQLITE_ENABLE_FTS5 -DSQLITE_ENABLE_GEOPOLY -DSQLITE_ENABLE_DBSTAT_VTAB -DSQLITE_ENABLE_COLUMN_METADATA -DSQLITE_USE_URI=1 -DSQLITE_DQS=0 -DSQLITE_DEFAULT_WAL_SYNCHRONOUS=1 -DSQLITE_THREADSAFE=1 -DSQLITE_MAX_MMAP_SIZE=268435456 -D_CRT_SECURE_NO_WARNINGS -Z7 -MD -GS -guard:cf -W3 -nologo sqlite3.c -link -dll -def:sqlite3.def -out:sqlite3.dll

This includes common extensions and security-hardening flags while maintaining debug symbol compatibility with .NET profilers and crash dumps.

Related Guides

Leave a Reply

Your email address will not be published. Required fields are marked *