熱點推薦:
您现在的位置: 電腦知識網 >> 編程 >> Delphi編程 >> 正文

Delphi中兩個BUG的分析與修復

2022-06-13   來源: Delphi編程 
在使用Delphi 進行三層數據庫開發時遇到了兩個小問題通過反復試驗終於找出了Delphi 中的兩個小BUG並進行了修復(好像Delphi 中也有相同的BUG)撰寫此文與大家一起分享成功的喜悅我也是初學Delphi文中一定存在不少說的不對的地方還請各位朋友多多指正
  
  BUG傳參時中文被截斷的問題
  
  BUG再現的方法
  後台用SQL Server 裡面有一個XsHeTong表用於試驗您可以根據您的實際情況進行調整
  
  先創建一個數據服務器新建項目創建一個遠程數據模塊上面放置ADOConnectionADODataSetDataSetProvider各一並做好相應設置其中ADODataSet的ComamndText留空並把它的Option中的poAllowCommandText設置為True編譯運行
  
  再創建客戶端程序新建項目在窗體上放置DCOMConnection連上前面上創建的數據服務器再放置一個ClientDataSet把它的連接設成這裡的DCOMConnection並設置它的ProviderName為上面的服務器上的DataSetProvider的名字最後放置DataSource和DBGrid各一並作相應設置用於查看結果再放置一Button用於測試
  
  在Button的OnClick中寫下類似於下面的代碼(這裡我用了XsHeTong的表和它的兩個字段HTH(char GCMC(varchar 您可以根據你的實際測試情況進行調整)
  
  with ClientDataSet do
  begin
  Close;
  CommandText := Insert Into XsHeTong(HTH GCMC) values(:HTH:GCMC);
  Params[]AsString := ;
  Params[]AsString := 會截斷的中文字;
  Execute;
  Close;
  CommandText := Select * from XsHeTong;
  Open;
  end;
  
  運行程序點擊按鈕看到記錄被插入了可惜結果並不正確會截斷的中文字變成了會截斷但沒有中文的倒是正確的插入了
  
  BUG分析與修復
  為了對照起見我試著直接用一個ADOConnection和ADOCommandADOTable進行C/S構架測試結果是正確的中文字不會被切斷這說明了此BUG只在三層構架上出現
  
  用SQL Server事件探查器探查提交到SQL Server上運行的語句發現兩層構架與三層構架的情況有以下不同
  
  兩層構架
  exec sp_executesql NInsert into XsHeTong(HTH GCMC) values(@P@P) N@P varchar()@P varchar() 會截斷的中文字
  
  三層構架
  exec sp_executesql NInsert into XsHeTong(HTH GCMC) values(@P@P) N@P varchar()@P varchar() 會截斷
  
  顯然兩層構架時參數的長度是按實際庫結構傳的三層構架時參數長度是按實際參數的字符串長度傳的而實際字符串長度又似乎是算錯了沒有把一個中文當兩個字符長度處理
  
  沒有辦法只好進行跟蹤調試為了調試Delphi的VCL庫需要在工程選項的Compiler Options中選上Use Debug DCUs
  
  先跟蹤客戶端程序ClientDataSetExecute後先後經歷了TCustomClientDataSetExectueTCustomeClientDataSetPackageParamsTCustomClientDataSetDoExecute等一系列函數一直到AppServerAS_Execute(ProviderName CommandText Params OwnerData); 把請求提交到服務器均沒有什麼異常情況看來問題出在服務器端
  
  對服務器進行跟蹤反復試驗後我把重點落在了TCustomADODataSetPSSetCommandText函數身上經過反復細致的跟蹤目標越來越精確TCustomADODataSetPSSetParamsTParameterAssignTParameterSetValueVarDataSize終於找到了BUG的源頭VarDataSize函數下面是它的代碼
  
  function VarDataSize(const Value: OleVariant): Integer;
  begin
  if VarIsNull(Value) then
  Result :=
  else if VarIsArray(Value) then
  Result := VarArrayHighBound(Value ) +
  else if TVarData(Value)VType = varOleStr then
  begin
  Result := Length(PWideString(@TVarData(Value)VOleStr)^); //出問題的行
  if Result = then
  Result := ;
  end
  else
  Result := SizeOf(OleVariant);
  end;
  
  就是在這個函數中計算實參的長度的它把Value中的值取出地址並把它作為一個WideString的指針去求字符串長度結果就導致了會截斷的中文字這個字符串的長度變成了而不是
  
  問題找到了解決起來也就不困難了只要簡單的把
  Result := Length(PWideString(@TVarData(Value)VOleStr)^); //出問題的行
  改成
  Result := Length(PAnsiString(@TVarData(Value)VOleStr)^); //沒問題了
  就可以了
  
  但是這樣就會導致求英文字符串的長度時長度被加倍了所以也可以把這一行改成
  Result := Length(Value);
  
  這樣不管是中文還是英文還是中英混合的字符串就都可求得正確的長度了這就我至今仍百思不解的問題為什麼Borland要繞個圈子通過指針去求參數值的長度呢?哪位朋友知道的話還請給我解釋一下非常感謝!
  
  有些朋友可能會有疑問為什麼在不通過三層構架來做的時候不產生這個字符串被截斷的問題呢?答案並不復雜在直接通過ADOCommand來向SQL Server發送命令時它是按表結構來決定參數長度的它會先向SQL Server發一條
  
  SET FMTONLY ON select HTHGCMC from XsHeTong SET FMTONLY OFF
  
  來獲取表結構而在三層構架下TCustomADODataSet內部雖然也是用TADOCommand對象來發命令但它卻在取得表結構的後並不用這個值來作為傳參長度而是重新去按實際參數來計算長度結果就導致了錯誤
  
  BUGClientDataSet的Lookup字段的問題
  
  BUG再現的方法
  新建工程在上面放置兩個ClientDataSet分別為cds和cds它的數據來源任意其中cds為主數據集在裡面增加一個新的Lookup字段這個Lookup字段根據cds中的一個字符型的字段值到cds中找出對應值來
  
  運行程序一般來說是正常的但是一旦cds的被Lookup字段中的值出現了一個單引號(您可以修改或新增一條記錄輸入單引號試試)立即會導致出錯 Unterminated string constant(未結束的字符串常量)
  
  BUG分析與修復
  這個BUG的產生原因要比上一個明顯得多一定是沒有正確處理單引號帶來的副作用引起的
  
  同樣的我們來跟蹤VCL的源碼
  
  運行程序出錯時打開Call Stack窗口(在View>Debug Windows)菜單中查看函數調用情況前面的一些調用是顯而易見的沒有問題我們從跟Lookup有關的地方開始查原因第一個與Lookup有關的函數調用是TFieldCalcLookupValue我們在這個函數中設置斷點重新運行程序中斷下來後進行單步調試
  
  TCustomClientDataSetLookup>TCustomClientDataSetLocateRecord
  
  經過上面的幾次函數調用很快的我們就把目標定在了LocateRecord過程中在這個過程中它根據Lookup字段的設置情況生成相應的過濾條件然後到目標數據集中把對應的值找到錯就錯在過濾條件的生成上了比如我們要按cds中Cust字段(假設是)的值到cds中按CustID字段值找到對應的CustName字段值那生成的條件就應該是[CustID] = 但如果Cust的值是aabb按生成的條件就會變成[CustID] = aabb顯然導致了一個未結束的字符串常量
  
  通常我們解決單引號中又出現單引號的情況只需把引號中的引號寫兩就行了這裡也是一樣只要讓生成的條件變成[CustID] = aabb就不會出錯了所以可以這樣修改源代碼
  
  在LocateRecord過程中找到下面的代碼
  
  ftString ftFixedChar ftWideString ftGUID
  if (i = FieldsCount ) and (loPartialKey in Options) then
  ValStr := Format(%s*[VarToStr(Value)]) else
  ValStr := Format(%s[VarToStr(Value)]);
  
  改成
  
  ftString ftFixedChar ftWideString ftGUID:
  if (i = FieldsCount ) and (loPartialKey in Options) then
  ValStr := Format(%s*[ StringReplace(VarToStr(Value)[rfReplaceAll])])
  else
  ValStr := Format(%s[ StringReplace(VarToStr(Value)[rfReplaceAll])]);
  
  也就是在生成過濾條件字符串時把條件的過濾值中的單引號全部一個變兩
  
  為了確保這樣修改的正確性我查看了TCustomADODataSet中的對應的LocateRecord過程(在用TADODataSet中的Lookup字段時不會因單引號出錯只在用TCustomClientDataSet時有這樣的情況)它的處理方法與TCustomClientDataSet稍有不同它是通過GetFilterStr函數來構造過濾條件的但在GetFilterStr中它正確處理了單引號的問題所以這樣來看沒有在TCustomClientDataSet的LocateRecord中正確處理單引號的問題確實是Borland一個不大不小的疏漏
From:http://tw.wingwit.com/Article/program/Delphi/201311/8438.html
    推薦文章
    Copyright © 2005-2022 電腦知識網 Computer Knowledge   All rights reserved.