Delphi の 2 つのバグの分析と修復
Delphi 7 を 3 層データベース開発に使用しているときに、2 つの小さな問題に遭遇しました。試行を繰り返すうちに、最終的に Delphi 7 の 2 つの小さな BUG を見つけて修正しました(Delphi 6 にも同じ BUG があるようです)。この記事では成功の喜びを皆さんと共有します。私も Delphi を初めて使用します。この記事には間違っている点がたくさんあると思います。友人に修正してもらいたいと思います。
バグ 1: パラメータを渡すときに中国語の文字が切り捨てられます。
バグを再現する方法:
SQL Server 2000 はバックグラウンドで使用され、テスト用の XsHeTong テーブルがあり、実際の状況に応じて調整できます。
まずデータ サーバーを作成します。新しいプロジェクトを作成し、リモート データ モジュールを作成し、その上に ADOConnection、ADODataSet、および DataSetPROvider を 1 つ配置し、対応する設定を行います。ADODataSet の ComamndText を空白のままにして、そのオプションの poAllowCommandText を True に設定します。コンパイルして実行します。
クライアント プログラムを再度作成します。新しいプロジェクトを作成し、フォームに DCOMConnection を配置し、前に作成したデータ サーバーに接続し、ClientDataSet を配置し、ここで DCOMConnection への接続を設定し、その ProviderName を上記のサーバーに設定します。データセットプロバイダー。最後に、DataSource と DBGrid を配置し、結果を表示するための対応する設定を行ってから、テスト用の Button を配置します。
Button の OnClick に次のようなコードを記述します (ここでは XsHeTong テーブルとその 2 つのフィールド HTH (char 15)、GCMC (varchar 100) を使用しました。実際のテスト状況に応じて調整できます)。
ClientDataSet1 を使用して行う
始める
近い;
CommandText := 'XsHeTong(HTH, GCMC) 値に挿入 (:HTH,:GCMC)';
Params[0].AsString := '12345';
Params[1].AsString := '切り捨てられる漢字';
実行する;
近い;
CommandText := 'XsHeTong から * を選択';
開ける;
終わり;
プログラムを実行し、ボタンをクリックすると、レコードが挿入されたことがわかります。残念ながら、「切り詰められる漢字」は「切り詰められます」と表示されますが、漢字のない「12345」は正しく挿入されます。 。
バグの分析と修復:
比較のために、ADOConnection、ADOCommand、ADOTable を直接使用して C/S アーキテクチャをテストしてみました。結果は正しく、中国語の文字は切れませんでした。これは、このバグが 3 層アーキテクチャでのみ発生することを示しています。
SQL Server プロファイラーを使用して、実行のために SQL Server に送信されたステートメントを調査し、2 層アーキテクチャと 3 層アーキテクチャの間の次の違いを見つけます。
2 層アーキテクチャ:
exec sp_executesql N'XsHeTong(HTH, GCMC) に挿入 値(@P1,@P2)', N'@P1 varchar(15),@P2 varchar(100)', '12345', '切り捨てられる漢字'
3 層アーキテクチャ:
exec sp_executesql N'XsHeTong(HTH, GCMC) に挿入値 (@P1,@P2)', N'@P1 varchar(5),@P2 varchar(7)', '12345', ' 切り捨てられます
明らかに、2 層アーキテクチャでは、パラメータの長さは実際のライブラリ構造に従って渡されます。3 層アーキテクチャでは、パラメータの長さは実際のパラメータの文字列長に従って渡されます。文字列の長さが間違って計算されているようです。漢字は 2 文字として扱われます。
Delphi の VCL ライブラリをデバッグするには、プロジェクト オプションの「コンパイラ オプション」で「デバッグ DCU を使用する」を選択する必要があります。
最初にクライアント プログラムを追跡し、次に ClientDataSet1.Execute を追跡し、AppServer.AS_Execute(ProviderName, CommandText, Params, OwnerData) まで、TCustomClientDataSet.Exectue、TCustomeClientDataSet.PackageParams、TCustomClientDataSet.DoExecute などの一連の関数を実行します。サーバーにリクエストを送信します。 サーバー側に問題があるようです。
サーバーを追跡し、試行を繰り返した後、TCustomADODataSet.PSSetCommandText 関数に焦点を当てました。詳細な追跡を繰り返した結果、ターゲットはますます正確になりました。TCustomADODataSet.PSSetParams、TParameter.Assign、TParameter.SetValue、VarDataSize。ついにバグの原因である VarDataSize 関数を見つけました。そのコードは次のとおりです。
関数 VarDataSize(const 値: OleVariant): 整数;
始める
VarIsNull(値)の場合、
結果 := -1
それ以外の場合、VarIsArray(Value)
結果 := VarArrayHighBound(値, 1) + 1
それ以外の場合、TVarData(Value).VType = varOleStr の場合
始める
Result := Length(PwideString(@TVarData(Value).VOleStr)^); //問題のある行
結果 = 0 の場合
結果 := -1;
終わり
それ以外
結果 := SizeOf(OleVariant);
終わり;
この関数では、実際のパラメータの長さが計算されます。この関数は、Value の値のアドレスを取得し、それを WideString ポインタとして使用して、文字列の長さを調べます。 be truncated」の場合、長さは 14 ではなく 7 になります。
問題が見つかったら、それを解決することは難しくありません。
Result := Length(PwideString(@TVarData(Value).VOleStr)^); //問題のある行
に変更します
結果 := Length(PAnsiString(@TVarData(Value).VOleStr)^); //問題ありません
それでおしまい。
ただし、これにより英語の文字列の長さを求めるときに長さが 2 倍になるため、この行を次のように変更することもできます。
結果 := 長さ(値);
このようにして、中国語、英語、または中国語と英語の混合文字列であっても、正しい長さを取得できます。これは私にとっていまだに不思議な疑問です。なぜボーランドはポインタを介してパラメータ値の長さを見つけるために円を描くのでしょうか?知っている人がいたら教えてください、よろしくお願いします!
友人の中には、3 層アーキテクチャを介していないのに、なぜこの文字列の切り捨ての問題が発生しないのかと疑問に思う人もいるかもしれません。答えは複雑ではありません。ADOCommand を通じて SQL Server にコマンドを直接送信する場合、テーブル構造に従ってパラメーターの長さが決定されます。最初にメッセージを SQL Server に送信します。
FMTONLY を ON に設定 XsHeTong から HTH、GCMC を選択 FMTONLY を OFF に設定
テーブル構造を取得します。 3 層アーキテクチャでは、TCustomADODataSet も内部で TADOCommand オブジェクトを使用してコマンドを発行しますが、テーブル構造を取得した後にこの値をパラメーターの長さとして使用せず、実際のパラメーターに基づいて長さを再計算します。エラーです。
BUG2. ClientDataSet の Lookup フィールドの問題:
バグを再現する方法:
新しいプロジェクトを作成し、その上に cds1 と cds2 という 2 つの ClientDataSet を配置します。その中で、cds1 がメインのデータ セットです。このルックアップ フィールドは文字フィールドに基づいています。 cds1 の値を入力して、cds2 内の対応する値を見つけます。
通常、プログラムを実行するのは正常ですが、cds1 の Lookup フィールドの値に一重引用符「'」が表示されると (レコードを変更または追加できます。一重引用符を入力してみてください)、すぐにエラーが発生します。 : 終了していない文字列定数。
バグの分析と修復:
このバグの原因は、前のバグよりもはるかに明白であり、一重引用符の副作用を適切に処理できないことが原因であると考えられます。
同様に、VCL のソース コードを追跡してみましょう。
プログラムを実行し、エラーが発生した場合は、[呼び出しスタック] ウィンドウ ([表示] -> [デバッグ ウィンドウ]) メニューを開いて、以前の呼び出しのいくつかは明らかであり、問題がないことを確認します。最初の Lookup に関連する関数呼び出しは TField.CalcLookupValue です。この関数にブレークポイントを設定し、プログラムを再実行し、中断後にシングル ステップ デバッグを実行します。
TCustomClientDataSet.Lookup->TCustomClientDataSet.LocateRecord
上記の関数呼び出しをいくつか行った後、LocateRecord プロセスでターゲットをすぐに設定します。このプロセスでは、Lookup フィールドの設定に基づいて対応するフィルター条件が生成され、対応するフィルター条件がターゲット データ セットに追加されます。が見つかりました。問題はフィルター条件の生成にあります。たとえば、cds1 の Cust フィールドの値 (001 と想定) に基づいて cds2 に移動し、CustID フィールドの値に基づいて対応する CustName フィールドの値を見つける必要があります。生成される条件は [CustID] = '001' である必要がありますが、Cust の値が aa'bb の場合、生成される条件は [CustID] = 'aa'bb' となり、明らかに未完成の String 定数になります。
通常、単一引用符内に単一引用符が現れる問題を解決する場合、生成される条件が [CustID] = 'aa''bb' になる限り、同じことが当てはまります。エラーは発生しません。したがって、ソースコードを次のように変更できます。
LocateRecord プロシージャで次のコードを見つけます。
ftString、ftFixedChar、ftwideString、ftGUID
if (i = Fields.Count - 1) および (オプションの loPartialKey) then
ValStr := Format('''%s*''',[VarToStr(Value)]) else
ValStr := Format('''%s''',[VarToStr(Value)]);
次のように変更します。
ftString、ftFixedChar、ftwideString、ftGUID:
if (i = Fields.Count - 1) および (オプションの loPartialKey) then
ValStr := Format('''%s*''',[ StringReplace(VarToStr(Value),'''','''''',[rfReplaceAll])])
それ以外
ValStr := Format('''%s''',[ StringReplace(VarToStr(Value),'''','''''',[rfReplaceAll])]);
つまり、フィルター条件文字列を生成するときに、条件のフィルター値内のすべての一重引用符を 1 から 2 に変更します。
この変更が正しいことを確認するために、TCustomADODataSet の対応する LocateRecord プロセスを確認しました (TADODataSet の Lookup フィールドを使用する場合、一重引用符によるエラーは発生しません。TCustomClientDataSet を使用する場合のみ)。その処理方法は次のとおりです。 TCustomClientDataSet は若干異なりますが、GetFilterStr 関数を通じてフィルター条件を構築しますが、GetFilterStr では一重引用符の問題が正しく処理されます。このように見ると、TCustomClientDataSet の LocateRecord で一重引用符が正しく処理されない問題は、確かに Borland による軽微な省略です。