среда, июля 18, 2012

Евангелисты врут. FireMonkey - отстой.

Вы, наверное, в курсе, что Embarcadero активно продвигает свое новое видение создания кроссплатформенного гуя – FireMonkey (они это называют фреймвоком, но для её нынешнего состояния это слишком круто звучит). В рунете анонсируется один конкурс за другим, проводятся вебинары, и пусть качество последних оставляет желать лучшего, но активность радует.  Теперь, собственно, к теме. В рамках последнего конкурса было предложено разработать какое-нибудь приложение для обучения. И вот вчера появилась очередная работа за авторством Евгения Чмеля (не знаю, склоняется эта фамилия или нет). В отличии от виденных ранее, простеньких “одноформочек”, тут была сделана попытка подергать обезьяну за все конечности: стилизация, 3D, шейдерные эффекты (о GPU accelerated graphics очень любят говорить евангелисты Embarcadero :)) ). Давайте посмотрим, что из этого вышло. Для тех, кто не смотрел вебинары сделаю маленькое отступление. На одном из вебинаров, евангелист Embarcadero, Всеволод Леонов рассказал душещипательную историю о том, как ему пришлось “компьютер перегрузить, конкретно, жестко” (это цитата), из-за того, что Silverlight SDK и эмулятор Windows Phone 7 “не cработали” (это цитата) на его компьютере т.к. им не понравился видеоадаптер или настройки графического процессора. А вот приложения разработанные с использованием FireMokey, продолжает Всеволод, совершенно не требовательны к аппаратному обеспечению. Давайте посмотрим, как он нам врал. Беспристрастным свидетелем нам будет Process Explorer v15.05 от Марка Русиновича. Итак, качаем приложение Евгения и запускаем (скриншотов приложения Евгения не привожу, они есть по ссылке на его работу. Обратите внимание на размытость шрифтов).

Запустили приложение. Смотрим на потребление:

afterstart_memory 

Нескромно, но можно простить “передовой технологии”. Переходим к разделу “Уроки” и выбираем “Урок 5”. Начинается подготовка сцены. Процесс этот длительный (у меня заняло чуть больше минуты, на четырехядерном Phenom II с частотой 3.3GHz), запаситесь терпением. Сцена построена. Смотрим на потребление:

opened_lesson

Обезьяна хорошо подкрепилась. Даже очень хорошо. Теперь попробуйте подвигать мышку над кнопками вариантов ответов. Ощущение, что GUI реагирует ну о-о-очень вяло, не так-ли? Смотрите на график использования CPU (я имею ввиду, что вы должны сами это попробовать, на своем компьютере) – в эти моменты его загрузка приближается к 100% (у меня было ~21.5% для четырехядерного процессора, что эквивалетно 86% для одноядерного). А ведь нам кто-то рассказывал про GPU accelerated graphics. Ладно, идем дальше. Отвечаем на все вопросы урока. Смотрим потребление:

after_lesson

Глаза не округлились? Теперь посмотрите, для сравнения, сколько потребляет 3D-стрелялка FarCry с активным игровым процессом (уровень называется Фабрика, если кому, вдруг, интересно) запущенная в полноэкранном режиме 1440x900:

farcry 

Выводы делайте сами.

четверг, июля 05, 2012

Delphi XE2 Update 4 HotFix 1. А с дженериками до сих пор проблемы :(

Хотите получить Access Violation на абсолютно ровном месте, без намека на ухабы? Велком, как говорится:

program array_of_generic_record;

{$APPTYPE CONSOLE}

Uses

TypInfo;

Type

TRec<T> = Record
  FField : T;
End;

Var

a : Array Of TRec<TObject>;
i : Integer;
r : TRec<TObject>;

begin

WriteLn(SizeOf(TRec<TObject>)); // 4
WriteLn(GetTypeData(TypeInfo(TRec<TObject>))^.elSize); // 4

SetLength(A, 1000000); // but system.DynArraySetLength.elSize = 1; line 28288
                        // (DelphiXE2 update 4 hotfix 1); look at attached screenshot: bug.png

// absolutely normal code lead to access violation error
For i := Low(a) To High(a) Do
  a[i] := r;

end.

 

В общем, из описания понятно, что в системном модуле неверно получается размер элемента динамического массива. Из-за чего, понятное дело, памяти выделяется меньше и попытки работать с таким массивом приводят к краху. На дворе 2012 год. Блин, когда они уже нормальный компилятор осилят написать. Лучи ненависти вам, абракадабры :E

 

p.s. QC #106917

среда, апреля 18, 2012

Firemonkey. Анимммммация.

Полюбуйтесь:

fmx_animmmmmation

Параметры анимации:

  • Продолжительность 0.5 сек
  • Стартовое значение 6
  • Конечное значение 64

В уголочке приложение работающее под виртуальной машиной с Windows XP и приложение работающее на хост-машине с Windows 7. Приложение пишет в Memo-лог изменения свойства Step на которое настроена анимация.

Обнаруженных косяков два:

  1. Дважды приходит стартовое значение
  2. Разное количество “шагов” анимации при идентичных параметрах

А я думаю чего это у меня анимашка так дергается на семерке, и отчего кажется более плавной на виртуализированной XP. Ужас, блин.

понедельник, декабря 05, 2011

Знатные грабельки компилятора

Сегодня налетел на такое:

program nested_exception_class;

{$APPTYPE CONSOLE}

{$R *.res}

uses
SysUtils;

Type

EUnitException = Class(Exception);

//
TMyRec = Record

Type

Exception = Class(EUnitException);

EOtherException = Class(Exception);

End;
//

begin

// ожидается: TMyRec.Exception, в действительности: Exception
WriteLn(TMyRec.EOtherException.ClassParent.ClassName);

// утверждение должно быть истинным, но нарушается
Assert(TMyRec.EOtherException.ClassParent = TMyRec.Exception);

end.


Компилятор выбрал неправильного родителя для моего класса (EOtherException), хотя в редакторе, если кликнуть (Ctrl+Click) по, родителю (Class(Exception)) переход происходит к “правильному” классу (TMyRec.Exception). Баг воспроизводится в Delphi 2006, Delphi XE2 Update 2, промежуточные не проверял, но думаю и там воспроизведется.

среда, декабря 01, 2010

Вы все еще верите докам?

Вот что говорит Platform SDK о реализации метода Invoke, в части о возвращаемых значениях, интерфейса IDispatch:

DISP_E_NONAMEDARGS

This implementation of IDispatch does not support named arguments.

А вот что говорит система получив DISP_E_NONAMEDARGS:

Eng:

---------------------------
test
---------------------------
No named arguments.
---------------------------
OK  
---------------------------

Rus:

---------------------------
test
---------------------------
Именованные аргументы отсутствуют.
---------------------------
ОК  
---------------------------

 

Совсем одно и то же, правда?

четверг, ноября 25, 2010

Условная компиляция: defines vs. const

Те кому приходится обеспечивать работоспособность кода на нескольких версиях Delphi не по наслышке знают о “прелестях” условной компиляции. Довольно сложно держать в голове набор фич каждой версии и особенности их работы. Ориентация на CompilerVersion, RTLVersion и VERXXX кажется простым делом в момент написания кода, но превращается в кошмар к моменту его рефакторинга т.к. все детали из головы уже выветрились и скучные выражения вроде {$IF CompilerVersion >= 18.5} ясности не добавляют. Обычно эта проблема решается с помощью включаемых файлов (те, что с расширением .inc), где, основываясь на значениях перечисленных выше сущностей (и возможно некоторых других), создается некий набор определений (defines) с читаемыми именами. Делается это, например, так:

{$IFNDEF FPC}
{$DEFINE DCC}
{$ENDIF}
...
// Anonymous methods
{$IF Defined(DCC) and (CompilerVersion >= 20)}
{$DEFINE HAS_ANON_METHODS}
{$IFEND}


Тут мы видим создаваемое определение HAS_ANON_METHODS, которое в дальнейшем позволит нам не вспоминать о версии компилятора, а просто и удобно писать код:



Type
TMyEvent = {$IFDEF HAS_ANON_METHODS}Reference To {$ENDIF}Procedure(Sender : TObject) Of Object;


Но так ли, на самом деле, это удобно? Судите сами. CodeInsight не обеспечивает подсказки для определений. Написав “HAS_” и нажав Ctrl+Space вы не получите весь список возможных определений, а значит снова должны все держать в голове. Это не удобно. Лично меня это просто убивало. Однако, решение есть (для версий Delphi начиная с 2005):



Unit Common.Features;

Interface

Type

//
Delphi = Record

Type

//
Platform = Record

Const

Windows = {$IF Defined(MSWINDOWS)}True{$ELSE}False{$IFEND};
Linux = {$IF Defined(LNUX)}True{$ELSE}False{$IFEND};
MacOS = {$IF Defined(MACOS)}True{$ELSE}False{$IFEND};

{$REGION ' Platform check '}

{$IF Ord(Windows) + Ord(Linux) + Ord(MacOS) <> 1}

{$MESSAGE FATAL 'Unknown platform'}

{$IFEND}

{$ENDREGION}

x32 = SizeOf(Pointer) = 4;
x64 = SizeOf(Pointer) = 8;

End;
//

//
Language = Record

Const

Unicode = {$IF Declared(UnicodeString)}True{$ELSE}False{$IFEND};
Generics = CompilerVersion > 18.5;
AnonymousMethods = CompilerVersion >= 20;
Attributes = {$IF Declared(TCustomAttribute)}True{$ELSE}False{$IFEND};

End;
//

//
RTL = Record

Type

//
TObject = Record

Const

ToString = RTLVersion >= 20;

End;
//

//
ObjectInvoke = Record

Const

MaxParams = {$IF RTLVersion < 20}10{$ELSE}32{$IFEND};

End;
//

End;
//

End;
//

Implementation

End.


Это не законченное решение, пока это только концепт. Надеюсь, основная идея понятна. Небольшой пример кода:



scode      := E_FAIL;
bstrSource := {$IF Not Delphi.Language.Unicode}UniUtf8Decode{$IFEND}(ClassName);

{$IF Delphi.RTL.TObject.ToString}

bstrDescription := ExceptObject.ToString

{$ELSE}

If ExceptObject Is Exception Then
bstrDescription := Exception(ExceptObject).Message
Else
bstrDescription := {$IF Not Delphi.Language.Unicode}UniUtf8Decode{$IFEND}(ExceptObject.ClassName);

{$IFEND}


 



Помимо того, что тут мы сразу видим иерархию (и это очень помогает), нам еще и CodeInsight будет помогать (только за пределами фигурных скобок).



26.11.2010



Забыл написать о самом серьезном преимуществе такого решения. Компилятор контролирует корректность идентификаторов (правда с некоторыми оговорками, но это не важно), и если где-то ошибиться в написании то такой код просто не скомпилируется.

суббота, июня 26, 2010

Generics vs. Inline

Директива Inline не действует на методы в дженериках. Кажется, благое дело обернуть работу с динамическими массивами структурой-дженериком дабы увеличить функциональность путем добавления простых методов Add, Remove, Insert и т.п. Но все благие намерения натыкаются на суровую реальность в которой существует компилятор Delphi :( Столкновение произошло на этапе добавления свойства Items с сеттером SetItem и геттером GetItem, которые, конечно-же, были объявлены как Inline ведь их тела состоят ровно из одной строки (делать проверки индексов в столь тонкой обертке не практично и даже вредно), а вызываться они могут очень и очень часто. После проверки выяснилось, что компилятор не разворачивает тела этих методов, а делает их вызовы :( Можно, конечно, было объявить свойство без сеттера и геттера, но такие свойства не могут быть дефолтными, а значит писать код вида arr[index] := x; уже не удастся.

“Что-же так все неуклюже-то?” © Особенности национальной охоты.

понедельник, января 25, 2010

Ужасы… Windows Platform SDK

Пользовался Windows Platform SDK for Windows Vista.  Желая иметь самую свежую документацию скачал новый Windows SDK for Windows 7 and .NET Framework 3.5 Service Pack 1 (Build date: 6/11/2009).  Ужас. Смотрим статью о WideCharToMultiByte:

Список кодовых страниц для которых флаги и некоторые параметры должны быть нулевыми.

Список из нового SDK:

  • 50220
  • 50221
  • 50222
  • 50225
  • 50227
  • 50229
  • 57002 through 57011
  • 65000 (UTF-7)
  • 42 (Symbol)

А вот страницы из прежнего SDK:

  • 50220
  • 50221
  • 50222
  • 50225
  • 50227
  • 50229
  • 52936
  • 54936
  • 57002 through 57011
  • 65000 (UTF7)
  • 42 (Symbol)

 

Как видим, из нового SDK исчезло упоминание страницы 52936 (54936 упоминается в документации дополнительно). Здравствуйте, грабли!

Теперь сравните описания параметров.

Новый SDK:

lpDefaultChar [in]

Optional. Pointer to the character to use if a character cannot be represented in the specified code page. The application sets this parameter to NULL if the function is to use a system default value. To obtain the system default character, the application can call the GetCPInfo or GetCPInfoEx function.

For the CP_UTF7 and CP_UTF8 settings for CodePage, this parameter must be set to NULL. Otherwise, the function fails with ERROR_INVALID_PARAMETER.

lpUsedDefaultChar [out]

Optional. Pointer to a flag that indicates if the function has used a default character in the conversion. The flag is set to TRUE if one or more characters in the source string cannot be represented in the specified code page. Otherwise, the flag is set to FALSE. This parameter can be set to NULL.

For the CP_UTF7 and CP_UTF8 settings for CodePage, this parameter must be set to NULL. Otherwise, the function fails with ERROR_INVALID_PARAMETER.

И прежний SDK:

lpDefaultChar

[in] Pointer to the character to use if a wide character cannot be represented in the specified code page. The application sets this parameter to a null pointer if the function is to use a system default value. To obtain the system default character, the application can call the GetCPInfo or GetCPInfoEx function.

For the code pages listed for dwFlags, this parameter must be set to a null pointer. Otherwise, the function fails with ERROR_INVALID_PARAMETER.

lpUsedDefaultChar
[in] Pointer to a flag that indicates if the function is to use a default character in the conversion. The flag is set to TRUE if a default character is necessary, and to FALSE otherwise. This parameter can be set to a null pointer.

For the code pages mentioned in dwFlags, lpUsedDefaultChar must be a null pointer. Otherwise, the function fails with ERROR_INVALID_PARAMETER.

Нужно ли говорить, что информация в новом SDK не соответствует действительности. Здравствуйте, еще одни грабли! Ужас… Как можно верить такой доке? Остается только проверять.

суббота, октября 31, 2009

Delphi 2010. Быдлокод

Юнит DBXJSON.pas. Я вообще в ауте...

class function TJSONString.Hex(const Digit: Integer): Byte;
var
HexData: TBytes;
begin
SetLength(HexData,16);
HexData[0] := Ord('0');
HexData[1] := Ord('1');
HexData[2] := Ord('2');
HexData[3] := Ord('3');
HexData[4] := Ord('4');
HexData[5] := Ord('5');
HexData[6] := Ord('6');
HexData[7] := Ord('7');
HexData[8] := Ord('8');
HexData[9] := Ord('9');
HexData[10] := Ord('A');
HexData[11] := Ord('B');
HexData[12] := Ord('C');
HexData[13] := Ord('D');
HexData[14] := Ord('E');
HexData[15] := Ord('F');
Result := HexData[Digit];
end;

вторник, октября 20, 2009

Delphi 2010. System.TObject.GetInterface

Полюбуйтесь на код обеспечивающий проверку идентификатора интерфейса на соответствие "magic GUID" ({CEDF24DE-80A4-447D-8C75-EB871DC121FD}), для безопасного кастинга (TMyObject(TMyIntf)) к объектому типу:

if (Int64(ObjCastGUID.D1) = Int64(IID.D1)) and
(Int64(ObjCastGUID.D4) = Int64(IID.D4)) then
Pointer(Obj) := Self;

И это в системном юните... #78808

вторник, мая 26, 2009

Ubuntu 9.04

Четыре дня назад получил по почте новую версию Ubuntu. Итог: оно все лучше и лучше. Несмотря на то, что чего-то не срослось с wubi общие впечатления самые положительные. Очень порадовала, сразу бросившаяся в глаза, качественная настройка саб-пиксельного сглаживания шрифтов. Им бы еще иконки отдизайнить в какой-нибудь конторе, чтоб в едином стиле, да в Гноме немного уменьшить контролы. В общем, мне понравилось :)

воскресенье, мая 17, 2009

#72213 Fixed

На фикс такой очевидной ошибки компилятора потребовалось два месяца... Страшно подумать сколько будут фиксить некорректную инициализацию/финализацию advanced-records, на которую налететь не так просто :( Вот и юзай новые языковые фичи...

вторник, апреля 28, 2009

Три класса секундомеров

Выложен модуль Common.Classes.pas содержащий три класса (advanced-records) секундомеров. TLowResStopwatch на основе GetTickCount, THighResStopwatch на основе QueryPerformanceCounter, и TStopwatch использующий один из предыдущих классов секундомеров в зависимости от аппаратной части компьютера.

вторник, апреля 21, 2009

Исправление в Interface Extender

Исправлена ошибка при работе с динамическим массивом. Проявлялась при попытке добавить несколько интерфейсов на один класс.

понедельник, апреля 20, 2009

Расширенная поддержка интерфейсов для Delphi

В блоге Алексея Тимохина озвучена хотелка касающаяся поддержки интерфейсов базовыми классами. На мой взгляд, идея весьма спорная, во всяком случае при нынешнем положении дел в языке. Однако, реализовать нечно подобное можно уже сейчас ;) Тут я набросал пример, как это может быть реализовано (intfExtender.pas реализует непосредственно механизм расширения, а unit1.pas содержит два примера). Если кому пригодится, буду только рад ;) Универсальность прикладного кода, о которой говорит Алексей, таким образом можно обеспечить.

p.s. Модуль сильно не тестировался, посему никаких гарантий если что ;)

среда, апреля 15, 2009

Очередные ошибки инициализации/финализации в Delphi 2006 - 2009

Отчет #73008 описывает несвоевременную финализцию энумератора являющегося интерфейсом при использовании в основном теле программы (в процедурах и функциях работает нормально), что в некоторых случаях может приводить к ошибкам доступа (Access violation).

Отчет #73009 описывает неадекватную инициализацию (а точнее ее отсутствие) интерфейсного поля advanced record в случае, когда последняя используется в качестве возвращаемого значения.