การวิเคราะห์และซ่อมแซม BUG สองตัวใน Delphi
เมื่อใช้ Delphi 7 สำหรับการพัฒนาฐานข้อมูลสามชั้น ฉันพบปัญหาเล็กๆ น้อยๆ สองปัญหาจากการทดลองซ้ำๆ ในที่สุดฉันก็พบ BUG เล็กๆ สองตัวใน Delphi 7 และทำการแก้ไข (ดูเหมือนว่ามี BUG เดียวกันใน Delphi 6) บทความนี้แบ่งปันความสุขแห่งความสำเร็จกับทุกคน ฉันยังใหม่กับ Delphi ต้องมีหลายอย่างผิดปกติในบทความนี้ฉันอยากจะขอให้เพื่อนแก้ไขฉัน
BUG1 อักขระภาษาจีนถูกตัดทอนเมื่อส่งพารามิเตอร์:
วิธีการสร้าง BUG:
SQL Server 2000 ถูกใช้ในพื้นหลัง และมีตาราง XsHeTong สำหรับการทดสอบ คุณสามารถปรับเปลี่ยนได้ตามสถานการณ์จริงของคุณ
ขั้นแรกให้สร้างเซิร์ฟเวอร์ข้อมูล: สร้างโปรเจ็กต์ใหม่ สร้างโมดูลข้อมูลระยะไกล วาง ADOConnection, ADODataSet และ DataSetPRovider หนึ่งอันไว้บนนั้น และทำการตั้งค่าที่เกี่ยวข้อง ปล่อย ComamndText ของ ADODataSet ว่างไว้ และตั้งค่า poAllowCommandText ใน Option เป็น True คอมไพล์และรัน
สร้างโปรแกรมไคลเอนต์อีกครั้ง: สร้างโครงการใหม่ วาง DCOMConnection บนแบบฟอร์ม เชื่อมต่อกับเซิร์ฟเวอร์ข้อมูลที่สร้างขึ้นก่อนหน้านี้ วาง ClientDataSet ตั้งค่าการเชื่อมต่อกับ DCOMConnection ที่นี่ และตั้งชื่อ ProviderName ไปยังเซิร์ฟเวอร์ด้านบน ชื่อของ DataSetProvider. สุดท้าย วางแหล่งข้อมูลและ DBGrid และทำการตั้งค่าที่เกี่ยวข้องเพื่อดูผลลัพธ์ จากนั้นจึงวางปุ่มสำหรับการทดสอบ
เขียนโค้ดที่คล้ายกับโค้ดต่อไปนี้ใน OnClick ของปุ่ม (ที่นี่ฉันใช้ตาราง XsHeTong และสองฟิลด์ HTH (ถ่าน 15), GCMC (varchar 100) คุณสามารถปรับได้ตามสถานการณ์การทดสอบจริงของคุณ):
ด้วย ClientDataSet1 ทำ
เริ่ม
ปิด;
CommandText := 'แทรกลงในค่า XsHeTong(HTH, GCMC) (:HTH,:GCMC)';
พารามิเตอร์[0].AsString := '12345';
Params[1].AsString := 'ตัวอักษรจีนที่จะถูกตัดทอน';
ดำเนินการ;
ปิด;
CommandText := 'เลือก * จาก XsHeTong';
เปิด;
จบ;
เรียกใช้โปรแกรม คลิกปุ่ม และดูว่ามีการแทรกบันทึกแล้ว น่าเสียดาย ผลลัพธ์ที่ไม่ถูกต้อง "อักขระจีนที่จะถูกตัดทอน" กลายเป็น "จะถูกตัดทอน" แต่ "12345" โดยไม่มีอักขระจีนถูกแทรกอย่างถูกต้อง .
การวิเคราะห์และซ่อมแซม BUG:
สำหรับการเปรียบเทียบ ฉันพยายามใช้ ADOConnection, ADOCommand และ ADOTable โดยตรงเพื่อทดสอบสถาปัตยกรรม C/S ผลลัพธ์ถูกต้องและตัวอักษรจีนจะไม่ถูกตัดออก นี่แสดงให้เห็นว่า BUG นี้ปรากฏบนสถาปัตยกรรมสามระดับเท่านั้น
ใช้ SQL Server Profiler เพื่อตรวจสอบคำสั่งที่ส่งไปยัง SQL Server เพื่อดำเนินการ และค้นหาความแตกต่างต่อไปนี้ระหว่างสถาปัตยกรรมสองระดับและสถาปัตยกรรมสามระดับ:
สถาปัตยกรรมสองชั้น:
exec sp_executesql N'แทรกลงในค่า XsHeTong(HTH, GCMC)(@P1,@P2)', N'@P1 varchar(15),@P2 varchar(100)', '12345', 'ตัวอักษรจีนที่จะถูกตัดทอน '
สถาปัตยกรรมสามชั้น:
exec sp_executesql N'แทรกลงในค่า XsHeTong(HTH, GCMC)(@P1,@P2)', N'@P1 varchar(5),@P2 varchar(7)', '12345', 'จะถูกตัดทอน
แน่นอนว่าในสถาปัตยกรรมสองชั้น ความยาวของพารามิเตอร์จะถูกส่งผ่านตามโครงสร้างไลบรารีจริง ในสถาปัตยกรรมสามชั้น ความยาวของพารามิเตอร์จะถูกส่งผ่านตามความยาวสตริงของพารามิเตอร์จริง และค่าจริง ดูเหมือนว่าความยาวของสตริงจะคำนวณผิด
ไม่มีทางเลือกนอกจากติดตามและแก้ไขข้อบกพร่อง ในการดีบักไลบรารี VCL ของ Delphi คุณต้องเลือก "ใช้ Debug DCU" ใน "ตัวเลือกคอมไพเลอร์" ของตัวเลือกโปรเจ็กต์
อันดับแรก ติดตามโปรแกรมไคลเอนต์ จากนั้น ClientDataSet1.Execute จากนั้นดำเนินการตามชุดฟังก์ชันต่างๆ เช่น TCustomClientDataSet.Exectue, TCustomeClientDataSet.PackageParams, TCustomClientDataSet.DoExecute เป็นต้น จนกระทั่ง AppServer.AS_Execute(ProviderName, CommandText, Params, OwnerData); ส่งคำขอไปยังเซิร์ฟเวอร์ ไม่มีความผิดปกติ ดูเหมือนว่าปัญหาอยู่ที่ฝั่งเซิร์ฟเวอร์
หลังจากติดตามเซิร์ฟเวอร์และทดลองใช้งานซ้ำแล้วซ้ำเล่า ฉันมุ่งเน้นไปที่ฟังก์ชัน TCustomADODataSet.PSSetCommandText หลังจากติดตามซ้ำแล้วซ้ำเล่าโดยละเอียด เป้าหมายก็แม่นยำมากขึ้นเรื่อยๆ: TCustomADODataSet.PSSetParams, TParameter.Assign, TParameter.SetValue, VarDataSize ในที่สุดฉันก็พบแหล่งที่มาของ BUG: ฟังก์ชัน VarDataSize นี่คือโค้ด:
ฟังก์ชัน VarDataSize (ค่า const: OleVariant): จำนวนเต็ม;
เริ่ม
ถ้า VarIsNull(Value) แล้ว
ผลลัพธ์ := -1
อย่างอื่นถ้า VarIsArray(Value) แล้ว
ผลลัพธ์ := VarArrayHighBound(ค่า, 1) + 1
อย่างอื่นถ้า TVarData(Value).VType = varOleStr แล้ว
เริ่ม
ผลลัพธ์ := ความยาว(PWideString(@TVarData(Value).VOleStr)^); //บรรทัดที่มีปัญหา
ถ้าผลลัพธ์ = 0 แล้ว
ผลลัพธ์ := -1;
จบ
อื่น
ผลลัพธ์ := SizeOf(OleVariant);
จบ;
ในฟังก์ชันนี้จะมีการคำนวณความยาวของพารามิเตอร์จริง โดยจะใช้ที่อยู่ของค่าใน Value และใช้เป็นตัวชี้ WideString เพื่อค้นหาความยาวของสตริง ดังนั้น สตริง "อักขระจีนที่จะ" ถูกตัดทอน" คือความยาวกลายเป็น 7 แทนที่จะเป็น 14
เมื่อพบปัญหาแล้ว การแก้ไขก็ไม่ใช่เรื่องยาก เพียงแค่
ผลลัพธ์ := ความยาว(PWideString(@TVarData(Value).VOleStr)^); //บรรทัดที่มีปัญหา
เปลี่ยนเป็น
ผลลัพธ์ := ความยาว(PAnsiString(@TVarData(Value).VOleStr)^); //ไม่มีปัญหา
แค่นั้นแหละ.
แต่จะทำให้ความยาวเพิ่มขึ้นเป็นสองเท่าเมื่อค้นหาความยาวของสตริงภาษาอังกฤษ ดังนั้นคุณจึงสามารถเปลี่ยนบรรทัดนี้เป็น:
ผลลัพธ์ := ความยาว (ค่า);
ด้วยวิธีนี้ จึงสามารถหาความยาวที่ถูกต้องได้ไม่ว่าจะเป็นสตริงภาษาจีน อังกฤษ หรือภาษาจีนผสมอังกฤษ นี่เป็นคำถามที่ยังทำให้ฉันงง เหตุใดบอร์แลนด์จึงวนเป็นวงกลมเพื่อค้นหาความยาวของค่าพารามิเตอร์ผ่านพอยน์เตอร์ ใครทราบช่วยอธิบายให้ผมหน่อยนะครับ ขอบคุณมากครับ!
เพื่อนบางคนอาจมีคำถามว่าเหตุใดปัญหาการตัดทอนสตริงจึงไม่เกิดขึ้นเมื่อไม่ได้ทำผ่านสถาปัตยกรรมสามชั้น คำตอบนั้นไม่ซับซ้อน เมื่อส่งคำสั่งไปยัง SQL Server โดยตรงผ่าน ADOCommand จะกำหนดความยาวของพารามิเตอร์ตามโครงสร้างตาราง ขั้นแรกจะส่งข้อความไปยัง SQL Server
ตั้งค่า FMTONLY ON เลือก HTH, GCMC จาก XsHeTong ตั้งค่า FMTONLY OFF
เพื่อให้ได้โครงสร้างโต๊ะ ภายใต้สถาปัตยกรรมสามชั้น แม้ว่า TCustomADODataSet ยังใช้วัตถุ TADOCommand ภายในเพื่อออกคำสั่ง แต่ก็ไม่ได้ใช้ค่านี้เป็นความยาวพารามิเตอร์หลังจากได้รับโครงสร้างตาราง แต่จะคำนวณความยาวใหม่ตามพารามิเตอร์จริง ผลลัพธ์ที่ได้ ข้อผิดพลาด
BUG2 ปัญหาเกี่ยวกับฟิลด์การค้นหาของ ClientDataSet:
วิธีการสร้าง BUG:
สร้างโปรเจ็กต์ใหม่และวาง ClientDataSets สองชุดในนั้น ได้แก่ cds1 และ cds2 แหล่งข้อมูลเหล่านี้สามารถกำหนดได้เอง โดย cds1 เป็นชุดข้อมูลหลัก ใน cds1 ค่าเพื่อค้นหาค่าที่สอดคล้องกันใน cds2
โดยทั่วไปแล้ว การรันโปรแกรมเป็นเรื่องปกติ แต่เมื่อเครื่องหมายคำพูดเดียว "'" ปรากฏในค่าในช่องการค้นหาของ cds1 (คุณสามารถแก้ไขหรือเพิ่มบันทึกได้ ลองป้อนเครื่องหมายคำพูดเดี่ยว) ข้อผิดพลาดจะเกิดขึ้นทันที : ค่าคงที่สตริงที่ไม่สิ้นสุด
การวิเคราะห์และซ่อมแซม BUG:
สาเหตุของ BUG นี้ชัดเจนกว่าครั้งก่อนมาก ต้องเกิดจากความล้มเหลวในการจัดการผลข้างเคียงของเครื่องหมายคำพูดเดี่ยวอย่างเหมาะสม
ในทำนองเดียวกัน มาติดตามซอร์สโค้ดของ VCL:
เรียกใช้โปรแกรมและเมื่อมีข้อผิดพลาดเกิดขึ้น ให้เปิดหน้าต่าง Call Stack (ในเมนู View->Debug Windows) เพื่อตรวจสอบการเรียกใช้ฟังก์ชันบางส่วนที่ชัดเจนและไม่มีปัญหา เริ่มจากจุดที่เกี่ยวข้องกัน เพื่อค้นหาสาเหตุ ประการแรก การเรียกใช้ฟังก์ชันที่เกี่ยวข้องกับ Lookup คือ TField.CalcLookupValue เราตั้งค่าเบรกพอยต์ในฟังก์ชันนี้ รันโปรแกรมใหม่ และทำการดีบักขั้นตอนเดียวหลังจากการหยุดชะงัก
TCustomClientDataSet.Lookup->TCustomClientDataSet.LocateRecord
หลังจากการเรียกใช้ฟังก์ชันหลายครั้งข้างต้น เราจะกำหนดเป้าหมายในกระบวนการ LocateRecord อย่างรวดเร็ว ในกระบวนการนี้ ระบบจะสร้างเงื่อนไขตัวกรองที่สอดคล้องกันโดยขึ้นอยู่กับการตั้งค่าของฟิลด์การค้นหา จากนั้นจึงเพิ่มเงื่อนไขตัวกรองที่สอดคล้องกันให้กับชุดข้อมูลเป้าหมาย พบและข้อผิดพลาดอยู่ที่การสร้างเงื่อนไขตัวกรอง ตัวอย่างเช่น เราต้องไปที่ cds2 ตามค่าของฟิลด์ Cust (ถือว่าเป็น 001) ใน cds1 และค้นหาค่าของฟิลด์ CustName ที่สอดคล้องกันตามค่าของฟิลด์ CustID เงื่อนไขที่สร้างขึ้นควรเป็น [CustID] = '001' แต่ถ้าค่าของ Cust คือ aa'bb เงื่อนไขที่สร้างขึ้นจะกลายเป็น [CustID] = 'aa'bb' ซึ่งเห็นได้ชัดว่านำไปสู่ค่าคงที่สตริงที่ยังไม่เสร็จ
โดยปกติแล้ว เมื่อเราแก้ไขปัญหาของเครื่องหมายคำพูดเดี่ยวที่ปรากฏภายในเครื่องหมายคำพูดเดี่ยว เราเพียงต้องเขียนเครื่องหมายคำพูดสองตัวในเครื่องหมายคำพูดเท่านั้น จะไม่มีข้อผิดพลาด ดังนั้นคุณสามารถแก้ไขซอร์สโค้ดได้ดังนี้:
ค้นหารหัสต่อไปนี้ในขั้นตอน LocateRecord:
ftString, ftFixedChar, ftWideString, ftGUID
ถ้า (i = Fields.Count - 1) และ (loPartialKey ในตัวเลือก) จากนั้น
ValStr := Format('''%s*''',[VarToStr(Value)]) อื่น ๆ
ValStr := Format('''%s''',[VarToStr(ค่า)]);
เปลี่ยนเป็น:
ftString, ftFixedChar, ftWideString, ftGUID:
ถ้า (i = Fields.Count - 1) และ (loPartialKey ในตัวเลือก) จากนั้น
ValStr := Format('''%s*''',[ StringReplace(VarToStr(Value),'''','''''',[rfReplaceAll])])
อื่น
ValStr := Format('''%s''',[ StringReplace(VarToStr(Value),'''','''''',[rfReplaceAll])]);
นั่นคือ เมื่อสร้างสตริงเงื่อนไขตัวกรอง ให้เปลี่ยนเครื่องหมายคำพูดเดี่ยวทั้งหมดในค่าตัวกรองของเงื่อนไขจากหนึ่งเป็นสอง
เพื่อให้มั่นใจว่าการแก้ไขนี้ถูกต้อง ฉันได้ตรวจสอบกระบวนการ LocateRecord ที่เกี่ยวข้องใน TCustomADODataSet (เมื่อใช้ฟิลด์การค้นหาใน TADODataSet จะไม่มีข้อผิดพลาดเนื่องจากเครื่องหมายคำพูดเดี่ยว เฉพาะเมื่อใช้ TCustomClientDataSet) วิธีการประมวลผลจะเหมือนกับ TCustomClientDataSet แตกต่างกันเล็กน้อย โดยจะสร้างเงื่อนไขตัวกรองผ่านฟังก์ชัน GetFilterStr แต่ใน GetFilterStr จะจัดการปัญหาของเครื่องหมายคำพูดเดี่ยวได้อย่างถูกต้อง เมื่อพิจารณาด้วยวิธีนี้ ปัญหาในการจัดการราคาเดี่ยวไม่ถูกต้องใน LocateRecord ของ TCustomClientDataSet ถือเป็นการละเลยเล็กน้อยโดย Borland