Анализ и исправление двух ошибок в Delphi
При использовании Delphi 7 для разработки трехуровневой базы данных я столкнулся с двумя небольшими проблемами. Путем повторных испытаний я наконец обнаружил две небольшие ошибки в Delphi 7 и исправил их (похоже, что в Delphi 6 есть такие же ошибки), написав. Эта статья разделяет радость успеха со всеми. Я также новичок в Delphi. Должно быть, в статье много неточностей. Я хотел бы попросить друзей поправить меня.
ОШИБКА 1. Китайские иероглифы обрезаются при передаче параметров:
Способы воспроизведения ОШИБКИ:
SQL Server 2000 используется в фоновом режиме, и для тестирования имеется таблица XsHeTong. Вы можете настроить ее в соответствии с реальной ситуацией.
Сначала создайте сервер данных: создайте новый проект, создайте удаленный модуль данных, поместите на него один ADOConnection, ADODataSet и DataSetPRovider и выполните соответствующие настройки. Оставьте ComamndText в ADODataSet пустым и установите для poAllowCommandText в его Option значение True. Скомпилируйте и запустите.
Снова создайте клиентскую программу: создайте новый проект, поместите DCOMConnection в форму, подключитесь к созданному ранее серверу данных, поместите ClientDataSet, установите здесь его соединение с DCOMConnection и установите его ProviderName на сервер выше. Имя DataSetProvider. Наконец, разместите DataSource и DBGrid и настройте соответствующие параметры для просмотра результатов, а затем поместите кнопку для тестирования.
Напишите код, аналогичный следующему, в OnClick кнопки (здесь я использовал таблицу XsHeTong и ее два поля HTH (символ 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. Результат оказался правильным, и китайские иероглифы не обрезались. Это показывает, что эта ОШИБКА появляется только в трехуровневой архитектуре.
Используйте SQL Server Profiler для проверки операторов, отправленных на SQL Server для выполнения, и найдите следующие различия между двухуровневой и трехуровневой архитектурой:
Двухуровневая архитектура:
exec sp_executesql N'Insert в значения XsHeTong(HTH, GCMC)(@P1,@P2)', N'@P1 varchar(15),@P2 varchar(100)', '12345', 'Китайские символы, которые будут обрезаны '
Трехуровневая архитектура:
exec sp_executesql N'Insert в значения XsHeTong(HTH, GCMC)(@P1,@P2)', N'@P1 varchar(5),@P2 varchar(7)', '12345', 'будут усечены
Очевидно, что в двухуровневой архитектуре длина параметра передается в соответствии с фактической структурой библиотеки. В трехуровневой архитектуре длина параметра передается в соответствии с длиной строки фактического параметра и фактической. Длина строки, по-видимому, рассчитана неверно. Длина китайского иероглифа считается равной двум символам.
Нет другого выбора, кроме как отслеживать и отлаживать. Чтобы отладить библиотеку VCL Delphi, вам необходимо выбрать «Использовать отладочные DCU» в «Параметры компилятора» параметров проекта.
Сначала отслеживайте клиентскую программу, затем ClientDataSet1.Execute, а затем выполните ряд функций, таких как TCustomClientDataSet.Exectue, TCustomClientDataSet.PackageParams, TCustomClientDataSet.DoExecute и т. д., пока AppServer.AS_Execute(ProviderName, CommandText, Params, OwnerData); отправляет запрос на сервер. Никаких отклонений нет. Похоже, проблема кроется на стороне сервера.
После отслеживания сервера и повторных испытаний я сосредоточился на функции TCustomADODataSet.PSSetCommandText. После повторного и детального отслеживания цель становилась все более точной: TCustomADODataSet.PSSetParams, TParameter.Assign, TParameter.SetValue, VarDataSize. Наконец-то я нашел источник ОШИБКИ: функцию VarDataSize. Вот ее код:
функция VarDataSize (const Value: OleVariant): Integer;
начинать
если VarIsNull(Значение), то
Результат := -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)^); //Нет проблем
Вот и все.
Но это приведет к удвоению длины при определении длины английской строки, поэтому вы также можете изменить эту строку на:
Результат: = Длина (значение);
Таким образом, можно получить правильную длину, будь то китайская, английская или смешанная китайско-английская строка. Это вопрос, который до сих пор меня озадачивает. Почему Borland ходит по кругу, чтобы найти длину значения параметра через указатель? Если кто знает, объясните мне большое спасибо!
У некоторых друзей могут возникнуть вопросы: почему проблема усечения строк не возникает, если это не выполняется с помощью трехуровневой архитектуры? Ответ несложный. При отправке команд SQL Server напрямую через ADOCommand он определяет длину параметра в соответствии со структурой таблицы. Сначала он отправит сообщение на SQL Server.
SET FMTONLY ON выберите HTH,GCMC из XsHeTong SET FMTONLY OFF
чтобы получить структуру таблицы. В трехуровневой архитектуре, хотя TCustomADODataSet также использует объект TADOCommand внутри себя для выдачи команд, он не использует это значение в качестве длины параметра после получения структуры таблицы, а пересчитывает длину на основе фактических параметров. Результат Это привело к следующему результату. ошибка.
ОШИБКА 2. Проблема с полем поиска ClientDataSet:
Способы воспроизведения ОШИБКИ:
Создайте новый проект и поместите в него два набора данных ClientDataSet, а именно cds1 и cds2. Их источники данных могут быть произвольными. Среди них cds1 является основным набором данных. Добавьте в него новое поле поиска, основанное на символьном поле. в cds1, чтобы найти соответствующее значение в cds2.
В целом программа запускается нормально, но как только в значении поля поиска cds1 появится одинарная кавычка "'" (вы можете изменить или добавить запись, попробуйте ввести одинарную кавычку), сразу же возникнет ошибка : Незавершенная строковая константа.
Анализ и исправление ошибок:
Причина этой ОШИБКИ гораздо более очевидна, чем предыдущая. Она должна быть вызвана неспособностью должным образом обработать побочные эффекты одинарных кавычек.
Аналогично, давайте отследим исходный код VCL:
Запустите программу и при возникновении ошибки откройте окно «Стек вызовов» (в меню «Просмотр->Отладка Windows»), чтобы проверить вызовы функций. Некоторые из предыдущих вызовов очевидны и проблем нет. для поиска, чтобы проверить причину. Первый. Вызов функции, связанной с поиском, — TField.CalcLookupValue. Мы устанавливаем точку останова в этой функции, перезапускаем программу и выполняем одноэтапную отладку после прерывания.
TCustomClientDataSet.Lookup->TCustomClientDataSet.LocateRecord
После нескольких вызовов функций, описанных выше, мы быстро устанавливаем цель в процессе LocateRecord. В этом процессе он генерирует соответствующие условия фильтра на основе настроек поля поиска, а затем добавляет соответствующие условия фильтра в целевой набор данных. найден, и неисправность заключается в формировании условий фильтра. Например, нам нужно перейти к cds2 на основе значения поля Cust (предположительно 001) в cds1 и найти соответствующее значение поля CustName на основе значения поля CustID. Сгенерированное условие должно быть [CustID] = '001', но если значение Cust равно aa'bb, сгенерированное условие станет [CustID] = 'aa'bb', что, очевидно, приводит к незавершенной строковой константе.
Обычно, когда мы решаем проблему появления одинарных кавычек внутри одинарных кавычек, нам нужно написать только две кавычки в кавычках. То же самое верно и здесь. Пока сгенерированное условие принимает вид [CustID] = 'aa''bb', ошибки не будет. Итак, вы можете изменить исходный код следующим образом:
Найдите следующий код в процедуре LocateRecord:
ftString, ftFixedChar, ftWideString, ftGUID
если (i = Fields.Count - 1) и (loPartialKey в параметрах), то
ValStr := Format('''%s*''',[VarToStr(Value)]) else
ValStr := Format('''%s''',[VarToStr(Value)]);
Изменить на:
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 (при использовании поля Lookup в TADODataSet ошибок из-за одинарных кавычек не будет, только при использовании TCustomClientDataSet), метод его обработки такой же, как и TCustomClientDataSet немного отличается. Он создает условия фильтра с помощью функции GetFilterStr, но в GetFilterStr он правильно обрабатывает проблему одинарных кавычек. Таким образом, если посмотреть на это с этой точки зрения, проблема неправильной обработки одинарных кавычек в LocateRecord TCustomClientDataSet действительно является незначительным упущением Borland.