среда, декабря 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 не соответствует действительности. Здравствуйте, еще одни грабли! Ужас… Как можно верить такой доке? Остается только проверять.