Aide - Recherche - Membres - Calendrier
Version complète : Je n'arrive pas à enregistrer des chaînes de caractères
Les Forums de PalmAttitude.org > GENERAL PalmOS > Développement sous PalmOS
naguttes
je n'arrive pas à enregistrer des chaînes de caractères dans ma base.
j'ai défini la structure suivante:
struct {
UInt16 Type;
Char Titre;
} LivreRec


Type me permet d'enregistrer le numéro de l'élément sélectionné dans une popup liste
Titre est une chaîne de caractères saisie.
Ensuite voici les instructions que j'utilise pour créer un enregistrement dans ma base (c'est plus ou moins du copier/coller d'un programme que j'ai récupéré ailleur):

LivreRec Record;
LivreRec *RecordP;
MemHandle NewRecH,rec;
UInt16 Index=0;
FormPtr form;
ListPtr list;
FieldPtr field;
Char *text;

form = FrmGetActiveForm();
field = FrmGetObjectPtr(form, FrmGetObjectIndex(form, fldTitre));
text = FldGetTextPtr(field);
FrmCustomAlert(custAlrtID, "AddRec", "Titre", text); trace permettant de voir que le titre est bien saisi

Record.Titre=text;
// Get list
list = FrmGetObjectPtr(form, FrmGetObjectIndex(form, CatListID));
// Get item # selected
Record.Type=LstGetSelection(list);
NewRecH=DmNewRecord(LivreDB,&Index,sizeof(Record));
RecordP=MemHandleLock(NewRecH);
DmWrite(RecordP,0,&Record,sizeof(Record));
MemHandleUnlock(NewRecH);
DmReleaseRecord(LivreDB,Index,true);


Cela fonctionne pour la zone Type que j'arrive bien à relire après coup mais impossible d'enregistrer la zone de texte (j'arrive cependant à afficher le contenu de la zone de saisie "fldTitre"). j'ai essayé ensuite différentes combinaisons avec les pointeurs (que je ne maîtrise pas) mais soit j'ai une erreur de compilation indiquant que les champs sont de types incompatibles sur l'instruction Record.Titre=text; soit ça se compile bien mais ça ne donne aucun résultat.

Merci de votre aide.
Question complémentaire :
Quelle est la différence entre la déclaration char et Char?
olivier101
Attention Char n'est pas une chaine de caractères mais UN caractère !
Donc dans ta déclaration
Char Titre;
tu réserves en fait un seul caractère pour Titre...
(remarque en passant: il est déconseillé de mettre une majuscule au début des noms de variables, ceci pour les différencier des types)

La solution la plus simple est de réserver un tableau de caractères avec une longueur maximale:
Char Titre[32];
par exemple.
Il te faut ensuite copier la chaîne que tu récupères de FldGetTextPtr() dans Titre, mais comme il ne s'agit pas d'une valeur simple il faut utiliser StrNCopy:
StrCopy(Record.Titre, text);
Toutefois cette solution présente le risque de déborder du nombre de caractères que tu as réservés (32); une meilleure solution consiste à utiliser StrNCopy, qui fait la même chose mais avec une limite sur le nombre de caractères à copier:
StrNCopy(Record.Titre, text, 31);
Record.Titre[31] = 0;

La première ligne copie au max 31 caractères dans Record.Titre, la deuxième s'assure que la chaine est bien terminée par un 0, car StrNCopy ne met pas le 0 final si la chaine à copier est plus longue que la limite.
Bien sûr il vaut mieux ne pas hadcoder les limites, mais utiliser un define
#define MAXTITRE 32
Char Titre[MAXTITRE ];
...
StrNCopy(Record.Titre, text, MAXTITRE - 1);
Record.Titre[MAXTITRE - 1] = 0;


Enfin, char est un type standard de C, alord que Char est un type propre à PalmOS. Il est conseillé d'utiliser Char.
naguttes
Merci, je testerai ce soir
naguttes
Ca marche, merci beaucoup.
Maintenant encore une autre question sur la manipulation de ces chaînes de caractère.
L'enregistrement que j'obtiens est de longueur fixe car quelle que soit la longueur du titre, on enregistre la longueur de MAXTITRE. Pour optimiser l'espace, il faudrait compacter l'enregistrement avant de le sauver (surtout dans la mesure où j'aurai à terme plusieurs champs caractère).
J'imagine un peu la mécanique qui consisterait à concaténer l'ensemble des champs caractère dans un seul champ à l'aide des fonctions StrNCopy et StrLen (j'ai vu qu'elle retournait la longueur utilisée et non la longueur déclarée) et en y associant des variables stockant le "offset". Mais il me reste le problème de la déclaration de ce champ réceptacle qui doit être de longueur varibale.
Patrice
C'est là où il vaut mieux utiliser l'allocation dynamique...
naguttes
CITATION(Patrice)
C'est là où il vaut mieux utiliser l'allocation dynamique...


C'était dans la formation? si oui j'ai bien peur d'avoir oublié comment ça marche :? (ça devait être à la fin quand mon regard concentré sur les photos n'était en fait qu'un masque trompeur).
olivier101
C'est ce que Palm appelle des enregistrements "compactés", il me semble que les programmes de base comme l'agenda etc. utilisent cette technique qui consiste à stocker la longueur des chaines et à n'écrire que les caractères utiles.
On doit pouvoir trouver des sources d'exemples, je vais voir si je remets la main dessus...
olivier101
Je ne trouve plus.... Patriiiiiiiiiiiiiice, tu n'as pas ça sous la main?
Patrice
Je vois bien les exemples fournis avec le SDK mais ce n'est pas super simple à suivre anim_wink.gif

Pour proposer une solution pas trop compliquée (autant à comprendre qu'à expliquer, c'est que j'ai du boulot moi icon_lol2.gif ) : l'idée est de mettre toutes les chaînes bout à bout et d'en faire un enregistrement. Avec un record, c'est relativement simple puisque l'appel à DmWrite permet de spécifier la position où écrire.

Supposons que tu veuilles enregistrer un record avec une chaine de 12 caractères, une de 15 et une de 12. Tu crées un record de 12+15+12+3 (les 3 pour enregistrer le marqueur de fin de chaîne, 0).
Ensuite tu écris la première chaine à la position 0 (13 caractères, pour inclure le 0 final), la deuxième à la position 13 (16 caractères) et la troisième à la position 29 (13 caractères).

En pseudo-code (je n'ai pas les API sous les yeux), un peu plus générique :

Char * chaine1, * chaine2, * chaine3;
int pos, len;
MemHandle hnd;
Char * ptr;

len = StrLen(chaine1) + StrLen(chaine2) + StrLen(chaine3) + 3;
hnd = DmNewRecord(len);
ptr = MemHandleLock(hnd);
pos = 0;
len = StrLen(chaine1)+1;
DmWrite(ptr, pos, chaine1, len);
pos += len;
len = StrLen(chaine2)+1;
DmWrite(ptr, pos, chaine2, len);
pos += len;
len = StrLen(chaine3)+1;
DmWrite(ptr, pos, chaine3, len);

Pour relire le record, rien de plus simple : tu récupère un pointeur sur le record, il pointe sur la première chaine, tu avances de la longueur + 1 et tu as la deuxième chaîne... et ainsi de suite. En code :

hnd = DmGetRecord();
ptr = MemHandleLock(hnd);
chaine1 = ptr;
ptr += StrLen(ptr) + 1;
chaine2 = ptr;
ptr += StrLen(ptr) + 1;
chaine3 = ptr;
naguttes
Merci, je testerai ce soir
tintaal
Il peut t'être intéressant de créer des fonctions de manipulation de flux (streams) gérant automatiquement un couple buffer / taille / pointeur relatif au début du buffer. De cette façon, tu géres l'accès à un enreg de la façon suivante :
- Tu sérialises tous tes champs dans un flux.
- Tu écris le flux dans ta base.

A la lecture, tu lis un flux, puis tu extraies tes champs de ton flux... C'est un premier pas vers l'écriture d'un tout petite moteur de bases de données, dans lequel tu ne coderas pas "en dur" la structure de tes tables - ce qui te permettra de réutiliser du code d'un projet à l'autre...
naguttes
CITATION(tintaal)
Il peut t'être intéressant de créer des fonctions de manipulation de flux (streams) gérant automatiquement un couple buffer / taille / pointeur relatif au début du buffer. De cette façon, tu géres l'accès à un enreg de la façon suivante :
- Tu sérialises tous tes champs dans un flux.
- Tu écris le flux dans ta base.

A la lecture, tu lis un flux, puis tu extraies tes champs de ton flux... C'est un premier pas vers l'écriture d'un tout petite moteur de bases de données, dans lequel tu ne coderas pas "en dur" la structure de tes tables - ce qui te permettra de réutiliser du code d'un projet à l'autre...
8O 8O 8O 8O 8O

Merci, mais je penses que tu n'as pas réalisé que tu parlais à un nul icon_lol2.gif (C et Palm OS) et qu'il faut me parler plus clairement si tu veux que je comprennes. C'est quoi un flux pour toi?
naguttes
CITATION(Patrice)
Pour relire le record, rien de plus simple...


Rien de plus simple, rien de plus simple il faut le dire vite (je commence à devenir parano moi à force de ne pas arriver à faire les choses simples).
L'écriture ça s'est passé nickel chrome (la preuve je n'ai pas posé de question sourire.gif ) mais pour relire l'enregistrement c'est une autre paire de manche et j'ai pourtant essayé de suivre les instructions de Patrice
Voici mon code

MemHandle RecordH;
LivreRec *Record; LivreRec est une structure qui comprend des entiers en début d'enregistrement et 3 chaînes de caractères.

RecordH=DmQueryRecord(LivreDB,index);
Record=MemHandleLock(RecordH);
gRecord=*Record GRecord est de structure LivreRec

Et après rien ne marche, j'ai essayé
Record+=StrLen(Record)+1; mais ça me sort une erreur à la compil sur StrLen

Record+=StrLen(gRecord.Titre);
gRecord.SousTitre=Record

Mais ça ne donne rien et je n'ai pas l'impression que l'addition sur Record fonctionne (augmentation de 1 alors que je rajoute plus de 1) et en plus j'ai une erreur de compil sur l'affectation à SousTitre (j'ai aussi essayé plusieurs formules).
Patrice
Si ta structure LivreRec est de longueur fixe, tu n'as pas besoin des astuces ci-dessus pour la relire. A partir de ton exemple, tu peux simplement accéder aux champs de la structure :
gRecord.Titre
gRecord.SousTitre
...
naguttes
La structure LivreRec est de longueur fixe mais pas l'enregistrement que je lis qui a des champs entier au début puis 3 chaines de longueur variable terminée par 0.
L'affectation
gRecord=*Record
permet bien de récupérer les champs numérique et le premier champ caractère mais pas les 2 autres.

Peut être est ce ma déclaration
LivreRec *Record
qui n'est pas bonne?

En plus j'ai maintenant un pb avec le memory manager qui m'oblige à faire un reset (on n'a pas besoin de faire un DmReleaseRecord sur un DmQueryRecord?)
Patrice
CITATION(naguttes)
RecordH=DmQueryRecord(LivreDB,index);
Record=MemHandleLock(RecordH);
gRecord=*Record         GRecord est de structure LivreRec

Et après rien ne marche, j'ai essayé
Record+=StrLen(Record)+1;       mais ça me sort une erreur à la compil sur StrLen

Record+=StrLen(gRecord.Titre);
gRecord.SousTitre=Record

Cette dernière ligne, n'est pas correcte : tu changes un pointeur de position alors, que c'est un pointeur "fixe".

Essaye :
RecordH = DmQueryRecord(LivreDB,index);
Record = MemHandleLock(RecordH);
// Pas la peine gRecord=*Record

gRecord.numero = Record -> numero;
// ... idem pour tous les numériques
Ptr = Record -> Titre;
StrCopy(gRecord.Titre, Ptr);
Ptr += StrLen(Ptr) + 1;
StrCopy(gRecord.Titre, Ptr);
// Et ainsi de suite...

Mais tu es bien certain d'avoir écrit les chaines en succession et AVEC le "0" terminal ?
naguttes
A priori, j'ai mis le 0 terminal à la fin de chaque chaîne de caractères (je vais quand même revérifier).
Je teste (si possible ce soir) et je donne le résulat) mais j'avais l'impression que le StrLen(Record) me sortait une erreur à la compil.
(Il faudra que tu m'expliques en détail le ->)

Merci je te tiens au courant
naguttes
Ca a marché, j'ai toutes mes zones qui sont affichées mais j'ai aussi un problème avec le memory manager (il semblerait que ce soit lié à à "free handle"). Il y a une ligne (du programme compilé pas du source) sur laquelle se produit mon erreur y-a-t-il un moyen de trouver à quelle instruction cela correspond.
J'ai vérifié et j'ai un MemHandleUnlock après chaque MemHandleLock, faut il faire quelque chose de particulier pour un MemHandleNew ou pour un FldSetTextHandle.
Patrice
CITATION(naguttes)
faut il faire quelque chose de particulier pour un MemHandleNew ou pour un FldSetTextHandle.

Il faut libérer la mémoire allouée avec MemHandleNew(), via MemHandleFree(). Sinon, tu auras des "fuites" et, uniquement sur émulateur, une erreur en quittant l'appli.
Si un handle est associé à un champ (via FldSetTextHandle()), il faut rompre ce lien avant de manipuler / supprimer la zone mémoire (avec un FldSetTextHandle(pfld, NULL)).
C'est tout ce que je vois.

Pour tes recherches, il faudrait utiliser le debogueur (gdb) mais je ne l'utilises pas et je ne peux donc pas te conseuller. Tu peux aussi poser des traceurs (avec ErrDisplay()) pour afficher des messages au fur et à mesure de l'exécution.
naguttes
A quel moment exactement faut il faire ces opérations (MemHandleFree)
Si je les fait à la fin la procédure qui a créé le MemHandleNew (c'est la fonction qui me permet d'assigner mon enregistrement aux champs de l'écran), l'erreur continue et l'affichage n'est pas bon.
Même chose si je le fait à la fermeture de l'ecran quand j'appuie sur le bouton qui va mener à l'exécution d'un frmGotoForm
serait ce dans le traitement de l'événement frmCloseEvent?
Faut il les faire avant ou après le MemHandleUnlock?
cancan
La longueur variable c'est bien mieux, surtout sur Palm ou la mémoire est comptée.

Du coup la definition Char Titre; est bonne.

Pour ecrire ton titre il te faut faire un DmStrCopy a la place du DmWrite.
Ceci est une version "bas débit" de notre forum. Pour voir la version complète avec plus d'information, la mise en page et les images, veuillez cliquer ici.
Invision Power Board © 2001-2008 Invision Power Services, Inc.