ECPG a un support limité pour les applications C++. Cette section décrit certains pièges.
Le préprocesseur ecpg
prend un fichier
d'entrée écrit en C (ou quelque chose qui ressemble à du C) et
des commandes SQL embarquées, et convertit les commandes SQL
embarquées dans des morceaux de langage, et finalement génère
un fichier .c
. Les déclarations de fichiers
d'entête des fonctions de librairie utilisées par les morceaux
de langage C que génère ecpg
sont entourées de
blocs extern "C" { ... }
quand ils sont
utilisés en C++, ils devraient donc fonctionner de façon transparente
en C++.
En général, toutefois, le préprocesseur ecpg
ne comprend que le C; il ne gère pas la syntaxe spéciale et les
mots réservés du langage C++. Par conséquent, du code SQL embarqué
écrit dans du code d'une application C++ qui utilise des fonctionnalités
compliquées spécifiques au C++ pourrait ne pas être préprocessé
correctement ou pourrait ne pas fonctionner comme prévu.
Une façon sûre d'utiliser du code SQL embarqué dans une application C++ est de cacher les appels à ECPG dans un module C, que le code C++ de l'application appelle pour accéder à la base, et lier ce module avec le reste du code C++. Voyez Section 36.13.2 à ce sujet.
Le préprocesseur ecpg
comprend la porté des
variables C. Dans le langage C, c'est plutôt simple parce que la
portée des variables ne dépend que du bloc de code dans lequel
elle se trouve. En C++, par contre, les variables d'instance sont
référencées dans un bloc de code différent de la position de déclaration,
ce qui fait que le préprocesseur ecpg
ne comprendra
pas la portée des variables d'instance.
Par exemple, dans le cas suivant, le préprocesseur ecpg
ne peut pas trouver de déclaration pour la variable dbname
dans la méthode test
, une erreur va donc se produire.
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; }
Ce code génèrera une erreur comme celle qui suit :
ecpg test_cpp.pgc
test_cpp.pgc:28: ERROR: variable "dbname" is not declared
Pour éviter ce problème de portée, la méthode test
pourrait être modifiée pour utiliser une variable locale comme stockage
intermédiaire. Mais cette approche n'est qu'un mauvais contournement,
parce qu'elle rend le code peu élégant et réduit la performance.
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); }
Si vous comprenez ces limitations techniques du préprocesseur
ecpg
en C++, vous arriverez peut-être à la conclusion
que lier des objets C et C++ au moment du link pour permettre à des
applications C++ d'utiliser les fonctionnalités d'ECPG pourrait
être mieux que d'utiliser des commandes SQL embarquées dans du code
C++ directement. Cette section décrit un moyen de séparer des commandes
SQL embarquées du code d'une application C++ à travers un exemple simple.
Dans cet exemple, l'application est implémentée en C++, alors que
C et ECPG sont utilisés pour se connecter au serveur PostgreSQL.
Trois types de fichiers devront être créés: un fichier C
(*.pgc
), un fichier d'entête, et un fichier C++ :
test_mod.pgc
#
Un module de routines pour exécuter des commandes SQL embarquées en C.
Il sera converti en test_mod.c
par le préprocesseur.
#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
#
Un fichier d'entête avec les déclarations des fonctions
du module C (test_mod.pgc
). Il est inclus
par test_cpp.cpp
. Ce fichier devra avoir un
bloc extern "C"
autour des déclarations,
parce qu'il sera lié à partir d'un module C++.
#ifdef __cplusplus extern "C" { #endif void db_connect(); void db_test(); void db_disconnect(); #ifdef __cplusplus } #endif
test_cpp.cpp
#
Le code principal de l'application, incluant
la routine main
, et dans cet exemple
une classe 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; }
Pour construire l'application, procédez comme suit. Convertissez
test_mod.pgc
en test_mod.c
en
lançant ecpg
, et générez
test_mod.o
en compilant
test_mod.c
avec le compilateur C:
ecpg -o test_mod.c test_mod.pgc cc -c test_mod.c -o test_mod.o
Puis, générez test_cpp.o
en compilant
test_cpp.cpp
avec le compilateur C++:
c++ -c test_cpp.cpp -o test_cpp.o
Finalement, liez ces objets, test_cpp.o
et test_mod.o
, dans un exécutable, en utilisant
le compilateur C++:
c++ test_cpp.o test_mod.o -lecpg -o test_cpp