13 февраля 2015 г.

Пробуем перейти на XE5

В 2012 году я уже пытался перевести свои основные проекты с Delphi XE на XE2. Тогда дело встало из-за ошибки компилятора в XE2.
На этот раз я решил повторить эксперимент и попытался перейти с 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. По крайней мере с ее багами я уже знаком.

Комментариев нет:

Отправить комментарий