Analisis dan perbaikan dua BUG di Delphi
Saat menggunakan Delphi 7 untuk pengembangan database tiga tingkat, saya menemui dua masalah kecil. Melalui percobaan berulang kali, saya akhirnya menemukan dua BUG kecil di Delphi 7 dan memperbaikinya (sepertinya ada BUG yang sama di Delphi 6), menulis Artikel ini membagikan kegembiraan kesuksesan kepada semua orang. Saya juga baru mengenal Delphi. Pasti banyak yang salah di artikel ini. Saya ingin meminta teman-teman untuk mengoreksi saya.
BUG1. Karakter Cina terpotong saat meneruskan parameter:
Metode untuk mereproduksi BUG:
SQL Server 2000 digunakan di latar belakang, dan terdapat tabel XsHeTong untuk pengujian.
Pertama buat server data: buat proyek baru, buat modul data jarak jauh, tempatkan satu ADOConnection, ADODataSet, dan DataSetPRovider di dalamnya, dan buat pengaturan yang sesuai. Biarkan ComamndText dari ADODataSet kosong, dan atur poAllowCommandText di Option-nya ke True. Kompilasi dan jalankan.
Buat program klien lagi: buat proyek baru, letakkan DCOMConnection pada formulir, sambungkan ke server data yang dibuat sebelumnya, letakkan ClientDataSet, atur koneksinya ke DCOMConnection di sini, dan atur ProviderName-nya ke server di atas Nama dari Penyedia DataSet. Terakhir, tempatkan DataSource dan DBGrid dan buat pengaturan yang sesuai untuk melihat hasilnya, lalu tempatkan Tombol untuk pengujian.
Tulis kode yang mirip dengan yang berikut ini di OnClick Button (di sini saya menggunakan tabel XsHeTong dan dua bidangnya HTH (char 15), GCMC (varchar 100), Anda dapat menyesuaikannya sesuai dengan situasi pengujian Anda yang sebenarnya):
dengan ClientDataSet1 lakukan
mulai
Menutup;
CommandText := 'Masukkan ke nilai XsHeTong(HTH, GCMC)(:HTH,:GCMC)';
Param[0].AsString := '12345';
Params[1].AsString := 'Karakter Cina yang akan dipotong';
Menjalankan;
Menutup;
CommandText := 'Pilih * dari XsHeTong';
Membuka;
akhir;
Jalankan program, klik tombol, dan lihat bahwa catatan telah dimasukkan. Sayangnya, hasilnya tidak benar. "Karakter Cina yang akan terpotong" menjadi "akan terpotong", tetapi "12345" tanpa karakter Cina dimasukkan dengan benar. .
Analisis dan perbaikan BUG:
Sebagai perbandingan, saya mencoba langsung menggunakan ADOConnection, ADOCommand, dan ADOTable untuk menguji arsitektur C/S. Hasilnya benar dan karakter Cina tidak terpotong. Hal ini menunjukkan bahwa BUG ini hanya muncul pada arsitektur tiga tingkat.
Gunakan SQL Server Profiler untuk menyelidiki pernyataan yang dikirimkan ke SQL Server untuk dieksekusi dan temukan perbedaan berikut antara arsitektur dua tingkat dan arsitektur tiga tingkat:
Arsitektur dua tingkat:
exec sp_executesql N'Masukkan ke nilai XsHeTong(HTH, GCMC)(@P1,@P2)', N'@P1 varchar(15),@P2 varchar(100)', '12345', 'Karakter Cina yang akan dipotong '
Arsitektur tiga tingkat:
exec sp_executesql N'Masukkan ke nilai XsHeTong(HTH, GCMC)(@P1,@P2)', N'@P1 varchar(5),@P2 varchar(7)', '12345', 'akan terpotong
Jelasnya, dalam arsitektur dua lapis, panjang parameter diteruskan sesuai dengan struktur perpustakaan sebenarnya, dalam arsitektur tiga lapis, panjang parameter diteruskan sesuai dengan panjang string parameter aktual, dan panjang string sebenarnya panjang string tampaknya salah perhitungan. Karakter Cina diperlakukan sebagai dua karakter.
Tidak ada pilihan selain melacak dan men-debug. Untuk men-debug perpustakaan VCL Delphi, Anda harus memilih "Gunakan Debug DCUs" di "Opsi Kompiler" pada opsi proyek.
Pertama lacak program klien, lalu ClientDataSet1.Execute, lalu jalankan serangkaian fungsi seperti TCustomClientDataSet.Exectue, TCustomeClientDataSet.PackageParams, TCustomClientDataSet.DoExecute, dll., hingga AppServer.AS_Execute(ProviderName, CommandText, Params, OwnerData); mengirimkan permintaan ke server Tidak ada kelainan. Tampaknya masalahnya terletak pada sisi server.
Setelah melacak server dan uji coba berulang kali, saya fokus pada fungsi TCustomADODataSet.PSSetCommandText. Setelah pelacakan berulang dan mendetail, target menjadi semakin tepat: TCustomADODataSet.PSSetParams, TParameter.Assign, TParameter.SetValue, VarDataSize. Saya akhirnya menemukan sumber BUG: fungsi VarDataSize.
fungsi VarDataSize(Nilai konstan: OleVariant): Integer;
mulai
jika VarIsNull(Nilai) maka
Hasil := -1
lain jika VarIsArray(Nilai) lalu
Hasil := VarArrayHighBound(Nilai, 1) + 1
lain jika TVarData(Value).VType = varOleStr lalu
mulai
Hasil := Panjang(PWideString(@TVarData(Value).VOleStr)^); //Baris yang bermasalah
jika Hasil = 0 maka
Hasil := -1;
akhir
kalau tidak
Hasil := SizeOf(OleVariant);
akhir;
Dalam fungsi inilah panjang parameter sebenarnya dihitung. Dibutuhkan alamat nilai dalam Value dan menggunakannya sebagai penunjuk WideString untuk menemukan panjang string terpotong" adalah Panjangnya menjadi 7 bukannya 14.
Setelah masalah ditemukan, tidak sulit untuk menyelesaikannya
Hasil := Panjang(PWideString(@TVarData(Value).VOleStr)^); //Baris yang bermasalah
Ubah ke
Hasil := Panjang(PANsiString(@TVarData(Value).VOleStr)^); //Tidak masalah
Itu saja.
Namun hal ini akan menyebabkan panjangnya menjadi dua kali lipat saat mencari panjang string bahasa Inggris, jadi Anda juga dapat mengubah baris ini menjadi:
Hasil := Panjang(Nilai);
Dengan cara ini, panjang yang benar dapat diperoleh baik itu string berbahasa Mandarin, Inggris, atau campuran Mandarin-Inggris. Ini adalah pertanyaan yang masih membingungkan saya. Mengapa Borland berputar-putar untuk mencari panjang nilai parameter melalui sebuah pointer? Jika ada yang tahu, tolong jelaskan kepada saya. Terima kasih banyak!
Beberapa teman mungkin bertanya-tanya, mengapa masalah pemotongan string ini tidak terjadi jika tidak dilakukan melalui arsitektur tiga tingkat? Jawabannya tidak rumit. Saat mengirimkan perintah ke SQL Server secara langsung melalui ADOCommand, ini menentukan panjang parameter sesuai dengan struktur tabel. Ini pertama-tama akan mengirim pesan ke SQL Server
SET FMTONLY ON pilih HTH,GCMC dari XsHeTong SET FMTONLY OFF
untuk mendapatkan struktur tabel. Di bawah arsitektur tiga tingkat, meskipun TCustomADODataSet juga menggunakan objek TADOCommand secara internal untuk mengeluarkan perintah, ia tidak menggunakan nilai ini sebagai panjang parameter setelah mendapatkan struktur tabel, tetapi menghitung ulang panjang berdasarkan parameter sebenarnya sebuah kesalahan.
BUG2. Masalah dengan bidang Pencarian ClientDataSet:
Metode untuk mereproduksi BUG:
Buat proyek baru dan letakkan dua ClientDataSets di dalamnya, yaitu cds1 dan cds2. Sumber datanya bisa berubah-ubah. Diantaranya, cds1 adalah kumpulan data utama. Tambahkan bidang Pencarian baru di dalamnya di cds1 nilai untuk menemukan nilai yang sesuai di cds2.
Secara umum, menjalankan program adalah hal yang normal, tetapi begitu satu tanda kutip "'" muncul di nilai di bidang Pencarian cds1 (Anda dapat mengubah atau menambahkan catatan, coba masukkan satu tanda kutip), kesalahan akan segera terjadi : Konstanta string yang tidak diakhiri.
Analisis dan perbaikan BUG:
Penyebab BUG ini jauh lebih jelas dibandingkan yang sebelumnya. Pasti disebabkan oleh kegagalan dalam menangani efek samping dari tanda kutip tunggal dengan benar.
Demikian pula, mari kita lacak kode sumber VCL:
Jalankan program, dan ketika terjadi kesalahan, buka jendela Call Stack (di View->Debug Windows) untuk memeriksa pemanggilan fungsi. Beberapa panggilan sebelumnya sudah jelas dan tidak ada masalah ke Pencarian untuk memeriksa penyebabnya. Yang pertama Panggilan fungsi yang terkait dengan Pencarian adalah TField.CalcLookupValue. Kami menetapkan breakpoint dalam fungsi ini, menjalankan kembali program, dan melakukan debugging satu langkah setelah gangguan.
TCustomClientDataSet.Lookup->TCustomClientDataSet.LocateRecord
Setelah beberapa pemanggilan fungsi di atas, kami dengan cepat menetapkan target dalam proses LocateRecord. Dalam proses ini, proses ini menghasilkan kondisi filter yang sesuai berdasarkan pengaturan bidang Pencarian, dan kemudian menambahkan kondisi filter yang sesuai ke kumpulan data target ditemukan, dan kesalahannya terletak pada pembentukan kondisi filter. Misalnya, kita perlu pergi ke cds2 berdasarkan nilai bidang Cust (diasumsikan 001) di cds1 dan mencari nilai bidang CustName yang sesuai berdasarkan nilai bidang CustID. Kondisi yang dihasilkan seharusnya [CustID] = '001', tetapi jika nilai Cust adalah aa'bb, kondisi yang dihasilkan akan menjadi [CustID] = 'aa'bb', yang jelas mengarah ke konstanta String yang belum selesai.
Biasanya, ketika kita menyelesaikan masalah tanda kutip tunggal yang muncul di dalam tanda kutip tunggal, kita hanya perlu menulis dua tanda kutip di dalam tanda kutip. Hal yang sama juga berlaku di sini selama kondisi yang dihasilkan menjadi [CustID] = 'aa''bb', tidak akan ada kesalahan. Jadi Anda dapat memodifikasi kode sumber seperti ini:
Temukan kode berikut dalam prosedur LocateRecord:
ftString, ftFixedChar, ftWideString, ftGUID
if (i = Fields.Count - 1) dan (loPartialKey di Opsi) lalu
ValStr := Format('''%s*''',[VarToStr(Nilai)]) lain
ValStr := Format('''%s''',[VarToStr(Nilai)]);
Ubah menjadi:
ftString, ftFixedChar, ftWideString, ftGUID:
if (i = Fields.Count - 1) dan (loPartialKey di Opsi) lalu
ValStr := Format('''%s*''',[ StringReplace(VarToStr(Nilai),'''','''''',[rfReplaceAll])])
kalau tidak
ValStr := Format('''%s''',[ StringReplace(VarToStr(Nilai),'''','''''',[rfReplaceAll])]);
Artinya, saat membuat string kondisi filter, ubah semua tanda kutip tunggal dalam nilai filter kondisi dari satu menjadi dua.
Untuk memastikan kebenaran modifikasi ini, saya memeriksa proses LocateRecord yang sesuai di TCustomADODataSet (saat menggunakan bidang Pencarian di TADODataSet, tidak akan ada kesalahan karena tanda kutip tunggal, hanya saat menggunakan TCustomClientDataSet), metode pemrosesannya sama dengan TCustomClientDataSet sedikit berbeda. Ini membangun kondisi filter melalui fungsi GetFilterStr, tetapi di GetFilterStr, ini menangani masalah tanda kutip tunggal dengan benar. Jadi jika dilihat seperti ini, masalah tidak menangani tanda kutip tunggal dengan benar di LocateRecord dari TCustomClientDataSet memang merupakan kelalaian kecil oleh Borland.