Как связать приложения Rusqlite с пользовательскими сборками SQLite в Windows?

Я хочу использовать специально созданную библиотеку SQLite с привязками Rusqlite при создании приложения базы данных на языке Rust. Меня интересуют как динамические, так и статические варианты связывания. В README Rusqlite указано, что подключение к SQLite в Windows возможно через пакет vcpkg, но не приводятся инструкции (я не хочу использовать vcpkg или любой другой «помощник»).

Веб-сайт Rust предлагает использовать набор инструментов Rust и инструменты сборки Visual Studio C++. Пример «человека» в Rusqlite можно построить с помощью:

{repo-root}> cargo build --example persons --features bundled
or
{repo-root}> cargo build --example persons --features bundled-full

Я могу построить пример «людей» и успешно запустить его (и другие простые примеры) с помощью этих команд. Они создают статически связанный исполняемый файл в «{repo root}\target\debug\examples», используя копию SQLite, включенную в библиотеку.

Команды

{repo-root}> cargo build --example persons
and
{repo-root}> cargo build --example persons --features modern-full

ожидаемо потерпит неудачу из-за отсутствия sqlite3.lib. Какие варианты доступны?

Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
0
0
92
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Репозиторий Rusqlite включает копию файла объединения SQLite (sqlite3.c) и связанного с ним заголовочного файла (sqlite3.h) внутри каталога "{repo-root}\libsqlite3-sys". Пожалуй, самый простой способ начать тестирование динамической компоновки — скачать x64 «Предварительно скомпилированные двоичные файлы для Windows» и использовать его с проектом Rusqlite.

Предварительно скомпилированный двоичный файл SQLite

Загрузите архив и распакуйте его в каталог «sqlite», расположенный рядом с каталогом «rusqlite», содержащий клонированную/загруженную копию репозитория Rusqlite. Каталог «sqlite» должен содержать два файла: sqlite3.dll и sqlite3.def. Файл sqlite3.lib, необходимый для компоновщика, можно создать из этих двух файлов. Откройте консоль «cmd» с набором среды сборки (Rust/MSBuild), перейдите в каталог «sqlite» и выполните следующую команду:

...sqlite> lib /MACHINE:x64 /DEF:sqlite3.def

который должен создать два новых файла, включая sqlite3.lib. Теперь перейдите в каталог «rusqlite», cd ..\rusqlite. Местоположение «sqlite3.lib» можно передать компоновщику через переменную среды SQLITE3_LIB_DIR. Выполнение

...rusqlite> (set "SQLITE3_LIB_DIR=..\sqlite") && cargo build --example persons
or
...rusqlite> (set "SQLITE3_LIB_DIR=..\sqlite") && cargo build --example persons --features modern-full

должно завершиться успешно и сгенерировать файл "persons.exe" в папке "{repo root}\target\debug\examples". Существует несколько возможных способов проверить результат перед его выполнением. Например, Far Manager имеет плагин ImpEx (также доступен в Far PlugRing), который обеспечивает удобный доступ к исполняемым метаданным. Список элементов верхнего уровня, видимых в ImpEx для «persons.exe», должен, среди прочего, содержать файловый элемент «64BIT» и каталог «Таблица импорта». При открытии последнего должно появиться несколько элементов, похожих на каталоги, названных в честь импортированных файлов DLL, в том числе один для sqlite3.dll.

Также обратите внимание, что размер нового исполняемого файла person.exe значительно меньше, поскольку он больше не интегрирует код библиотеки SQLite. Теперь скопируйте файл sqlite3.dll в каталог, содержащий файл person.exe, например:

...rusqlite> cd target\debug\examples
...examples> copy /Y ..\..\..\..\sqlite\sqlite3.dll .

и запустите файл person.exe, который теперь должен выдавать выходные данные, как и раньше.

Сборка SQLite из исходного кода

Еще одним шагом будет создание библиотеки SQLite из официальной объединенной версии. Удалите ранее созданный каталог «sqlite» и вместо него создайте скрипт sqlite_MSVC_Cpp_Build_Tools_Demo.bat со следующим содержимым:

@echo off


:: ================================ BEGIN MAIN ================================
:MAIN
SetLocal EnableExtensions EnableDelayedExpansion

set ERROR_STATUS=0

set BASEDIR=%~dp0
set BASEDIR=%BASEDIR:~0,-1%
set DISTRODIR=%BASEDIR%\sqlite

call :DOWNLOAD_SQLITE
if %ERROR_STATUS% NEQ 0 exit /b 1
call :EXTRACT_SQLITE
if %ERROR_STATUS% NEQ 0 exit /b 1
if not exist "%DISTRODIR%" (
  echo Distro directory does not exists. Exiting
  exit /b 1
)
call :BUILD_SQLITE
if %ERROR_STATUS% NEQ 0 exit /b 1

EndLocal
exit /b 0
:: ================================= END MAIN =================================


:: ============================================================================
:DOWNLOAD_SQLITE
set YEAR=2024
set VERSION=3460000
set DISTROFILE=sqlite.zip
set URL=https://sqlite.org/%YEAR%/sqlite-amalgamation-%VERSION%.zip

if not exist "%DISTROFILE%" (
  echo ===== Downloading current SQLite release =====
  curl %URL% --output "%DISTROFILE%"
  if %ErrorLevel% EQU 0 (
    echo ----- Downloaded  current SQLite release -----
  ) else (
    set ERROR_STATUS=%ErrorLevel%
    echo Error downloading SQLite distro.
    echo Errod code: !ERROR_STATUS!
  )
) else (
  echo ===== Using previously downloaded SQLite distro =====
)

exit /b %ERROR_STATUS%


:: ============================================================================
:EXTRACT_SQLITE
if not exist "%DISTRODIR%\sqlite3.c" (
  echo ===== Extracting SQLite distro =====
  tar -xf "%DISTROFILE%"
  if %ErrorLevel% EQU 0 (
    move "sqlite-amalgamation-%VERSION%" "%DISTRODIR%"
    echo ----- Extracted  SQLite distro -----
  ) else (
    set ERROR_STATUS=%ErrorLevel%
    echo Error extracting SQLite distro.
    echo Errod code: !ERROR_STATUS!
  )
) else (
  echo ===== Using previously extracted SQLite distro =====
)

exit /b %ERROR_STATUS%


:: ============================================================================
:BUILD_SQLITE
cd /d "%DISTRODIR%"

if not exist sqlite3.lo     (cl -O2 -c sqlite3.c -Fosqlite3.lo)
if not exist libsqlite3.lib (lib sqlite3.lo /OUT:libsqlite3.lib)
if not exist libsqlite3.dmp (dumpbin /ALL libsqlite3.lib /OUT:libsqlite3.dmp)

echo EXPORTS > sqlite3.def
set Command=findstr /XRB /C:"^ *1 sqlite[^ ]* *$" libsqlite3.dmp
for /f "Usebackq tokens=2 delims= " %%I in (`%Command%`) do (
    echo %%I
) 1>>sqlite3.def

lib  /MACHINE:x64 /DEF:sqlite3.def
link /MACHINE:x64 /DEF:sqlite3.def sqlite3.lo /DLL /OUT:sqlite3.dll

exit /b 0

Скрипт относительно небольшой и разбит на функциональные блоки, поэтому я не буду подробно останавливаться на нем (подробности смотрите в коде). При выполнении скрипт загружает копию выпуска SQLite amalgamation , расширяет ее и собирает (среда MSBuild должна быть активирована, как и раньше). Он создает каталог «sqlite» с несколькими файлами, включая sqlite3.dll и sqlite3.lib, которые можно использовать, как и раньше. Этот процесс можно использовать для динамического связывания приложения со специально созданным SQLite, который может интегрировать дополнительные расширения, такие как ICU (см., например, этот проект, в котором основное внимание уделяется инструментальной цепочке MinGW, но также обсуждается среда MSBuild). и предоставляет пользовательские сценарии сборки).

Встраивание кастомного SQLite — взлом процесса сборки Rusqlite

Эта часть, пожалуй, самая сложная, и ее основная цель — скорее исследовательский характер, чем рецепт для повседневного использования. Когда используется один из «связанных» вариантов сборки, процесс сборки Rusqlite, управляемый Cargo, компилирует файл объединения sqlite3.c, включенный в каталог «libsqlite3-sys\sqlite3» репозитория Rusqlite. В принципе, этот файл объединения можно заменить собственной копией, но это самая простая часть. Поскольку процесс сборки SQLite контролируется во время сборки с помощью параметров компилятора, передача этих параметров компилятору C, вызываемому Cargo, имеет важное значение (если только вы не хотите иметь дело со сценарием сборки Rust ("libsqlite3-sys\build.rs"), который помимо этого исследования). Сценарий «libsqlite3-sys\build.rs» принимает параметры конфигурации SQLite «-Dxx» через переменную среды «LIBSQLITE3_FLAGS», но он отклоняет другие типы параметров в этой переменной, например параметры включения. Более того, есть еще варианты связывания, которые, возможно, придется как-то передать.

Например, у меня есть расширенный сценарий (или сценарии, некоторые из которых доступны из соответствующего репозитория и более новый сценарий MSVC, доступный здесь ), который позволяет взломать процесс сборки SQLite. Скрипты не только включают интегрированные расширения SQLite, но также «интегрируют» несколько загружаемых расширений. Среди прочего я интегрирую расширение Zipfile, которое зависит от библиотеки zlib , и включаю расширение ICU, которое зависит от библиотеки ICU . Оба этих расширения требуют флагов компилятора и компоновщика. Мне неизвестно общее решение, но инструменты MSBuild поддерживают специальные переменные среды "CL"/"_CL_" и "LINK"/"_LINK_", которые позволяют передавать необходимые параметры компилятора/компоновщика. Большая часть кода расширенных сценариев ориентирована на создание индивидуального файла объединения. После подготовки это объединение можно скомпилировать в библиотеку dll или использовать для замены объединения, включенного в Rusqlite. Перед вызовом Cargo скрипт устанавливает указанные переменные среды. Соответствующий раздел скрипта:

:: ============================================================================
:RUSQLITE
:: 
:: If RUSQLITE_REPO is set and valid, execute bundled build 
:: 

if not exist "%RUSQLITE_REPO%\libsqlite3-sys\sqlite3\sqlite3.c" (exit /b 0)


echo ========== Building RUSQLITE ===========

cd /d "%RUSQLITE_REPO%\libsqlite3-sys\sqlite3"
if not exist "sqlite3.c.orig" (
    copy /Y "sqlite3.c" "sqlite3.c.orig"
    copy /Y "sqlite3.h" "sqlite3.h.orig"
)
copy /Y "%BINDIR%\src"

if %USE_ZLIB% EQU 1 (
    set ZLIBINCDIR=!DISTRODIR!\compat\zlib
    set ZLIBLIBDIR=!DISTRODIR!\compat\zlib
    set _CL_=!_CL_! "-I%DISTRODIR%\compat\zlib"
    set LINK=!LINK! "/LIBPATH:!ZLIBLIBDIR!"
    set _LINK_=!_LINK_! zdll.lib
)

if %USE_ICU% EQU 1 (
    set _CL_=!_CL_! -DSQLITE_ENABLE_ICU=1 "-I!ICUINCDIR!"
    set LINK=!LINK! "/LIBPATH:!ICULIBDIR!"
    set _LINK_=!_LINK_! icuuc.lib icuin.lib
    set Path=!ICUBINDIR!;!Path!
)

cd /d "%RUSQLITE_REPO%"
set LIBSQLITE3_FLAGS=%EXT_FEATURE_FLAGS%
set SQLITE3_LIB_DIR=%BINDIR%"
::set LINK=!LINK! "/LIBPATH:%BINDIR%"
if not defined EXAMPLE_NAME set EXAMPLE_NAME=intro_sqlite_function_list
rem  --features bundled
call cargo build
call cargo run --example "%EXAMPLE_NAME%"
cd /d "%BASEDIR%"

echo ---------- Built    RUSQLITE -----------

exit /b 0

В дополнение к параметрам компилятора «-D» для каждой связанной библиотеки сценарий передает параметры компилятора «INCLUDE_DIR», «LIB_DIR» и имена файлов *.lib.

Поскольку библиотека SQLite скомпонована статически, наиболее очевидный способ продемонстрировать разницу между обычной и пользовательской версиями SQLite — это создать и запустить специально созданную демонстрационную версию. Подготовленная демонстрация возвращает результаты двух запросов:

SELECT name FROM pragma_module_list() ORDER BY name;
SELECT lower('щЩэЭюЮфФ') || upper('щЩэЭюЮфФ');

Первый запрос возвращает список доступных модулей; второй запрос проверяет преобразование регистра с символами, отличными от ANSI (в данном случае кириллицей). Ниже приведены выходные данные этого примера, скомпилированные с дополнительными функциями и без них (выровненные вручную).

+++++++Стандартная сборка++++++++ +++++++++С дополнительными функциями++++++++++ байт-код csv база данных база данных фсдир фтс3 фтс3 fts3токенизировать fts3токенизировать fts4 fts4 fts4aux fts4aux фтс5 фтс5 fts5vocab fts5vocab генерировать_серию геополия json_each json_each json_tree json_tree pragma_module_list pragma_module_list дерево дерево rtree_i32 rtree_i32 sqlite_dbpage sqlite_stmt table_used zip-файл CI-тест: щЩэЭюЮфФщЩэЭюЮфФ CI-тест: щщээююффЩЭЭЮЮФФ

Честно говоря, в данном конкретном случае это упражнение не увенчалось успехом на 100%. Важно помнить, что статическое связывание — это всего лишь запрос, который может быть выполнен, а может и не быть выполнен. Оказалось, что со всеми добавленными дополнениями, включая три динамически подключаемые библиотеки DLL, статическое связывание SQLite было невозможно. В то же время этот подход может быть полезен для исследовательских сборок сторонних приложений, где функцию rusqlite нельзя легко указать напрямую.

ОБНОВЛЕНИЕ: Создание приложения SQLx.

Мне удалось создать приложение SQLx, следуя последнему подходу, встраивающему собственное объединение SQLite (я добавил соответствующий раздел в расширенный сценарий сборки, упомянутый выше и доступный на GitHub). Интересно, что на этот раз цепочки инструментов Rust/MSBuild сумели выполнить запрос статического связывания с библиотекой SQLite. И скомпилированный демонстрационный проект, и двоичный файл SQLx статически связываются с SQLite, наследуя при этом его зависимости от ICU/zlib.

Другие вопросы по теме