На этот раз я решил повторить эксперимент и попытался перейти с Delphi XE на версию XE5 (Update 2).
Кратко: сразу не заработало, проблемы удалось решить, но осадок остался.
Для интересующихся подробности ниже.
Check
Начал с перевода базовой инфраструктуры, исходный код здесь: github.com/achechulin/loodsman.
После сборки в XE5 все тесты выполнились успешно. Приложение тоже работает.
Запускаю приложение на машине тестового пользователя — завершается с ошибкой «Access violation at address...». Запускаю тесты на машине пользователя — все тесты, использующие
TClientDataSet
, завершаются с исключением EAccessViolation
.
Здесь уже стало очевидно, что проблема в
midas.dll
. Вместе с «Комплексом решений Аскон» устанавливается версия 6.0 библиотеки (с версии «Комплекс 2013» устанавливается еще и версия 16.0, но мне надо чтобы работало и со старыми версиями). Быстрое изучение нового кода TClientDataSet
под отладчиком и сравнение со старым кодом из Delphi XE подтвердило догадку.
В Delphi 2009 и более поздних версиях используется кодировка UTF-8 для метаданных
TClientDataSet
(и сделано это неудачно — пакеты данных не совместимы между старыми и новыми версиями, см. Наборы данных). Для этого вызывается DSBase.SetProp(dspropUTF8METADATA, True)
. Так как старые версии midas.dll
не знают флага dspropUTF8METADATA
, то SetProp
возвращает ошибку DBIERR_INVALIDPARAM
(9986). Решение очевидно — установить правильную версию midas.dll
. Вот только не совсем понятно появление исключения EAccessViolation
вместо EDBClient
c сообщением «Invalid parameter».
Смотрим дальше. Немного кода:
procedure TCustomClientDataSet.CreateDataSet; begin ... // Создает объект DSBase, реализованный в midas.dll // Переменная FDSBase пока nil FDSBase := CreateDSBase; ... end; function TCustomClientDataSet.CreateDSBase: IDSBase; begin CreateDbClientObject(CLSID_DSBase, IDSBase, Result); // Вызов SetProp вернет ошибку и мы попадем в InternalCheck Check(Result.SetProp(dspropANSICODEPAGE, DefaultSystemCodePage)); ... end; procedure TCustomClientDataSet.Check(Status: DBResult); begin if Status <> 0 then InternalCheck(Status); end; procedure TCustomClientDataSet.InternalCheck(Status: DBResult); begin if Status <> 0 then begin ... // FDSBase все еще nil // Здесь и возникнет исключение FDSBase.GetErrorString(Status, Pointer(@ErrMsgBuffer[0])); ... // А сюда уже не попадем raise EDBClient.Create(ErrMsg, Status); end; end;Теперь возникновение исключения
EAccessViolation
понятно. Но знания эти приносят только печаль — большое количество изменений в коде DataSnap не тестировалось.
StrLen
Так получилось, что большинство моих проблем с TClientDataSet связано с русскими буквами в именах полей наборов данных. ЛОЦМАН:PLM может возвращать в наборе данных поля с атрибутами. Названия атрибутов обычно пишутся русскими буквами и могут быть любой длины (в пределах разумного). Например «Группа материала по сортаменту» (30 символов, 57 байт в UTF-8).
При сборке
Loodsman.Infrastructure.DataSet.pas
в новой версии компилятор выдал предупреждение:
W1000 Symbol 'StrLen' is deprecated: 'Moved to the AnsiStrings unit'
Ничего страшного, но лучше исправить. Этот фрагмент кода был скопирован из
DBClient.pas
, и я решил посмотреть как избавились от предупреждения разработчики Delphi:
- LName := MetaDataToUnicode(szName); + LName := TMarshal.ReadStringAsAnsi(CP_UTF8, szName); - if StrLen(szName) = SizeOf(MIDASNAME) - 1 then + if LName.Length = SizeOf(MIDASNAME) - 1 then begin V := InternalGetOptionalParam(szFIELDNAME, FieldID); if not VarIsNull(V) and not VarIsClear(V) then LName := VarToStr(V); end;— Как же так? — воскликнет внимательный читатель, — ведь длина строки в UTF-16 это совсем не то же самое, что длина строки в UTF-8!
И действительно, простой тест подтверждает нашу догадку:
procedure TTestDataSet.TestLongFieldName; const CFieldName = 'Группа материала по сортаменту'; var DS: TClientDataSet; LFieldDef: TFieldDef; LData: OleVariant; begin DS := TClientDataSet.Create(nil); try LFieldDef := DS.FieldDefs.AddFieldDef; LFieldDef.Name := CFieldName; LFieldDef.DataType := ftString; LFieldDef.Size := 255; DS.CreateDataSet(); LData := DS.Data; DS.Data := LData; LFieldDef := DS.FieldDefs.Items[0]; CheckEquals(CFieldName, LFieldDef.Name); finally DS.Free(); end; end;Результат:
TestLongFieldName: ETestFailure
expected: <Группа материала по сортаменту> but was: <Группа материал�>
Новые знания приносят новую печаль — изменения вообще не тестируются. Embarcadero выпускает два релиза в год, а в старых версиях ошибки уже не исправляются.
Похоже, придется пока остаться на XE. По крайней мере с ее багами я уже знаком.
Комментариев нет:
Отправить комментарий