04.05.2014

UPPER в SQLite и киррилица

Вчера писал маленькую программку на заказ и столкнулся с проблемой, которая, как оказалось, волнует многих программистов, программирующих с использованием SQLite. Речь пойдет о работе SQL-функции UPPER с кириллическими буквами (на самом деле это не столько важно, правильнее будет сказать "с юникодом").
Начну с того, что я использую Delphi XE3. В данной версии (и последующих) реализована поддержка баз данных SQLite при помощи dbExpress. Т.е. никакие сторонние компоненты и модули уже не нужны. Достаточно воспользоваться "родными" компонентами с вкладки dbExpress. Более детально о том, как работать с SQLite в Delphi XE3 описано на webdelphi.ru (есть и другие источники, достаточно спросить у Google). Я же хочу остановиться конкретно на функции UPPER.
SQLite поддерживает стандартную SQL-функцию UPPER. Но, как видно из описания, поддерживаются только ASCII-символы. Т.е. не о какой кириллице речи нет. Это похоже на стандартную Delphi функцию UpperCase - латиницу преобразуем в верхний регистр, кириллицу - нет. Но в Delphi есть еще одна похожая функция - AnsiUpperCase, которая умеет работать и с кириллицей. В SQLite такой функции нет. Что же делать? Решение есть! Нужно научить SQLIte понимать функцию UPPER так, как нужно нам.
В SQLite есть возможность добавлять свои функции и реализовывать их. Делается это при помощи функции sqlite3_create_function. Данная функция описана в модуле System.Sqlite и вызывать ее необходимо только после того, как установлено соединение с базой данных. Я делаю это так:
sqlite3_create_function(Db, 'UPPER', 1, SQLITE_ANY, nil, @SQLiteUpper, nil, nil);
  • Первый параметр имеет тип sqlite3, но как мы можем увидеть в модуле System.Sqlite - это обычный Pointer. По сути это указатель на установленное соединение с базой данных (именно поэтому и необходимо чтобы соединение уже было установлено). Получить данный указатель можно, воспользовавшись классом TDBXSqliteConnection, описанным в модуле Data.DbxSqlite. У данного класса есть свойство ConnectionHandle, которое и возвращает нужный нам указатель. Получить экземпляр TDBXSqliteConnection можно через свойство DBXConnection у TSQLiteConnection. Таким образом:
    Db := (Connection.DBXConnection as TDBXSqliteConnection).ConnectionHandle;
    где Connection: TSQLiteConnection
  • Второй параметр - это и есть имя той функции, которую мы добавляем и которую мы потом можем использовать в SQL-запросах.
  • Шестой параметр - это ссылка на код, который будет непосредственно обрабатывать нашу добавляемую функцию. В нашем случае это должен быть код, который преобразует символы к верхнему регистру. Но тут так же есть свои нюансы. Я не будут вдаваться в подробности, а просто опишу тут этот код:
    procedure SQLiteUpper(Context: Pointer; Arg: Integer; Args: PPChar); cdecl;
    var
      S: string;
    begin
      S := UTF8ToWideString(sqlite3_value_text(Args^));
      CharUpperBuff(@S[1], S.Length);
      sqlite3_result_text16(Context, PChar(S), S.Length, nil);
    end;
    
Теперь можно пользоваться функцией UPPER в SQL-запросах и для кириллических символов.


30.09.2013

Динамические (dynamic) и виртуальные (virtual) методы.

Очень часто при разборе чужого кода сталкиваюсь с непониманием программистов разницы между динамическими и виртуальными методами. Лепят везде virtual не задумываясь. Даже определение придумали "завиртуалить и перекрыть". Давайте попробуем разобраться в чем же разница.

Как известно, главными принципами объектно-ориентированного программирования является наследование и полиморфизм (есть еще инкапсуляция, но нам сейчас этот принцип не интересен). Это значит, что от любого объекта можно создать наследника и в этом наследнике переопределить методы родительского класса. Пример:
type
  TMyFirstClass = class
  protected
    procedure AnyMethod; virtual; {или dynamic}
  end;

  TMySecondClass = class(TMyFirstClass)
  protected
    procedure AnyMethod; override;
  end;
Что мы тут видим? TMyFirstClass и его наследника TMySecondClass. У обоих есть метод с именем AnyMethod. У TMyFirstClass он объявлен как virtual (но возможно объявить как dynamic. В чем разница мы как раз и разбираем). У TMySecondClass этот метод объявлен как override, что говорит нам о том, что этот метод перекрывает родительский. Теперь, если мы создадим объект TMyFirstClass и вызовем AnyMethod, то выполнится метод класса TMyFirstClass. Если же объект будет создан как TMySecondClass, то выполнится метод класса TMySecondClass. Пример:
procedure TestAnyMethod;
var
  MyClass: TMyFirstClass;
begin
  MyClass := TMyFirstClass.Create   // создаем объект класса TMyFirstClass
  MyClass.AnyMethod;                // выполняем метод класса TMyFirstClass
  MyClass.Free;                     // освобождаем объект

  MyClass := TMySecondClass.Create  // создаем объект класса TMySecondClass
  MyClass.AnyMethod;                // выполняем метод класса TMySecondClass
  MyClass.Free;                     // освобождаем объект
end;
Как видим, оба блока отличаются только указанием класса, от которого создается MyClass, но из-за этого метод AnyMethod фактически вызывает разный код.

С теорией разобрались, теперь вернемся к нашему вопросу, в чем же разница между virtual и  dynamic?
Так вот, когда мы объявляем в родительском классе метод как virtual, при создании потомка этого класса для него (потомка) так же будет создан этот метод в таблице методов в памяти. Это хорошо если данный метод перекрыт в потомке и нам в любом случае он необходим в памяти. А если метод не перекрыт? Зачем нам идентичная копия метода? А если у нас реализована цепочка наследования из 10 классов и только в одном из наследников этот метод перекрыт, то зачем нам создавать 10 копий одного и того же? Проще говоря, при объявлении в классе метода как virtual, в каждом наследнике этого класса будет создана копия этого метода вне зависимости от того перекрыт этот метод или нет. Что нам это дает? Скорость вызова. Каждый класс имеет свою копию метода и может быстро его вызвать, не тревожа при этом своего родителя, родителя своего родителя и так далее по цепочке. Минус - занимаемая память.
Если же мы объявим в родительском классе метод как dynamic, то он не будет создаваться в наследниках, если наследники его не перекрывают. Таким образом, даже если у нас реализована цепочка наследования из 10 классов, такой метод будет в памяти только в одном экземпляре. Что нам это дает? Экономия памяти. Минус - скорость вызова. Дело в том, что если мы пытаемся вызвать такой метод у наследника, он пытается найти этот метод в своей таблице методов - не находит. Пытается найти этот метод в таблице методов своего родителя - не находит. И так далее по цепочке наследования. На это тратится время.

Что мы имеем в итоге?
  • Если у вас длинная цепочка наследования и метод, который редко вызывается, используйте dynamic. Этим вы сэкономите память в ущерб скорости в вызова, что на редко вызываемых методах будет не существенно. Именно по этой причине большинство методов в VCL объявлены как dynamic.
  • Если у вас длинная цепочка наследования и метод, который вызывается часто, оценивайте необходимость быстрого вызова. Если это не критично, используйте dynamic. Если быстрый вызов важен несмотря на занимаемую память, используйте virtual.
  • Если у вас не большая цепочка наследования, используйте virtual. Экономия памяти при использовании dynamic в таком случае будет минимальна.
Другими словами, dynamic - метод создается в памяти один раз, но для его доступа необходимо пройти по всей цепочке наследования. virtual - метод копируется для каждого наследника. Если вам не важна используемая программой память, но очень важна скорость выполнения методов - virtual. Если скорость не существенна, а вот память хочется сэкономить - dynamic.

26.09.2013

Приватные (private) секции видимости класса

Хотелось бы начать свой блог с освещения вопроса, с которым я часто сталкиваюсь при изучении кода, написанного начинающими программистами. Как известно, при создании класса в нашем распоряжении есть так называемые уровни инкапсуляции, в народе более известные как области видимости (хотя на самом деле понятие "область видимости" немного иное и касается переменных). И если с уровнями public, protected и published все достаточно ясно, они ведут себя в точности как и называются, то уровень private не так и прост. Как переводится private? Частный, личный, тайный, закрытый. Многие начинающие программисты так и относятся к методам, описанным в этой секции. А что мы умеем на самом деле? Справка Delphi XE5 (актуальная на сегодня) нам говорит следующее:
A private member is invisible outside of the unit or program where its class is declared. In other words, a private method cannot be called from another module, and a private field or property cannot be read or written to from another module. By placing related class declarations in the same module, you can give the classes access to one another’s private members without making those members more widely accessible. For a member to be visible only inside its class, it needs to be declared strict private.
В общем все написано как и есть. Но вот беда, люди начинают изучать программирование совершенно не зная английского, а если и знают, то на недостаточном уровне что бы свободно читать справку (чего таить, я сам таким был). Что они делают? Они ищут русскоязычную информацию. А русскоязычные справочники в большинстве своем говорят нам следующее:
Директива Private начинает раздел данных (полей) и подпрограмм (методы) класса, которые являются частными (внутренними) для этого класса. Это - жизненно важная часть Объектно-ориентированной концепции, в которой класс рассматривается как черный ящик - то, что используется внутри не уместно для внешнего использования. Указанные в частном (Private) разделе данные и подпрограммы используются другими подпрограммами только данного класса.
Если частные данные нуждаются во внешнем доступе, то свойство public (или published) предоставят данный доступ.
Частные (Private) данные и подпрограммы не доступны даже потомкам класса, чтобы обеспечить этот доступ вы должны использовать Protected. Защищенные (Protected) данные и методы внешне невидимы, но доступны для всех классов в иерархии.
Данный текст был взят с русскоязычного справочника функций и процедур как пример, но подобное я встречаю практически во всех русскоязычных обучающий материалах.

Что мы имеем в итоге?
Русскоязычные начинающие программисты неверно трактуют секцию private, считая ее полностью защищенной от вызовов за пределами методов класса. Что бы людям, которые плохо знают английский и изучают программирование по русскоязычным материалам, стало понятно о чем я пишу, давайте переведем официальную справку на русский (перевод мой личный и не дословный, а отражающий суть написанного):
Все что указано в private секции класса невидимо за пределами модуля или программы, где этот класс объявлен. Другими словами, метод, объявленный в private секции не может быть вызван из другого модуля, поля и свойства, объявленные в private секции, не могут быть прочитаны или установлены из другого модуля. Размещая объявление классов в одном модуле, Вы можете иметь доступ к private секции всех классов этого модуля, не делая эту секцию широко доступной. Для объявления секции, видимой только внутри данного класса, необходимо использовать strict private.
Думаю теперь видны отличия русскоязычных справочников и официальной информации. Если русскоязычные справочники делают акцент на то, что private секция полностью изолирована от внешнего мира, то в официальной информации четко сказано, что изоляция происходит только за пределами модуля, а все что внутри модуля может иметь полный доступ к private секции. Так же озвучено, что если нужна действительно полная изоляция (именно та, а которой пишется в русскоязычных справочниках), то нужно применять strict private.

Чего так получилось? 
Для начала стоит заметить, что объявление секции как strict private появилось только в Delphi 2007, а большинство книг и справочников на русском языке написаны в далекие времена господствования Delphi 7. Т.е. когда писались эти книги и справочники, еще не было введено конструкции strict private, авторы не задавались вопросом: "А в чем же разница?". Еще одной причиной может быть перевод самого слова private и недопонимание авторов русскоязычных материалов всех нюансов и тонкостей языка Delphi.

Какие из всего этого можно сделать выводы?
  1. Доверяйте только официальным источникам! Даже если Вы не знаете английский, воспользуйтесь сервисами он-лайн перевода, суть вам будет понятна.
  2. Старайтесь объявлять каждый класс в отдельном модуле (для версий Delphi ниже 2007) или пользоваться конструкцией strict private. Так вы сможете гарантировать что private секция действительно будет приватной и никто посторонний не имеет к ней доступа.
  3. Используйте информацию, которая касается именно той версии Delphi, с которой Вы работаете. Использование устаревших данных может быть опасно и ввести в заблуждение.
О том, почему нужно серьезно относится к скрытию методов, полей и свойств, я напишу в следующих публикациях.

25.09.2013

Begin

Приветствую всех читателей. Да, так уж вышло, что захотелось мне создать площадку для своих мыслей, посвященных любимому мной языку Delphi. Я прекрасно знаю о количестве подобных блогов на просторах рунета, даже являюсь постоянным читателем некоторых из них, но бывают моменты, когда есть мысль и не плохо было бы её куда-то записать. Можно  конечно воспользоваться бумажным блокнотом или сохранить все в текстовый файл, но лучше всего для подобного подходит именно блог.

Я не буду здесь вдаваться в глубины разработки сложных систем и непонятных алгоритмов. Я самостоятельно обучался программированию и не знаю многих научных терминов и понятий, о которых рассказывают студентам профессора в вузах. У меня экономическое образование и программирование было моим увлечением, пока оно не переросло в работу. Поэтому материал, которым будет наполняться этот блог, будет более интересен таким же как я самоучкам. В первую очередь этот блог позиционируется как площадка для хранения моих личных наработок и открытий в программировании. Если кому-то еще они будут интересны, то я буду только рад этому.