37.13. Приложения на C++ #
ECPG обеспечивает поддержку языка C++ в ограниченном объёме. Некоторые её особенности описаны в этом разделе.
Препроцессор ecpg
принимает входной файл, написанный на C (или языке, подобном C) со встраиваемыми командами SQL, преобразует встроенные команды SQL в конструкции языка C и в результате формирует файл .c
. Объявления библиотечных функций, вызываемых в конструкциях C, которые генерирует ecpg
, заворачиваются в блоки extern "C" { ... }
при использовании C++, так что они должны прозрачно работать в C++.
Однако вообще говоря, препроцессор ecpg
понимает только C; он не воспринимает особый синтаксис и зарезервированные слова языка C++. Поэтому какой-то код SQL, встроенный в код приложения на C++, в котором используются сложные особенности C++, может корректно не обработаться препроцессором или не работать как ожидается.
Надёжный подход к применению внедрённого кода SQL в приложении на C++ заключается в том, чтобы скрыть вызовы ECPG в модуле C, который будет вызываться приложением на C++ для работы с базой данных и который будет скомпонован с остальным кодом C++. Подробнее это описано в Подразделе 37.13.2.
37.13.1. Область видимости переменных среды #
Препроцессор ecpg
имеет понимание области видимости переменных в C. С языком C это довольно просто, так как область видимости переменных определяется их блоками кода. В C++, однако, переменные-члены класса задействуются не в том блоке кода, в каком они объявлены, так что препроцессор ecpg
не сможет корректно определить область видимости таких переменных.
Например, в следующем случае препроцессор ecpg
не сможет найти определение переменной dbname
в методе test
, так что произойдёт ошибка.
class TestCpp { EXEC SQL BEGIN DECLARE SECTION; char dbname[1024]; EXEC SQL END DECLARE SECTION; public: TestCpp(); void test(); ~TestCpp(); }; TestCpp::TestCpp() { EXEC SQL CONNECT TO testdb1; EXEC SQL SELECT pg_catalog.set_config('search_path', '', false); EXEC SQL COMMIT; } void Test::test() { EXEC SQL SELECT current_database() INTO :dbname; printf("current_database = %s\n", dbname); } TestCpp::~TestCpp() { EXEC SQL DISCONNECT ALL; }
При обработке данного кода будет выдано сообщение:
ecpg test_cpp.pgc
test_cpp.pgc:28: ERROR: variable "dbname" is not declared
(test_cpp.pgc:28: ОШИБКА: переменная "dbname" не объявлена)
Для решения этой проблемы можно немного изменить метод test
и задействовать в нём локальную переменную для промежуточного хранения. Но предложенный подход нельзя считать хорошим, так как это портит код и снижает производительность.
void TestCpp::test() { EXEC SQL BEGIN DECLARE SECTION; char tmp[1024]; EXEC SQL END DECLARE SECTION; EXEC SQL SELECT current_database() INTO :tmp; strlcpy(dbname, tmp, sizeof(tmp)); printf("current_database = %s\n", dbname); }
37.13.2. Разработка приложения на C++ с внешним модулем на C #
Если вы поняли технические ограничения препроцессора ecpg
с C++, вы можете прийти к заключению, что для использования ECPG в приложениях на C++ лучше связывать код C с кодом C++ на стадии компоновки, а не внедрять команды SQL непосредственно в код на C++. В данном разделе показывается, как отделить встраиваемые команды SQL от кода приложения на C++, на простом примере. В этом примере приложение реализуется на C++, а взаимодействие с сервером Postgres Pro построено на C и ECPG.
Для сборки нужно создать три типа файлов: файл на C (*.pgc
), заголовочный файл и файл на C++:
test_mod.pgc
#Модуль подпрограмм будет выполнять SQL-команды, встроенные в C. Этот код нужно будет преобразовать в
test_mod.c
с помощью препроцессора.#include "test_mod.h" #include <stdio.h> void db_connect() { EXEC SQL CONNECT TO testdb1; EXEC SQL SELECT pg_catalog.set_config('search_path', '', false); EXEC SQL COMMIT; } void db_test() { EXEC SQL BEGIN DECLARE SECTION; char dbname[1024]; EXEC SQL END DECLARE SECTION; EXEC SQL SELECT current_database() INTO :dbname; printf("current_database = %s\n", dbname); } void db_disconnect() { EXEC SQL DISCONNECT ALL; }
test_mod.h
#Заголовочный файл с объявлениями функций в модуле на языке C (
test_mod.pgc
). Он включается вtest_cpp.cpp
. Объявления в этом файле должны заключаться в блокextern "C"
, так как он будет связываться с модулем C++.#ifdef __cplusplus extern "C" { #endif void db_connect(); void db_test(); void db_disconnect(); #ifdef __cplusplus } #endif
test_cpp.cpp
#Основной код приложения, содержащий функцию
main
, а также, в данном примере, класс C++.#include "test_mod.h" class TestCpp { public: TestCpp(); void test(); ~TestCpp(); }; TestCpp::TestCpp() { db_connect(); } void TestCpp::test() { db_test(); } TestCpp::~TestCpp() { db_disconnect(); } int main(void) { TestCpp *t = new TestCpp(); t->test(); return 0; }
Для сборки приложения проделайте следующее. Преобразуйте test_mod.pgc
в test_mod.c
с помощью ecpg
, а затем получите test_mod.o
, скомпилировав test_mod.c
компилятором C:
ecpg -o test_mod.c test_mod.pgc cc -c test_mod.c -o test_mod.o
После этого получите test_cpp.o
, скомпилировав test_cpp.cpp
компилятором C++:
c++ -c test_cpp.cpp -o test_cpp.o
Наконец, свяжите полученные объектные файлы, test_cpp.o
и test_mod.o
, в один исполняемый файл, выполнив компоновку под управлением компилятора C++:
c++ test_cpp.o test_mod.o -lecpg -o test_cpp