Analyse und Reparatur von zwei Fehlern in Delphi
Bei der Verwendung von Delphi 7 für die dreistufige Datenbankentwicklung bin ich auf zwei kleine Probleme gestoßen. Durch wiederholte Versuche habe ich schließlich die beiden kleinen Fehler in Delphi 7 herausgefunden und sie (anscheinend gibt es dieselben Fehler in Delphi 6) schriftlich behoben Dieser Artikel teilt die Freude am Erfolg mit allen. Ich bin auch neu in Delphi, daher müssen in dem Artikel viele Dinge falsch sein. Bitte korrigieren Sie mich.
BUG1. Chinesische Zeichen werden beim Übergeben von Parametern abgeschnitten:
So reproduzieren Sie den Fehler:
Im Hintergrund wird SQL Server 2000 verwendet und es gibt eine XsHeTong-Tabelle zum Testen. Sie können diese an Ihre tatsächliche Situation anpassen.
Erstellen Sie zunächst einen Datenserver: Erstellen Sie ein neues Projekt, erstellen Sie ein Remote-Datenmodul, platzieren Sie eine ADOConnection, ein ADODataSet und einen DataSetPRovider darauf und nehmen Sie die entsprechenden Einstellungen vor. Lassen Sie den ComamndText von ADODataSet leer und setzen Sie den poAllowCommandText in seiner Option auf True. Kompilieren und ausführen.
Erstellen Sie erneut ein Client-Programm: Erstellen Sie ein neues Projekt, platzieren Sie eine DCOMConnection im Formular, stellen Sie eine Verbindung zum zuvor erstellten Datenserver her, platzieren Sie ein ClientDataSet, legen Sie hier seine Verbindung zur DCOMConnection fest und setzen Sie seinen ProviderName auf den oben genannten Server DataSetProvider. Platzieren Sie abschließend die DataSource und das DBGrid, nehmen Sie die entsprechenden Einstellungen vor, um die Ergebnisse anzuzeigen, und platzieren Sie dann eine Schaltfläche zum Testen.
Schreiben Sie Code ähnlich dem folgenden in Button's OnClick (hier habe ich die XsHeTong-Tabelle und ihre beiden Felder HTH (char 15), GCMC (varchar 100) verwendet, Sie können ihn entsprechend Ihrer tatsächlichen Testsituation anpassen):
mit ClientDataSet1 tun
beginnen
Schließen;
CommandText := 'In XsHeTong(HTH, GCMC) Werte einfügen(:HTH,:GCMC)';
Params[0].AsString := '12345';
Params[1].AsString := 'Chinesische Zeichen, die abgeschnitten werden';
Ausführen;
Schließen;
CommandText := 'Wähle * aus XsHeTong';
Offen;
Ende;
Führen Sie das Programm aus, klicken Sie auf die Schaltfläche und prüfen Sie, ob der Datensatz eingefügt wurde. Leider ist das Ergebnis nicht korrekt .
Fehleranalyse und -behebung:
Zum Vergleich habe ich versucht, die C/S-Architektur direkt mit ADOConnection, ADOCommand und ADOTable zu testen. Das Ergebnis war korrekt und die chinesischen Zeichen wurden nicht abgeschnitten. Dies zeigt, dass dieser Fehler nur in der dreistufigen Architektur auftritt.
Verwenden Sie SQL Server Profiler, um die zur Ausführung auf SQL Server übermittelten Anweisungen zu untersuchen und die folgenden Unterschiede zwischen der zweistufigen Architektur und der dreistufigen Architektur zu ermitteln:
Zweistufige Architektur:
exec sp_executesql N'In XsHeTong(HTH, GCMC) Werte einfügen(@P1,@P2)', N'@P1 varchar(15),@P2 varchar(100)', '12345', 'Chinesische Zeichen, die abgeschnitten werden '
Dreistufige Architektur:
exec sp_executesql N'Einfügen in XsHeTong(HTH, GCMC) Werte(@P1,@P2)', N'@P1 varchar(5),@P2 varchar(7)', '12345', 'wird abgeschnitten
Offensichtlich wird in der zweischichtigen Architektur die Länge des Parameters entsprechend der tatsächlichen Bibliotheksstruktur übergeben. In der dreischichtigen Architektur wird die Länge des Parameters entsprechend der Zeichenfolgenlänge des tatsächlichen Parameters übergeben Die Zeichenfolgenlänge scheint falsch berechnet zu sein. Ein chinesisches Zeichen wird als zwei Zeichen lang behandelt.
Um die VCL-Bibliothek von Delphi zu debuggen, müssen Sie in den „Compiler-Optionen“ der Projektoptionen „Use Debug DCUs“ auswählen.
Verfolgen Sie zuerst das Client-Programm, dann ClientDataSet1.Execute und durchlaufen Sie dann eine Reihe von Funktionen wie TCustomClientDataSet.Exectue, TCustomeClientDataSet.PackageParams, TCustomClientDataSet.DoExecute usw., bis AppServer.AS_Execute(ProviderName, CommandText, Params, OwnerData); sendet die Anfrage an den Server. Es gibt keine Auffälligkeiten. Es scheint, dass das Problem auf der Serverseite liegt.
Nach der Verfolgung des Servers und wiederholten Versuchen konzentrierte ich mich auf die Funktion TCustomADODataSet.PSSetCommandText. Nach wiederholter und detaillierter Verfolgung wurde das Ziel immer präziser: TCustomADODataSet.PSSetParams, TParameter.Assign, TParameter.SetValue, VarDataSize. Endlich habe ich die Quelle des Fehlers gefunden: die VarDataSize-Funktion. Hier ist ihr Code:
function VarDataSize(const Value: OleVariant): Integer;
beginnen
if VarIsNull(Value) dann
Ergebnis := -1
sonst wenn VarIsArray(Value) dann
Ergebnis := VarArrayHighBound(Value, 1) + 1
sonst wenn TVarData(Value).VType = varOleStr dann
beginnen
Ergebnis := Length(PWideString(@TVarData(Value).VOleStr)^); //Die problematische Zeile
wenn Ergebnis = 0 dann
Ergebnis := -1;
Ende
anders
Ergebnis := SizeOf(OleVariant);
Ende;
In dieser Funktion wird die Länge des tatsächlichen Parameters berechnet. Sie verwendet die Adresse des Werts in Value und verwendet sie als WideString-Zeiger, um die Länge der Zeichenfolge zu ermitteln abgeschnitten werden" ist Die Länge beträgt 7 statt 14.
Sobald das Problem gefunden ist, ist es nicht schwer, es einfach zu lösen
Ergebnis := Length(PWideString(@TVarData(Value).VOleStr)^); //Die problematische Zeile
Wechseln zu
Ergebnis := Length(PAnsiString(@TVarData(Value).VOleStr)^); //Kein Problem
Das ist es.
Dies führt jedoch dazu, dass die Länge verdoppelt wird, wenn die Länge der englischen Zeichenfolge ermittelt wird. Sie können diese Zeile also auch ändern in:
Ergebnis := Länge(Wert);
Auf diese Weise kann die richtige Länge ermittelt werden, unabhängig davon, ob es sich um eine chinesische, eine englische oder eine gemischte chinesisch-englische Zeichenfolge handelt. Das ist eine Frage, die mich immer noch beschäftigt. Warum dreht sich Borland im Kreis, um die Länge des Parameterwerts durch einen Zeiger zu ermitteln? Wenn jemand es weiß, erklären Sie es mir bitte. Vielen Dank!
Einige Freunde haben möglicherweise Fragen: Warum tritt dieses Problem des Abschneidens von Zeichenfolgen nicht auf, wenn dies nicht über eine dreistufige Architektur erfolgt? Die Antwort ist nicht kompliziert. Wenn Befehle direkt über ADOCommand an SQL Server gesendet werden, wird die Parameterlänge anhand der Tabellenstruktur bestimmt. Zunächst wird eine Nachricht an SQL Server gesendet
SET FMTONLY ON wählen Sie HTH,GCMC aus XsHeTong SET FMTONLY OFF
um die Tabellenstruktur zu erhalten. Unter der dreistufigen Architektur verwendet TCustomADODataSet zwar auch intern das TADOCommand-Objekt, um Befehle auszugeben, verwendet diesen Wert jedoch nicht als Parameterlänge nach dem Abrufen der Tabellenstruktur, sondern berechnet die Länge basierend auf den tatsächlichen Parametern neu. Das Ergebnis ist ein Fehler.
BUG2. Problem mit dem Lookup-Feld von ClientDataSet:
So reproduzieren Sie den Fehler:
Erstellen Sie ein neues Projekt und platzieren Sie darin zwei ClientDataSets, nämlich cds1 und cds2. Darunter ist cds1 der Hauptdatensatz. Dieses Suchfeld basiert auf einem Zeichenfeld in cds1. value, um den entsprechenden Wert in cds2 zu finden.
Im Allgemeinen ist es normal, das Programm auszuführen, aber sobald der Wert im Suchfeld von cds1 mit einem einfachen Anführungszeichen „'“ angezeigt wird (Sie können einen Datensatz ändern oder hinzufügen, versuchen Sie es mit der Eingabe eines einfachen Anführungszeichens), tritt sofort ein Fehler auf : Nicht abgeschlossene String-Konstante.
Fehleranalyse und -behebung:
Die Ursache dieses Fehlers ist viel offensichtlicher als der vorherige. Er muss darin liegen, dass die Nebenwirkungen von einfachen Anführungszeichen nicht richtig behandelt werden.
Verfolgen wir auf ähnliche Weise den Quellcode von VCL:
Führen Sie das Programm aus und öffnen Sie bei Auftreten eines Fehlers das Fenster „Aufrufstapel“ (im Menü „Ansicht“ > „Debugfenster“), um die vorherigen Funktionsaufrufe zu überprüfen. Beginnen wir mit der entsprechenden Stelle Der erste mit Lookup verbundene Funktionsaufruf ist TField.CalcLookupValue. Wir setzen einen Haltepunkt in dieser Funktion, führen das Programm erneut aus und führen nach der Unterbrechung ein Einzelschritt-Debugging durch.
TCustomClientDataSet.Lookup->TCustomClientDataSet.LocateRecord
Nach mehreren Funktionsaufrufen oben legen wir schnell das Ziel im LocateRecord-Prozess fest. In diesem Prozess werden entsprechende Filterbedingungen basierend auf den Einstellungen des Suchfelds generiert und dann die entsprechenden Filterbedingungen zum Zieldatensatz hinzugefügt gefunden, und der Fehler liegt in der Generierung von Filterbedingungen. Beispielsweise müssen wir basierend auf dem Wert des Cust-Felds (angenommen 001) in cds1 zu cds2 gehen und den entsprechenden CustName-Feldwert basierend auf dem CustID-Feldwert finden. Die generierte Bedingung sollte [CustID] = '001' sein, aber wenn der Wert von Cust aa'bb ist, wird die generierte Bedingung zu [CustID] = 'aa'bb', was offensichtlich zu einer unvollendeten String-Konstante führt.
Wenn wir das Problem lösen, dass einfache Anführungszeichen in einfachen Anführungszeichen erscheinen, müssen wir normalerweise nur zwei Anführungszeichen in die Anführungszeichen schreiben. Dasselbe gilt hier, solange die generierte Bedingung zu [CustID] = 'aa''bb' wird wird kein Fehler sein. Sie können den Quellcode also wie folgt ändern:
Suchen Sie den folgenden Code in der LocateRecord-Prozedur:
ftString, ftFixedChar, ftWideString, ftGUID
if (i = Fields.Count - 1) und (loPartialKey in Optionen) dann
ValStr := Format('''%s*''',[VarToStr(Value)]) else
ValStr := Format('''%s''',[VarToStr(Value)]);
Ändern zu:
ftString, ftFixedChar, ftWideString, ftGUID:
if (i = Fields.Count - 1) und (loPartialKey in Optionen) dann
ValStr := Format('''%s*''',[ StringReplace(VarToStr(Value),'''','''''',[rfReplaceAll])])
anders
ValStr := Format('''%s''',[ StringReplace(VarToStr(Value),'''','''''',[rfReplaceAll])]);
Das heißt, wenn Sie die Filterbedingungszeichenfolge generieren, ändern Sie alle einfachen Anführungszeichen im Filterwert der Bedingung von eins in zwei.
Um die Richtigkeit dieser Änderung sicherzustellen, habe ich den entsprechenden LocateRecord-Prozess in TCustomADODataSet überprüft (bei Verwendung des Suchfelds in TADODataSet treten keine Fehler aufgrund von einfachen Anführungszeichen auf, nur bei Verwendung von TCustomClientDataSet). Die Verarbeitungsmethode ist dieselbe wie TCustomClientDataSet ist etwas anders. Es erstellt Filterbedingungen über die Funktion GetFilterStr, aber in GetFilterStr wird das Problem von einfachen Anführungszeichen korrekt behandelt. Wenn man es also so betrachtet, ist das Problem, dass einfache Anführungszeichen in LocateRecord von TCustomClientDataSet nicht korrekt behandelt werden, tatsächlich ein kleines Versäumnis von Borland.