[C] API PostgreSQL

Linking libpq

a marqué ce sujet comme résolu.

Bonjour,

J’ai essayé il y a quelque temps de me mettre au C, une pause depuis… :) Je reprends et souhaite m’interfacer en C, au SGBDR PostgreSQL. API libpq.

Objectif : gestion de 4 planches de culture d’un potager en carré (association / rotation)

  • API : /usr/local/include/postgresql/libpq-fe.h
  • Utiliser l’option -I pour indiquer le chemin : zcc -I /usr/local/include/postgresql

Le problème que je rencontre :

(...)
ld: error: undefined symbol: PQstatus
>>> referenced by test.c
>>>               /tmp/test-6587af.o:(main)

ld: error: undefined symbol: PQerrorMessage
>>> referenced by test.c
>>>               /tmp/test-6587af.o:(main)
(...)

De ma compréhension, il est nécessaire ici d’inclure, le chemin de la librairie libpq lors de la compilation :

  • /usr/local/lib/libpq.a
  • /usr/local/lib/libpq.so.6.12

J’ai essayé via l’option -l sans succès. J’ai durant mes recherches, perdu le fil entre liaison statique et liaison dynamique lors de l’étape du linker:

OpenBSD clang version 11.1.0
Target: amd64-unknown-openbsd7.0
Thread model: posix
InstalledDir: /usr/bin
 "/usr/bin/clang" -cc1 -triple amd64-unknown-openbsd7.0 -emit-obj -mrelax-all -disable-free -disable-llvm-verifier -discard-value-names -main-file-name test.c -mrelocation-model pic -pic-level 1 -pic-is-pie -mframe-pointer=all -relaxed-aliasing -fno-rounding-math -mconstructor-aliases -munwind-tables -target-cpu x86-64 -target-feature +retpoline-indirect-calls -target-feature +retpoline-indirect-branches -fno-split-dwarf-inlining -debugger-tuning=gdb -v -resource-dir /usr/lib/clang/11.1.0 -I /usr/local/include/postgresql -internal-isystem /usr/lib/clang/11.1.0/include -internal-externc-isystem /usr/include -Wall -Wextra -pedantic -std=c11 -fdebug-compilation-dir /home/oliv/Coding/Potager -ferror-limit 19 -fwrapv -D_RET_PROTECTOR -ret-protector -fno-builtin -fgnuc-version=4.2.1 -fno-builtin-malloc -fno-builtin-calloc -fno-builtin-realloc -fno-builtin-valloc -fno-builtin-free -fno-builtin-strdup -fno-builtin-strndup -faddrsig -o /tmp/test-db52b9.o -x c test.c

clang -cc1 version 11.1.0 based upon LLVM 11.1.0 default target amd64-unknown-openbsd7.0

#include "..." search starts here:
#include <...> search starts here:
 /usr/local/include/postgresql
 /usr/lib/clang/11.1.0/include
 /usr/include
End of search list.
 "/usr/bin/ld" -e __start --eh-frame-hdr -Bdynamic -dynamic-linker /usr/libexec/ld.so -o a.out /usr/lib/crt0.o /usr/lib/crtbegin.o -L/usr/lib -l/usr/local/lib/libpq.so.6.12 /tmp/test-db52b9.o -lcompiler_rt -lc -lcompiler_rt /usr/lib/crtend.o
ld: error: unable to find library -l/usr/local/lib/libpq.so.6.12
clang: error: linker command failed with exit code 1 (use -v to see invocation)

Une orientation ?

Salut,

Ça manque un peu de précision… Comment est-ce que tu compiles ? Est-ce que le fichier /usr/local/lib/libpq.so.6.12 existe bien puisque visiblement le linker ne le trouve pas ?

J’ai durant mes recherches, perdu le fil entre liaison statique et liaison dynamique lors de l’étape du linker

C’est à dire ? Tu ne comprends pas la différence ou bien tu as d’autres soucis ?

Hello Adri1,

merci pour ton retour.

  1. Comment je compile :(zcc est un alias :zcc=’clang -Wall -Wextra -pedantic -std=c11 -fno-common -fno-builtin'
zcc -v -I /usr/local/include/postgresql -L /usr/local/lib/libpq.so.6.12 test.c

/usr/local/include/postgresql est la location de #include <libpq-fe.h>

fichier source :

#include <stdio.h>
#include <stdlib.h>
#include <libpq-fe.h>

void do_exit(PGconn *conn) {

    PQfinish(conn);
    exit(1);
}

int main() {

    PGconn *conn = PQconnectdb("host=xxx.xxx.xxx.xxx port=5432 user=oliv password=xxxxxxxx dbname=potager");

    if (PQstatus(conn) == CONNECTION_BAD) {

        fprintf(stderr, "Connection to database failed: %s\n",
            PQerrorMessage(conn));
        do_exit(conn);
    }

    char *user = PQuser(conn);
    char *db_name = PQdb(conn);
    char *pswd = PQpass(conn);

    printf("User: %s\n", user);
    printf("Database name: %s\n", db_name);
    printf("Password: %s\n", pswd);

    PQfinish(conn);

    return 0;
}
  1. Recherche, dans la doc Postgesql (fournisseur de la libp) il est précisé qu’il faille indiqué au linker la location de la libq.

https://www.postgresql.org/docs/13/libpq.html

Sur OpenBSD (ma plateforme) elle est située ici :

/usr/local/lib/libpq.a
/usr/local/lib/libpq.so.6.12

Ici, je ne sais / comprends pas, comment indiquer au linker la location de la libpq.

Merci.

+0 -0
  1. Comment je compile :(zcc est un alias :zcc=’clang -Wall -Wextra -pedantic -std=c11 -fno-common -fno-builtin'

Écrire un alias et compiler à la main, c’est bien quand on apprend le C et qu’on a un seul fichier sans dépendance (et encore, à mon sens c’est une erreur de faire ça à la main dans n’importe quelles circonstances). Au delà de ce stage, si tu veux t’éviter des migraines inutiles, utilise un système de build. Personne ne tape les commandes de build à la main. Par exemple avec CMake :

cmake_minimum_required(VERSION 3.18 FATAL_ERROR)

project(postgre LANGUAGES C)

add_executable(main main.c)

target_compile_options(main PRIVATE
    -Wall -Wextra -pedantic -fno-common -fno-builtin
)
target_compile_features(main PRIVATE c_std_11)

find_package(PostgreSQL REQUIRED)
target_link_libraries(main PRIVATE PostgreSQL::PostgreSQL)

Puis

cmake -B build
cmake --build build

Pour compiler. Ton exécutable sera dans le répertoire de build.

zcc -v -I /usr/local/include/postgresql -L /usr/local/lib/libpq.so.6.12 test.c

-L est pour ajouter un dossier où chercher les bibliothèques en plus de ce qui est déjà défini par ton environment (dans lequel /usr/local/lib est probablement déjà). -l est pour donner le nom d’une bibliothèque, ici -lpq. Si tu as le chemin complet, tu peux le passer directement sans t’encombrer de -l ni -L. Mais tu as rarement besoin de gérer ce genre de trucs à la main, laisse faire un système de build comme CMake ou meson.

un bon début semble être : l’article CMAKE - Automatiser la compilation de nos programme sur Zeste de Savoir

C’est un excellent exemple de ce qu’il ne faut pas faire. À la décharge des auteurs, un problème de CMake est qu’il se traîne des casseroles des versions précédentes. Cet outil est tellement répandu que les développeurs ne peuvent pas se permettre de casser les anciennes fonctionnalités maintenant dépassées au profit des bonnes pratiques. On trouve donc plein de sources sur Internet, incluant celle-ci, qui font n’importe quoi. Un autre problème de CMake est que sa documentation n’est pas très facile à lire quand on ne sait pas déjà un peu où on va. Les deux soucis conjugués rendent l’apprentissage de CMake plus difficile que ce qu’il devrait être. Quelques pistes pour te permettre de t’y retrouver, en expliquant pourquoi ce que conseille le tuto n’est pas bon.

Maintenant, il faut lister les fichiers qui constituent notre projet, en les séparant entre fichiers d’en-têtes et fichiers sources.

CMake n’a pas besoin qu’on lui liste les fichiers d’en-têtes, sauf cas extrêmement particulier (si tu es dans ce cas là, tu le sais).

CMake utilise des variables pour ça.

Non, CMake utilise des targets. Qu’elles soient concrètes (library, executable) ou bien plus abstraites comme les OBJECTS et INTERFACE notamment. Une bonne façon de trier les codes que tu pourrais trouver sur Internet est qu’une utilisation massive de set est un bon signe que les conseils sont périmés.

On va définir, grâce à la fonction set, une variable HEADERS regroupant tous nos fichiers d’en-têtes et une variable SOURCES regroupant les fichiers sources.

HEADERS sert à rien, et SOURCES serait beaucoup mieux dans une library INTERFACE ou OBJECTS. Les listes qu’on peut créer avec set n’ont pas de sens sémantique pour CMake. Tant que tu ne connectes pas ces listes avec une target, CMake ne comprend pas que ce sont des fichiers sources. Tu as donc tout intérêt à ne même pas passer par set et utiliser une target directement. Tu peux alors attacher des options de compilations qui seront propagées correctement lorsque tu lies les targets avec target_link_library.

set (CMAKE_CXX_STANDARD 17)

Encore un mauvais usage de set. Les variables CMAKE_* sont là pour la rétro-compatibilité et des détails d’implémentation internes. Tu as en pratique très rarement besoin de les set, et c’est même souvent une mauvaise idée. Ici, tu forces le standard pour toutes les targets. Forcer des choses au niveau global n’est pas une bonne idée. Encore une fois, il faut se rattacher au concept de target, comme ce que j’ai fait dans mon exemple pour dire que l’exécutable main a besoin du standard C11.

set (CMAKE_EXE_LINKER_FLAGS "-static-libgcc -static-libstdc++ -static")

Oof, celle-ci combine la totale. Au lieu de forcer les flags pour le linker pour toutes les targets via une variable globale, il faut utiliser target_link_options.

Il y a plusieurs autres problèmes du même style dans le reste du texte. Surtout, l’autre grosse erreur est au moment de lier avec Boost :

Ces deux variables ne sont pas juste là pour être affichées. Pour que notre programme soit correctement généré, il faut qu’on lui indique les fichiers d’en-têtes et objets à inclure. C’est justement ce que font les commandes include_directories et link_directories.

include_directories et link_directories agissent au niveau global. Il vaut mieux utiliser les fonctions qui agissent au niveau des targets target_*. Et surtout, FindBoost est codé proprement et sort des IMPORTED targets que l’on peut lier directement pour ne pas avoir à gérer manuellement les dépendances (comme ce que j’ai fait avec PostgreSQL::PostgreSQL dans mon exemple). C’est ce qui est fait à moitié avec le if(UNIX) plus loin d’ailleurs (mais pas sous Windows pour une raison obscure), rendant certains chose complètement redondantes.

Les message pour vérifier le résultat de find_package(Boost) sont aussi du bruit parce que déjà inscrits par cette dernière.

Au final, leur CMakeLists.txt est très verbeux, difficile à lire, et manipule des états globaux au lieu de manipuler des targets. Quelques conseils pour éviter les même erreurs:

  • au lieu de set(var list of options/files), définir des INTERFACE ;
  • au lieu de set(CMAKE_*), manipuler les targets ;
  • au lieu d’utiliser des fonctions globales comme include_directories, manipuler les targets ;
  • utiliser les IMPORTED targets de find_package lorsqu’elles existent.

Malheureusement, il existe peu de bonnes sources sur CMake. Le tutorial officiel ainsi que la doc plus poussée sont pas mal mais un peu difficiles quand on débute. Une bonne stratégie est de s’imprégner de la philosophie du truc progressivement en cherchant sur internet divers tuto sur des points particulier et fuir les sources qui font les erreurs mentionnées plus haut pour séparer le grain de l’ivraie.

Merci pour ton retour

OK, j’ai passé la soirée sans lire ton nouveau message. Où les instructions fonctionnaient très bien…

J’ais lu cet article, ainsi que celui de développez.com. J’avais effectivement positionnés pleins de SET SRC et SET HEADERS… où je me suis fait un noeud pour comprendre pourquoi cela ne compilait pas… J’y étais arrivé.

Je cherche évidemment les bonnes pratiques et revient vers la solution initiale plus courte et sans noeuds au cerveau :)

cmake_minimum_required(VERSION 3.18 FATAL_ERROR)
project(Potager)

add_executable(
    main main.c
    )

target_compile_options(
    main PRIVATE
    -Wall -Wextra -pedantic -fno-common -fno-builtin
    )

target_compile_features(
    main PRIVATE
    c_std_11
    )

# Include PostgreSQL (libpq)
find_package(
    PostgreSQL REQUIRED
    )
target_link_libraries(main PRIVATE PostgreSQL::PostgreSQL)
cmake src/ build
cmake --build build

A moi de comprendre quand j’incluerait d’autres librairies ou devgait en developper…

Merci encore pour ton feedback.

+0 -0
Connectez-vous pour pouvoir poster un message.
Connexion

Pas encore membre ?

Créez un compte en une minute pour profiter pleinement de toutes les fonctionnalités de Zeste de Savoir. Ici, tout est gratuit et sans publicité.
Créer un compte