Analyse et réparation de deux BUGs dans Delphi
Lors de l'utilisation de Delphi 7 pour le développement de bases de données à trois niveaux, j'ai rencontré deux petits problèmes. Grâce à des essais répétés, j'ai finalement découvert les deux petits BUG dans Delphi 7 et les ai corrigés (il semble qu'il y ait les mêmes BUG dans Delphi 6), en écrivant. Cet article partage la joie du succès avec tout le monde. Je suis également nouveau sur Delphi, donc il doit y avoir beaucoup de choses qui ne vont pas dans l'article, corrigez-moi.
BUG1. Les caractères chinois sont tronqués lors du passage des paramètres :
Comment reproduire un BUG :
SQL Server 2000 est utilisé en arrière-plan et il existe une table XsHeTong pour les tests. Vous pouvez l'ajuster en fonction de votre situation réelle.
Créez d'abord un serveur de données : créez un nouveau projet, créez un module de données distant, placez-y un ADOConnection, un ADODataSet et un DataSetPRovider, puis définissez les paramètres correspondants. Laissez le ComamndText d'ADODataSet vide et définissez le poAllowCommandText dans son option sur True. Compilez et exécutez.
Créez à nouveau un programme client : créez un nouveau projet, placez un DCOMConnection sur le formulaire, connectez-vous au serveur de données créé précédemment, placez un ClientDataSet, définissez ici sa connexion au DCOMConnection et définissez son ProviderName sur le serveur ci-dessus. Le nom du Fournisseur de jeux de données. Enfin, placez le DataSource et le DBGrid et effectuez les paramètres correspondants pour afficher les résultats, puis placez un bouton pour les tests.
Écrivez du code similaire à celui-ci dans Button's OnClick (ici j'ai utilisé la table XsHeTong et ses deux champs HTH (char 15), GCMC (varchar 100), vous pouvez l'ajuster en fonction de votre situation de test réelle :
avec ClientDataSet1 faire
commencer
Fermer;
CommandText := 'Insérer dans les valeurs XsHeTong(HTH, GCMC) (:HTH,:GCMC)';
Params[0].AsString := '12345';
Params[1].AsString := 'Caractères chinois qui seront tronqués';
Exécuter;
Fermer;
CommandText := 'Sélectionnez * dans XsHeTong';
Ouvrir;
fin;
Exécutez le programme, cliquez sur le bouton et vérifiez que l'enregistrement a été inséré. Malheureusement, le résultat n'est pas correct. "Les caractères chinois qui seront tronqués" deviennent "seront tronqués", mais "12345" sans caractères chinois est inséré correctement. .
Analyse et réparation de BUG :
A titre de comparaison, j'ai essayé d'utiliser directement un ADOConnection, un ADOCommand et un ADOTable pour tester l'architecture C/S. Le résultat était correct et les caractères chinois ne seraient pas coupés. Cela montre que ce BUG n'apparaît que sur l'architecture à trois niveaux.
Utilisez SQL Server Profiler pour examiner les instructions soumises pour s'exécuter sur SQL Server et rechercher les différences suivantes entre l'architecture à deux niveaux et l'architecture à trois niveaux :
Architecture à deux niveaux :
exec sp_executesql N'Insérer dans les valeurs XsHeTong(HTH, GCMC) (@P1,@P2)', N'@P1 varchar(15),@P2 varchar(100)', '12345', 'Caractères chinois qui seront tronqués '
Architecture à trois niveaux :
exec sp_executesql N'Insérer dans les valeurs XsHeTong(HTH, GCMC) (@P1,@P2)', N'@P1 varchar(5),@P2 varchar(7)', '12345', 'sera tronqué
Évidemment, dans l'architecture à deux couches, la longueur du paramètre est transmise en fonction de la structure réelle de la bibliothèque. Dans l'architecture à trois couches, la longueur du paramètre est transmise en fonction de la longueur de chaîne du paramètre réel et de la longueur réelle. la longueur de la chaîne semble avoir été mal calculée. Un caractère chinois est traité comme une longueur de deux caractères.
Il n'y a pas d'autre choix que de tracer et déboguer. Afin de déboguer la bibliothèque VCL de Delphi, vous devez sélectionner "Utiliser les DCU de débogage" dans les "Options du compilateur" des options du projet.
Suivez d'abord le programme client, puis ClientDataSet1.Execute, puis parcourez une série de fonctions telles que TCustomClientDataSet.Exectue, TCustomeClientDataSet.PackageParams, TCustomClientDataSet.DoExecute, etc., jusqu'à ce que AppServer.AS_Execute(ProviderName, CommandText, Params, OwnerData); soumet la requête au serveur. Il n'y a aucune anomalie. Il semble que le problème réside du côté du serveur.
Après avoir suivi le serveur et des essais répétés, je me suis concentré sur la fonction TCustomADODataSet.PSSetCommandText. Après un suivi répété et détaillé, la cible est devenue de plus en plus précise : TCustomADODataSet.PSSetParams, TParameter.Assign, TParameter.SetValue, VarDataSize. J'ai enfin trouvé la source du BUG : la fonction VarDataSize. Voici son code :
fonction VarDataSize (valeur const : OleVariant) : entier ;
commencer
si VarIsNull (Valeur) alors
Résultat := -1
sinon si VarIsArray (Valeur) alors
Résultat := VarArrayHighBound (Valeur, 1) + 1
sinon si TVarData(Value).VType = varOleStr alors
commencer
Résultat := Longueur(PWideString(@TVarData(Value).VOleStr)^); //La ligne problématique
si Résultat = 0 alors
Résultat := -1;
fin
autre
Résultat := SizeOf(OleVariant);
fin;
C'est dans cette fonction que la longueur du paramètre réel est calculée. Elle prend l'adresse de la valeur dans Value et l'utilise comme pointeur WideString pour trouver la longueur de la chaîne. En conséquence, la chaîne "caractères chinois qui sera. être tronqué" est La longueur devient 7 au lieu de 14.
Une fois le problème identifié, il n’est pas difficile de le résoudre simplement.
Résultat := Longueur(PWideString(@TVarData(Value).VOleStr)^); //La ligne problématique
Changer pour
Résultat := Longueur(PAnsiString(@TVarData(Value).VOleStr)^); //Pas de problème
C'est ça.
Mais cela entraînera le doublement de la longueur lors de la recherche de la longueur de la chaîne anglaise, vous pouvez donc également modifier cette ligne comme :
Résultat := Longueur(Valeur);
De cette façon, la longueur correcte peut être obtenue, qu'il s'agisse d'une chaîne chinoise, anglaise ou mixte chinois-anglais. C'est une question qui me laisse encore perplexe. Pourquoi Borland fait-il un tour en cercle pour trouver la longueur de la valeur du paramètre à l'aide d'un pointeur ? Si quelqu'un le sait, expliquez-moi, merci beaucoup !
Certains amis peuvent se poser des questions : pourquoi ce problème de troncature de chaîne ne se produit-il pas alors qu'il n'est pas effectué via une architecture à trois niveaux ? La réponse n'est pas compliquée. Lors de l'envoi de commandes à SQL Server directement via ADOCommand, il détermine la longueur du paramètre en fonction de la structure de la table. Il enverra d'abord un message à SQL Server
SET FMTONLY ON sélectionnez HTH, GCMC dans XsHeTong SET FMTONLY OFF
pour obtenir la structure de la table. Dans l'architecture à trois niveaux, bien que TCustomADODataSet utilise également l'objet TADOCommand en interne pour émettre des commandes, il n'utilise pas cette valeur comme longueur de paramètre après avoir obtenu la structure de la table, mais recalcule la longueur en fonction des paramètres réels. une erreur.
BUG2. Problème avec le champ Lookup de ClientDataSet :
Comment reproduire un BUG :
Créez un nouveau projet et placez-y deux ClientDataSets, à savoir cds1 et cds2. Parmi elles, cds1 est l'ensemble de données principal. Ajoutez-y un nouveau champ de recherche. dans cds1 pour trouver la valeur correspondante dans cds2.
D'une manière générale, il est normal d'exécuter le programme, mais une fois que la valeur dans le champ Recherche de cds1 apparaît avec un guillemet simple "'" (vous pouvez modifier ou ajouter un enregistrement, essayez de saisir un guillemet simple), une erreur se produira immédiatement : Constante de chaîne interminée.
Analyse et réparation de BUG :
La cause de ce BUG est beaucoup plus évidente que la précédente. Elle doit être causée par une mauvaise gestion des effets secondaires des guillemets simples.
De même, suivons le code source de la VCL :
Exécutez le programme et lorsqu'une erreur se produit, ouvrez la fenêtre Call Stack (dans le menu View->Debug Windows) pour vérifier les appels de fonction. Certains des appels précédents sont évidents et il n'y a pas de problème. à Lookup pour vérifier la cause. Le premier L'appel de fonction lié à Lookup est TField.CalcLookupValue Nous définissons un point d'arrêt dans cette fonction, réexécutons le programme et effectuons un débogage en une seule étape après l'interruption.
TCustomClientDataSet.Lookup->TCustomClientDataSet.LocateRecord
Après plusieurs appels de fonction ci-dessus, nous définissons rapidement la cible dans le processus LocateRecord. Dans ce processus, il génère les conditions de filtre correspondantes en fonction des paramètres du champ Recherche, puis ajoute les conditions de filtre correspondantes à l'ensemble de données cible. trouvé, et la faute réside dans la génération des conditions de filtre. Par exemple, nous devons accéder à cds2 en fonction de la valeur du champ Cust (supposé être 001) dans cds1 et trouver la valeur du champ CustName correspondante en fonction de la valeur du champ CustID. La condition générée doit être [CustID] = '001', mais si la valeur de Cust est aa'bb, la condition générée deviendra [CustID] = 'aa'bb', ce qui conduit évidemment à une constante de chaîne inachevée.
Habituellement, lorsque nous résolvons le problème des guillemets simples apparaissant entre guillemets simples, nous n'avons besoin d'écrire que deux guillemets dans les guillemets. La même chose est vraie ici tant que la condition générée devient [CustID] = 'aa''bb'. il n'y aura pas d'erreur. Vous pouvez donc modifier le code source comme ceci :
Recherchez le code suivant dans la procédure LocateRecord :
ftString, ftFixedChar, ftWideString, ftGUID
si (i = Fields.Count - 1) et (loPartialKey dans Options) alors
ValStr := Format('''%s*''',[VarToStr(Valeur)]) autre
ValStr := Format('''%s''',[VarToStr(Valeur)]);
Remplacer par :
ftString, ftFixedChar, ftWideString, ftGUID :
si (i = Fields.Count - 1) et (loPartialKey dans Options) alors
ValStr := Format('''%s*''',[ StringReplace(VarToStr(Value),'''','''''',[rfReplaceAll])])
autre
ValStr := Format('''%s''',[ StringReplace(VarToStr(Value),'''','''''',[rfReplaceAll])]);
Autrement dit, lors de la génération de la chaîne de condition de filtre, modifiez tous les guillemets simples de la valeur de filtre de la condition de un à deux.
Afin de garantir l'exactitude de cette modification, j'ai vérifié le processus LocateRecord correspondant dans TCustomADODataSet (lors de l'utilisation du champ Lookup dans TADODataSet, il n'y aura pas d'erreurs dues aux guillemets simples, uniquement lors de l'utilisation de TCustomClientDataSet), sa méthode de traitement est la même que TCustomClientDataSet est légèrement différent. Il construit des conditions de filtre via la fonction GetFilterStr, mais dans GetFilterStr, il gère correctement le problème des guillemets simples. Vu sous cet angle, le problème de la mauvaise gestion des guillemets simples dans LocateRecord de TCustomClientDataSet est en effet une omission mineure de la part de Borland.