воскресенье, 1 марта 2015 г.

Как обоблачиться с YandexDisk - 2.1 Подключение ЯД к Windows

Данная статья является продолжением предыдущей

Часть II. Практика

§2.1. Подключение ЯД к Windows


Часть I. Теория
2.1 Подключение ЯД к Windows
2.2 Создание папки приложения
2.3 Indy и HTTPS
2.4 Скрипач как прокси
2.5 Протокольные компоненты, часть 1. TidHTTP и JSON
2.5 Протокольные компоненты, часть 2. TREST~ и TDataSet

Начнём боевые действия с заброски десанта в тыл противника. Т.е. выложим файлы с картинками на ЯД. Если вы уже установили приложение ЯД, то у вас уже есть доступ к ЯД через проводник. Если же вы не стали загружать приложение от Яндекса, то пора пойти в Проводник, открыть меню Сервис, и выбрать команду "Подключить сетевой диск".



Если в вашем проводнике не оказалось меню Сервис, то вы скорее всего, счастливый обладатель новейшей OS MS-Windows 8.x. Тогда ищите Мой Компьютер и жмите правую кнопку - там "Подключить сетевой диск"  должна быть. Впрочем, обладателям Win7 этот способ также доступен.


В открывшемся диалоге выбираем "Подключение к сайту...". Далее шагая по мастеру-волшебнику мы выбираем сетевое размещение. У меня это единственный пункт. У вас, если судить по характеру окна, вероятно, могут быть другие варианты. В таком случае Вы уж там сами подумайте. Я же всегда безальтернативно дохожу до места, где надо ввести "Сетевой адрес или адрес в Интернете" - https://webdav.yandex.ru. Имя ресурсу я дам "Prikol". Но вам, возможно, захочется чего-то более общего, т.к. в дальнейшем этим ЯДом будут пользоваться и другие ваши приложения.






Часть I. Теория
2.1 Подключение ЯД к Windows
2.2 Создание папки приложения
2.3 Indy и HTTPS
2.4 Скрипач как прокси
2.5 Протокольные компоненты, часть 1. TidHTTP и JSON
2.5 Протокольные компоненты, часть 2. TREST~ и TDataSet

Как обоблачиться с YandexDisk - 2.2 Создание папки приложения

Данная статья является продолжением предыдущей

Часть II. Практика

§2.2. Создание папки приложения



Часть I. Теория
2.1 Подключение ЯД к Windows
2.2 Создание папки приложения
2.3 Indy и HTTPS
2.4 Скрипач как прокси
2.5 Протокольные компоненты, часть 1. TidHTTP и JSON
2.5 Протокольные компоненты, часть 2. TREST~ и TDataSet

Теперь мы видим ЯД у себя в Проводнике, и тут же возникает вопрос: а где та самая папка приложения, в которой будут расположены файлы приложения? Не волнуйтесь, папка появится, как только вы начнёте работать от имени вашего приложения, для чего у нас уже есть токен. И чтобы им воспользоваться открывать Делфи, создавать форму, кидать на неё компоненты, устанавливать параметры и вызывать методы - вовсе не обязательно. Если кто-то не в курсе, что в наборе инструментов Delphi есть REST-Debugger, то тому пока можно так и оставаться в неведении. Для тренировок Яндекс прямо в браузере предлагает нам свой собственный REST-отладчик, который называется "Полигон": https://tech.yandex.ru/disk/poligon/

Полагаю, вы сразу найдёте поле, куда вставить свой токен. А чтобы выполнить запрос к папке приложения надо раскрыть раздел "/v1/disk/resources : Файлы и папки" и подраздел "GET /v1/disk/resources Получить метаинформацию о файле или каталоге".


Вот здесь мы и укажем имя своей папки - "app:/". И сразу же можно нажать "Попробовать!".


Вуаля! - папка приложения готова. Можете заглянуть в Проводник и нажать F5 для обновления - всё есть. Осталось разложить по подкаталогам ту кучу художественных образов, которые мы собираемся использовать в нашем приложении.

to be continued...

Часть I. Теория
2.1 Подключение ЯД к Windows
2.2 Создание папки приложения
2.3 Indy и HTTPS
2.4 Скрипач как прокси
2.5 Протокольные компоненты, часть 1. TidHTTP и JSON
2.5 Протокольные компоненты, часть 2. TREST~ и TDataSet

четверг, 19 февраля 2015 г.

Как обоблачиться с YandexDisk

Эту статью я написал потому, что сам очень хотел прочитать такую - о сервисах Яндекса, авторизации OAuth, о том, как держать бубен, чтобы коробочный Indy выполнял запросы по HTTPS. Это всё не сложно на самом деле, но прочти я такую статью раньше - глядишь, и если бы не одним седым волосом в моей бороде было бы меньше, то уж наверняка одним бы волосом на моей макушке - больше. 

Часть I. Теория

2.1 Подключение ЯД к Windows
2.2 Создание папки приложения
2.3 Indy и HTTPS
2.4 Скрипач как прокси
2.5 Протокольные компоненты, часть 1. TidHTTP и JSON
2.5 Протокольные компоненты, часть 2. TREST~ и TDataSet

YandexDisk (далее - ЯД, в хорошем смысле) может служить не только в качестве личного облачного хранилища, но и выступать как источник данных для вашего приложения.  Если вам нужно просто иметь доступное отовсюду место, куда можно заглянуть на предмет наличия каких-либо новостей, то бесплатное облако Яндекса может вполне удовлетворить ваши потребности.


В качестве примера давайте сделаем по следам Праздника Влюблённых (или в преддверии Международного Женского Дня) приложение для украшения фоток. Возьмём фотографию с камеры или из галереи, а потом налепим на неё каких-нибудь прикольных штучек. Например, пусть это будут сердечки, цветочки, звёздочки, очки, усы, рамки, буквы... Вот, видите - ассортимент может быть самый разнообразный. Не обновлять же всякий раз приложение, когда мы творчески разродившись добавим в нашу коллекцию приколов новую фишку? Значит, надо дать приложению возможность скачивать эти картинки по мере их создания.

Чтобы построить наше  фишкохранилище давайте заведём аккаунт на ЯД(https://disk.yandex.ru).

Думаю, ввести своё имя и телефон вы сможете без подсказок. Что касается предлагаемого Яндексом клиента для Windows, то тут советую подумать - нужно ли вам, чтобы каждый раз, когда вы сделаете копию экрана или воткнёте в телефон шнурок, выскакивало окошко с просьбой забрать у вас свеженький контент. Меня лично такая настойчивая забота несколько отпугивает. А подключить ЯД к проводнику Windows можно и другим, менее "дружественным" способом - через "Сервис->Подключить сетевой диск". Об этом позднее.

Сейчас же подумаем, как нам дать приложению доступ к данным. Разумеется, в Яндексе об этом уже подумали за нас - они предоставили нам программный интерфейс. И даже не один.


Ну, во-первых, давайте не будем рассматривать "внешние ссылки", т.к. они на самом-то деле ведут не к требуемому файлу, а на страницу скачивания, где искать уже ссылку на исходный файл - задача архисложная для слишком больших любителей. Мы пойдём другим путём(© Владимир Ильич Ульянов) - API Диска.

API Диска предлагает Виджеты Диска, REST API Диска, SDK Диска и WebDAV API Диска. Виджеты нам в приложении вряд ли помогут, SDK (основан на WebDAV) для Delphi пока не сделали, а выбор между REST и WebDAV - однозначно в пользу REST, т.к. о REST слышал даже самый ленивый менеджер проектов, а рассказывать на собеседовании, что вы освоили какой-то "Web-Dove" - ну, мало ли уже в компании всяких Web-разработчиков и Web-дизайнеров, так что не будем путать людей.

Естественно, это  всё шутки. А на самом деле я исхожу из того, что при определении разрешённых действий REST-ЯД даёт возможность ограничить доступ к диску рамками папки приложения. Если учесть, что к одному номеру телефона Яндекс разрешает привязывать только 3 аккаунта, то ограничение приложения своей папкой - весьма значительный фактор. Так что, выбор - очевиден.

Ну что ж, решив, в каком направлении будем двигаться, начинаем. Первым пунктом нам предлагают получить для своего приложения OAuth-токен. OAuth (Open Authorization, я так думаю ©Рубен Вартанович Хачикян) - это что такое? Это когда нам надо получить куда-то доступ, а пароль за нас вводит кто-то другой. Это весьма странная штука, найти аналогию которой в реальном мире довольно затруднительно.



Двигаясь в направлении получения токена не будем тратить время на изучение документации (её читают только от безысходности) и сразу идём регистрировать приложение, где заполняем анкету, в которой обозначим желаемые ресурсы. Их тут, оказывается, довольно много - не только ЯД. Так что мы можем пожелать одновременно и что-то ещё - например, Я-Фотки. Кстати, я обнаружил, что у Яндекса всё ещё нет своего приложения для Я-Телепрограмма - надо как-нибудь потом обязательно сделать.


После регистрации у нас появится ещё не токен, а только идентификатор приложения и плюс к нему - пароль. В этот момент могут возникнуть вполне логичные сомнения: а нужен ли ещё какой-то токен, если уже есть и логин(айди) и пароль? Не одно ли это и то же - этот айди и тот токен?

А где же токен? Вернёмся на шаг назад (кнопка "Редактировать") и ещё раз глянем в низ анкеты - там ясно написано, что будет токен, время жизни которого "Не менее, чем 1 год"!


   
Вот, кажется, и пришла пора ознакомиться с документацией, в которой какие-то схемы взаимодействия, УРЛы обратного вызова и много-много букв. Ну-ну-ну, не будем отчаиваться! Давайте просто немного подумаем, прикинем, что должно происходить в процессе, и наверняка разберёмся без лишних страданий.

Итак, как уже было замечено, айди - это не просто номер приложения, а обозначение списка ресурсов. Причём сейчас стало совершенно очевидно, что ресурсы принадлежат не приложению, а пользователю. Т.е. чтобы добраться до ресурса, нужен логин и пароль пользователя. А логин и пароль пользователя - это доступ не к ресурсам из списка, а ко ВСЕМ ресурсам пользователя, это ПОЛНЫЙ доступ ко всем ресурсам, включая их администрирование. Поэтому, если чисто конкретно для своего аккаунта мы можем вписать в своё приложение логин и пароль, то ждать от стороннего пользователя, чьими, скажем, фотками (только фотками и ничем больше!) мы хотим воспользоваться, что он для этого станет вводить нам в диалог свои входные данные - это, мягко скажем, слишком большие ожидания.

И как же тогда быть? Или точнее - как же они это делают?

"Они" вызывают на помощь Web-интерфейс сервера авторизации Яндекса (или того, кого им нужно), отправив ему при вызове в качестве параметра идентификатор приложения. Да, да, вот так в маленьком окошке браузера открывается Web-интерфейс, где Яндекс показывает  повешенные на идентификатор при регистрации приложения хотелки относительно ресурсов пользователя, а пользователь, видя знакомый логотип в окружении привычных красок и текстов, доверчиво вводит свои учётные сведения и, ознакомившись со всем списком желаний приложения, разрешает (или нет) приложению своими ресурсами пользоваться. 

Да вы наверняка сами часто видели такие окошки, когда какая-нибудь "Забавная ферма" или "Джонни-попрыгунчик" просят вас поделиться своими успехами где-нибудь в FaceBook® или в Одноклассниках®. И, если вы заметили, часто даже и пароль вводить не нужно - обычно он уже давно лежит в куках.
{Здесь должна быть картинка из "Забавная ферма" или "Джонни-попрыгунчик" и авторизация кого-нибудь в чём-то из, но я не играю ни в то, ни в другое, и поэтому у меня нет такой картинки.}
У Яндекса это окошко вызывается УРЛом https://oauth.yandex.ru/authorize?response_type=token&client_id=<идентификатор приложения>[&display=popup][&state=<произвольная строка>]. Добавка display=popup - это чтобы без навигации Яндекса и прочего дополнительного контента. Такой минимализм изначально предназначался для небольших экранов, но на самом деле, я считаю, для диалога в приложении по-другому и быть не должно. Параметр state - это придумываемая автором приложения метка, которая вместе с выдаваемым токеном пойдет по тому УРЛу, что мы задали во время регистрации приложения. Для Web-приложений может быть очень удобно указать свою точку входа, общую для нескольких идентификаторов, и по параметру state судить о том, какое приложение сейчас получило токен. 


Поскольку у нас не Web-приложение, а нормальное настоящее, то нам надо показывать форму с компонентом TWebBrowser, у которого при старте следует вызвать Navigate(<тот самый УРЛ>). Когда нажимается "Разрешить" и уходит POST с формы, обратно возвращается 302 - Temporary moved с "новым" Location https://oauth.yandex.ru/verification_code#access_token=<желанный OAuth-токен>&token_type=bearer&expires_in=31536000. Разумеется, браузер тут же попытается открыть эту страничку, где будет - что? - правильно, наш токен, аккуратно написанный на милой страничке  красивым почерком. Конечно, страничку грузить не обязательно, а достаточно повеситься на событие OnBeforeNavigate2 и вырезать параметр access_token=. Всё!

А вы знаете, что форма с браузером уже есть в готовом виде? Она лежит  в  $(BDS)\Source\data\rest в виде двух модулей REST.Authenticator.OAuth.WebForm - .Win и .FMX. Как ею пользоваться - в примерах Object Pascal\Database\RESTDemo\RESTDemos.dpr. В форму добавлена нерусская кнопка Close, событие редиректа почему-то отлавливается с двух событий - Before и After, а сам обработчик поиска токена в УРЛе в комплектацию формы не входит. Так что, если надумаете пользоваться, советую скопировать модуль с формой в папку проекта и подшаманить, заглядывая в RESTDemos.

А как же компоненты с такими интересными названиями - TOAuth1Authenticator и TOAuth2Authenticator? Увы, господа, эти компоненты ничего не делают. Ну, то есть не то, чтобы совсем ничего, но, скажем так, ничего, что приводит к вызову той самой аутентификации в окошке. Это всего лишь пристёжка к TRESTClient, которая добавляет в параметры уже полученный ранее  OAuth-токен. Често говоря, мне кажется, что делать для этого компонент - как из пушки по воробьям, проще самому добавить параметр ручками. Но тут играют роль общеконцептуальные соображения, т.к. аутентификация может быть не только OAuth.

Ну, вот. Откуда токен - мы знаем: из УРЛа с идентификатором приложения. Что такое OAuth-токен - мы тоже уже понимаем: токен - это идентификатор связки прав доступа с ресурсом пользователя. Срок действия - 31536000 секунд - позволяет нам зашить этот токен в приложение, и уже безо всяких особых форм с браузерами просто работать с Яндексом через REST или другой API.

--------------------------------------
Чтобы найти своё приложение я захожу на ЯД, выбираю "Разработчикам", "REST API Диска", "OAuth-токен" и тычу в. Это почему-то кажется мне проще, чем просто набрать oauth.yandex.ru.



2.1 Подключение ЯД к Windows
2.2 Создание папки приложения
2.3 Indy и HTTPS
2.4 Скрипач как прокси
2.5 Протокольные компоненты, часть 1. TidHTTP и JSON
2.5 Протокольные компоненты, часть 2. TREST~ и TDataSet

пятница, 30 января 2015 г.

Как я боролся с iOS 8.1.3

Давно я не брал в руки йОС, но давеча мне говорят: "ну-ка, покажи нам на айпаде пару новых идей"! Я говорю: "сей момент"! И показываю. А потом, пока я ещё радостный и добрый, айпад тихо так говорит, что хочет чего-то новенького - обновиться желает. И я широким жестом разрешаю, а мне - оп-па! - и геморрой хидэйк на сутки - Unable to install package. (e800003a). и Unable to install package. (e8008016).

Ребята, кому есть до этого дело:
https://www.danielwolf.eu/blog/2015/1458-delphi-mobile-und-ios-8-1-3
http://blogs.embarcadero.com/ao/2013/05/24/39472

четверг, 22 января 2015 г.

Как хранить числа в объектах TStringList

Как хранить числа в объектах TStringList прекрасно известно: MyStrings.AddObject( MyString, TObject( MyNumber ) )...казалось бы. Однако, мне (да вот - дошла очередь и до меня) пришлось убедиться, что к словам "прекрасно известно" следует добавить слово "было" - прекрасно известно было. Увы, двадцать лет работавший в Win32 и даже в Win64 способ теперь уже не работает. Точнее, не работает на мобильных платформах, т.е. там, где действует незримая власть коварного AUTOREFCOUNT aka ARC.

Я наступил на эти грабли (а читать книжки - для чайников), когда в мобильном приложении надо было выполнить две простые задачи. Первая - заполнять два связанных комбобокса: заполняешь одно поле, и если второе ещё не задано, то взять для него значение по-умолчанию. Вторая задача - красить элементы списка в зависимости от связанного значения - красный, жёлтый, голубой -  выбирай себе любой.


Цвет идёт к справочнику в виде строки #rrggbb - тут StringToAlphaColor() сразу в руки. Получаем бинарное значение TAlphaColor, которое на самом деле = type Cardinal, т.е. то, что отличнейшим образом укладывается в AddObject() рядом со строчкой из справочника. Теперь, получая данные, я легко в справочнике найду соответствующий цвет: 'критично'-#ff0000, 'неважно'-#cccccc, и т.д. Один раз цвета распознал, а потом пользуйся сколько влезет. Только обязательно надо не забыть включить для цвета канал Alpha (например, Result := TAlphaColorRec.Alpha + AColor), а то нарисованное будет настолько прозрачным, что его станет совершенно невозможно разглядеть.

Связка справочников - тоже штука банальная. У меня в одном из двух справочников дано дефолтное значение из второго. Надо при заполнении TStringList просто бежать по первому списку и искать в предварительно заполненном втором IndexOf() и класть в объекты первого списка найденные индексы, а текущий счётчик класть, разумеется, в объекты второго списка с этими индексами. Тут, правда, есть нюанс: нулевое значение nil, которым инициализируются объекты списков, будет в виде целого числа равно нулю, что означает не пустое значение, а индекс первого элемента. Надо быть внимательнее - ставить -1 везде, где только можно, или при сохранении найденных индексов добавлять единицу, а потом об этом надо не забывать, и оперировать уже с отниманием единицы. Я лично предпочитаю второй способ - точно не нарвёшься на случайный ноль.


Не долго думая, я сделал прототип на Win32. И всё у меня прекрасно заработало: метки красились нужным цветом, словари дружно связывались. Ура! Осталось только скомпилить под Андроид, который требовался Заказчику, и ... Вот тут-то и обнаружилось, что связки не связываются, а цвета не цветут. Как же так?

Чтобы не рвать куски из проекта, возьмём надуманный пример. Создадим проект пустого мобильного приложения, добавим в дереве проекта платформу Win32 и вставим на событие OnCreate главной и единственной формы код, что дан ниже (имя формы только подкорректируйте под себя). Ах, да - не забудем ещё бросить на форму TMemo c Align = alClient ( все эти манипуляции читатель может делать мысленно ).

const
  Labels: array [1..5] of String = ('Lorem','ipsum','dolor','sit','amet');
 
procedure TForm6.FormCreate(Sender: TObject);
var
  I: Integer;
begin
  with TStringList.Create do
  try
    for I := Low(Labels) to High(Labels) do
      AddObject( Labels[ I ], TObject( I ) );
    Sort;
    for I := 0 to Count-1 do
      Strings[ I ] := Format( '%s=%d', [ Strings[ I ], Integer( Objects[ I ] ) ] );
    Memo1.Lines.Text := Text;
  finally Free;
  end;
end;

Ожидаемый результат получен:


А вот Андроидная версия скорее всего при запуске выдаст чёрный экран, упёршись в первую встречу с AddObject(). Не будем разбирать на части коробку передач, а удовлетворимся фактом, что нейтраль - тут, а чтобы ехать - там. Ясно, что использовать непонятные адреса для объектов, когда тут и там ссылки считают - это как ходить по коровьему пастбищу с закрытыми глазами.

А у меня в проекте этот фокус прошёл. Я поназаполнял словарей, развесил там индексы и цвета и пошёл дальше читать уже данные. И только когда стал доставать из закромов все свои богатства, то обнаружил сплошные nil. И сильно удивился - мыши съели?! - Так нет здесь мышей! Ну да говорят, что удивляться - это даже полезно.

Что делать? Проект - вон он уже дышит и играет мускулами! Не будешь же перетрахивать (© Лукашенко А.Г.) его из-за какой-то мелочи. Сейчас дадим таблеточку, и будет как здоровый!

type
  TData<T> = class
    FValue: T;
    constructor Create( const AValue: T );
  end;
 
const
  Labels: array [1..5] of String = ('Lorem','ipsum','dolor','sit','amet');
 
procedure TForm6.FormCreate(Sender: TObject);
var
  I: Integer;
begin
  with TStringList.Create( True ) do
  try
    for I := Low(Labels) to High(Labels) do
      AddObject( Labels[ I ], TData<Integer>.Create( I ) );
    Sort;
    for I := 0 to Count-1 do
      Strings[ I ] := Format( '%s=%d', [ Strings[ I ], TData<Integer>( Objects[ I ] ).FValue ] );
 
    Memo1.Lines.Text := Text;
  finally Free;
  end;
end;
 
{ TData<T> }
 
constructor TData<T>.Create(const AValue: T);
begin
  FValue := AValue;
end;

Просто? Да! Мне даже как-то понравилось - появилось ещё "пустое значение": nil=Null. Так что и игры с плюс-минус единицей для индексов можно выкинуть. А если, например, нужно Альфа-канал цвета устанавливать по месту, то уже ни за что не спутаешь чёрный цвет с прозрачным - 0 уже не равен nil! И кода лишнего - с гулькин нос. Главное - при создании TStringList включить  параметр OwnsObjects, чтобы объекты прибивались самим списком, и всё. Красота!


...............

Конечно, старики в стойбище ворчат, что на новой мягкой земле уже нельзя просто оставить пойманную рыбу, как раньше на льду, а надо держать в садке, привязанном верёвкой к колышку, чтобы рыба не стухла, или не стащил кто. И чтобы оттуда достать рыбу, надо теперь этот садок вытягивать из воды, лезть в него рукой и там шарить. Как раньше хорошо было! А теперь таскайся с этой штукой. Это же, конечно, ещё и денег каких-то стоит!  Ох уж эти новые края с их климатом! Как тут другие люди живут?...

вторник, 20 января 2015 г.

Как несовершенны константные аргументы

Я тут попал в такие чудеса на виражах, что уже хотел застрелить компилятор и повеситься на шею кому-нибудь с жилеткой, чтобы рыдать аки дитя малое. Меня обидел компилятор! И причём не где-нибудь, а в Win32!!! Ну, посудите сами - он ссылки на строку посчитать не может, и втихаря вместо одной переменной, присваивает значения двум! Вот сейчас расскажу как.

Есть у меня процедура со строкой в аргументе. Она слегка чистит листбокс и потом по аргументу заливает в список новые элементы. Вызывается из разных мест. В том числе по клику на списке, где  в качестве аргумента в ту процедуру передаётся строковое свойство текущего элемента.

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

Полазил я в CPU на предмет совпадения адресов - но это так, чисто посмотреть с укором в подкапотное пространство. А дай, думаю, присвою той локальной переменной значение аргумента. Присваивается, идёт дальше, и вот в момент "волшебного" присвоения - Бах! - неверная операция с указателем! И только тогда, когда аргумент не пуст.

Стал я думать-гадать-искать и понял, что переданная мной непустая строка принадлежит тому элементу списка, который убивается внутри процедуры перед присвоением нового значения локальной строке. А константные аргументы как передаются? - А в том числе и по ссылке! И видимо, поскольку я строковое свойство кладу в неизменном виде, как раз такой вариант развития событий и был выбран компилятором. И выходит, что в магический момент ссылка уже битая, и совпадает как-то хитрослучайно точно с адресом локальной переменной. 

Это просто счастливое совпадение, что мёртвая ссылка точно легла на локальную переменную! 

Лечение прошло банально - передачей параметра по значению. Но каково?! Как компилятор не отследил, что область памяти занята процедурой?! 

К сожалению, я не смог воспроизвести ситуацию на простом примере. И если кто-нибудь понял что за оптимизация такая у компилятора происходит, то пусть попробует создать пример в пробирке для quality.embarcadero.com. А я свой рассказ заканчиваю. Спасибо за внимание!

Как побег лямбды из цикла удивляет компилятор

Это такая шутка - заставить компилятор выдать [dcc32 Fatal Error] InternalError.dpr(15): F2084 Internal Error: C22972.

Попробуйте написать в цикле лямбда-выражение, в котором есть оператор управления циклом - break или continue. Причём не важно, какой цикл - for, while, repeat. Компилятор от этого не умрёт, но и сказать ничего умного не сможет.

program InternalError;
{$APPTYPE CONSOLE}
begin
  while True
  (procedure begin
    break
  end)();
end.
 
Так ему! Пусть не думает, что умнее паровоза! Пусть выучит и повторяет: BREAK or CONTINUE outside of loop!

..........................

Не, ну что это я так на него в самом деле? Всё-таки он не совсем крэшанулся, не сбил Дэлфу, не открыл синий экран - тоже своего рода утешение. Ну, в конце-то концов он же понял, что тут что-то не так! И отреагировал, как уж смог.

Ну, ладно. Иди сюда мой хороший, давай я тебя обниму. Ну, что ты, что ты? Расстроился? Ну, ничего, не плачь, всё будет хорошо... А вот мы сейчас с тобой ещё одну программку покомпилим, хорошую программку... А та програмяка - бяка! Фу, брось её! Пусть там на quality.embarcadero.com с ней разбираются, а нам она не нужна, у нас и так программок много. Пойдём, моя радость!

суббота, 17 января 2015 г.

Как я стал недоверять try-except

Я уже имел возможность убедиться, что try-except - жутко тормозная штука. Но я всё ещё думал, что это штука надёжная. По крайней мере, она никогда не подводила меня в мире Win32. И вот я пошёл с этими представлениями в мир мобильных приложений. И пришёл к печальному выводу - try-except работает ещё и отнюдь не всегда. Увы, "кто умножает познания, умножает скорбь"/широко известный источник/

Вот пример, где я пытался замерить скорость работы try-except при попытке доступа к неассигнованным объектным переменным:

procedure TForm5.Button1Click(Sender: TObject);
var
  dt: TDateTime;
  I: Integer;
  A: array[ 0..9 ] of TStringList;
 
  function V: Integer;
  begin
    try
      Result := A[ I mod 10 ].Count;
    except
      Result := 0;
    end;
  end;
  function V2: Integer;
  var
    Index: Integer;
  begin
    Index := I mod 10;
    if Assigned( A[ Index ] ) then
      Result := A[ Index ].Count
    else
      Result := 0;
  end;
var
  Summ: Integer;
begin
  FillChar( A, SizeOf( A ), 0 );
  for I := Low(A) to High(A) do
  if Odd( I ) then
    A[I] := TStringList.Create;
 
  dt := Now;
  try
 
    Summ := 0;
    for I := 0 to 1000000 do
    Summ := Summ + V2;
 
    ShowMessage( IntToStr( Summ ) + ': ' + FormatDateTime( 'hh:nn:ss:zzz', Now-dt ) );
 
  finally
    for I := Low(A) to High(A) do
    if Odd( I ) then
      FreeAndNil( A[I] );
  end;
 
end;

Сначала я пробую цикл с if Assigned(... - результат мгновенный: 0: 00:00:00:006. Теперь меняю Summ + V2 на Summ + V, и...


!!! Что же это за кака-бяка такая!!! И как дальше жить? Теперь, выходит, кроме создаваемых в коде исключений, отлавливать ничего не получится? Тоже не верно - перехватчик самого приложения всё-таки отработал, выдал сообщение, корректно завершил метод и даёт таким образом нам возможность нажимать на кнопку сколько угодно раз и сколько угодно раз любоваться этим "Access violation at address ..., accessing address 00000000." Но где это работает, а где нет? Где играть, где не играть, где рыбу заворачивать? Ы-ы-ы-ы-ы...


ЗЫ: Спасибо Владимиру Натановичу Винокуру за певца Григория Долголоба, служащего в Чугуевской филармонии.

Как лучше брать поля объектных полей? Или Assigned() VS try-except.

Как лучше брать поля объектных полей? Что предпочесть - ясный и простой код с исключением в стиле Delphi, или проверки с повторным чтением объекта или использованием  промежуточных значений в стиле Pascal? Вот пример:

procedure TForm5.Button1Click(Sender: TObject);
var
  dt: TDateTime;
  I: Integer;
  A: array[ 0..9 ] of TStringList;
 
  function V: Integer;
  begin
    try
      Result := A[ I mod 10 ].Count;
    except
      Result := 0;
    end;
  end;
  function V2: Integer;
  var
    Index: Integer;
  begin
    Index := I mod 10;
    if Assigned( A[ Index ] ) then
      Result := A[ Index ].Count
    else
      Result := 0;
  end;
var
  Summ: Integer;
begin
  FillChar( A, SizeOf( A ), 0 );
  try
for I := Low(A) to High(A) do if Odd( I ) then A[I] := TStringList.Create;   dt := Now;   Summ := 0; for I := 0 to 100000 do Summ := Summ + V;   ShowMessage( IntToStr( Summ ) + ': ' + FormatDateTime( 'hh:nn:ss:zzz', Now-dt ) );   finally for I := Low(A) to High(A) do if Odd( I ) then FreeAndNil( A[I] ); end;   end;

Это событие нажатия на кнопку,  в результате работы которого у меня на экране компьютера появляется сообщение с такими цифрами: 0: 00:04:41:248(*). Т.е. около пяти минут на 100 тыс. записей. Много ли это? Скажу только, что изначально я ставил 1 миллион, и просто замучился ждать.

И вот, вместо Summ + V я ставлю Summ + V2, запускаю, жму кнопку... Оп! И 0: 00:00:00:002!


!!! Так вот как должен работать процессор с восемью ядрами на частоте 4 ГГц! И миллион операций ведёт к увеличению времени до 0.021 секунды. И 10 миллионов - лишь малая доля секунды...

Красивый, простой как 2 копейки код с try-exсept против жуть какого старого, извилистого, но всё ещё очень и очень доброго if... Если кто-то опять мне будет говорить, что новее - значит лучше, то скорее всего, я снова ему не поверю.

Кстати, надо погонять это дело на телефоне...

Примечание *: Такое ужасно время получено из-за запуска по F9 - с отладкой. Для такого рода замеров следует запускать Shift-Ctrl-F9, т.е. без отладки. Без отладки время ф-ии V будет уже 0,677 секунды, что ни в какое сравнение не идёт с 5-ю минутами. Однако, это всё же более, чем в 300 раз дольше варианта с V2. И к тому же совершенно очевидно, что отладка программы, где происходит исполнение такого рода кода, может стать весьма утомительным занятием. Спасибо Alexey Kazantsev за замечание по поводу отладчика. 

пятница, 26 декабря 2014 г.

Как нарисовать крестик в середине TImage


Суть даже не в крестике, а как рисовать в нужном месте. Координаты? Да, слыхал. И рисовал как бы даже... Но стоило с MSWindows перейти на Android - и "Шеф, всё пропало, все пропало!"(© Козодоев Г.П.)

Вот пример, замечательно работающий в Винде, но не работающий на моём Nexus 4:
const
  Ident = 10;
... 
      DrawLine( //Horizontal
        TPointF.Create( Ident, ( FRawBitmap.Height / 2 ) ),
        TPointF.Create( FRawBitmap.Width - Ident, FRawBitmap.Height / 2 ), 0.5 );
      DrawLine(  //Vertical
        TPointF.Create( FRawBitmap.Width / 2, Ident ),
        TPointF.Create( FRawBitmap.Width / 2, FRawBitmap.Height - Ident ), 0.5 );

Вот на компе рисует, а в телефоне - нет!

Отладчик говорит, что всё нормально - методы проходят. А сдвиг координат подсказал, что действительно - всё рисуется, но где-то за пределами картинки. Гром и молния! Как может что-то рисоваться за пределами, если мы рисуем в точке с координатами меньше пределов?!

Посмотрев в FMX.Objects как рисует TImage, я среди прочего заметил странную такую штуку - FScreenScale. Не "паблик", не свойство, а чисто "для себя". И берётся оно из свойства TControl.Scene:IScene. А Scene - это, как можно легко заметить, интерфейс, у которого есть даже не свойство SceneScale, а именно метод GetSceneScale. Мало того, интерфейс этот ещё может и отсутствовать, и тогда FScreenScale = 1.

Ещё есть у IScene функции расчёта координат экран-локаль, но они тут не работают, нужен именно  "Scale". Этот Скэйл у меня на ПК - 1, а на Нексусе - 2. Сама же Сцена есть и там, и там. Т.е. вроде бы получается, что проверять её наличие не обязательно (но я себе проверку всё же скопипастил), а нужно взять коэффициент и разделить координаты на него.

Поскольку я много делить не люблю, я разделил один раз, а потом умножаю:
var
  FScreenScale : Single;
... 
  if Assigned(Scene) then
    FScreenScale := 1 / Scene.GetSceneScale
  else
    FScreenScale := 1.0;
... 
      DrawLine( //Horizontal
        TPointF.Create( FScreenScale * Ident, FScreenScale * ( FRawBitmap.Height / 2 ) ),
        TPointF.Create( FScreenScale * ( FRawBitmap.Width - Ident ), FScreenScale * FRawBitmap.Height / 2 ), 0.5 );
      DrawLine(  //Vertical
        TPointF.Create( FScreenScale * FRawBitmap.Width / 2, FScreenScale * Ident ),
        TPointF.Create( FScreenScale * FRawBitmap.Width / 2, FScreenScale * ( FRawBitmap.Height - Ident ) ), 0.5 );

Ну вот, теперь мой крестик ровно в середине рисунка.

- Погоди-ка, а если там всё в два раза больше, то попиксельно ничего не нарисуешь????
- Да нарисуешь, координаты-то не целые!

Только зачем такие странности? Чтобы понять это, как сказал Ярослав Бровин "В общем ничего кроме стандартных знаний линейной алгебры здесь не требуется". И мне остаётся стыдливо воспринимать эти обстоятельства как данность, данную нам свыше.

четверг, 18 декабря 2014 г.

Как сканировать штрих-код


Конечно, баркоды как объект манипуляций программными средствами для меня - не новость, мы эти коды на ПК сто лет в обед как программировали - и печатали, и считывали. А что такого? Вставляешь в разъём клавиатуры считыватель, и вот - оно уже и считывается. А как быть с телефоном или планшетом?

Если с печатью штрих кодов понятно - там какой-то шрифт или ещё как что нарисовать по правилам можно, то считать, распознать - у-у-у - это сложно. Это же математика какая-то, алгоритмы сложные - короче, на коленке не сделаешь.

А что же делать, если вдруг нужно? Конечно, готовые компоненты искать. И желательно - бесплатные :)

Но пойдите на Torry - что там? Там компонентов для распознавания кодов - раз, и обчёлся.

Например, для FMX есть компонент от WINSOFT. Даже с возможностью перед покупкой закомпилить примерчик. Только он у меня не пошёл - на телефоне что-то с синтаксическим нераспознаванием пакета, а на планшете запускается, но умирает - какой-то  libzbar хочет или что-то в этом духе. Я бы подробнее почитал, но не сошёлся же свет на них клином - должно же быть такого добра  (тем более, что и не за бесплатно) - море! Но что-то море это мелкое какое-то...

Я даже и дот-нетов смотрел, и, поскольку мне для Андроида, смотрел, что джависты предлагают - на всё кидался, там может и мне что найдётся. Вот, например, Java4less предлагает за сотню убитых енотов с хвостиком на каждый тип кода (а их не два и не три) отдельный Delphi-компонент. И ещё полторы сотни просит за редистрибуцию! А вот демку скомпилить - не даёт!

Но, долго ли, коротко ли, вариант подходящий отыскался. Вы не поверите! - БЕСПЛАТНАЯ библиотека на сайте - (барабанная дробь) - кто бы мог подумать, кому это в голову только могло прийти?! - EMBARCADERO!!!! (та-дам!!!)

http://cc.embarcadero.com/Item/29811 Там штрих-коды и для Андроида, и для йОса. И плюс ещё там какие-то другие нативные штуки типа всплывающих сообщений Андроида и проверки наличия сетевого подключения. Сборничек небольшой. Небольшой, но в общем интересный.

Да, я стокамногабуков написал, чтобы только ссылку дать. Но я просто рад очень, чес-слово!

Правда, оказалось, что проект был сделан для XE5. Для XE6 пришлось пробежаться по uses и расставить для Android новый юнит Androidapi.Helpers, а старую енумерацию aeBecameActive надо было заменить на TApplicationEvent.BecameActive. И вот - у меня на телефоне (некоторые утверждают, что правильно говорить "в телефоне") уже новое приложение!

Однако, счастье всё ещё никак не давалось в руки так просто. Как только я нажимал на кнопку сканирования - телефон уходил в задумчивость и предлагал приложение остановить. Тоже и планшет. И я тогда пошёл под отладчиком. Странно, но под отладчиком на телефоне у меня даже появилось такое зелёное окошко с видом через видеокамеру телефона. И там даже хелп был, и всё говорило, что сейчас штрих-код считается. Но не считывалось ничего. А казалось, что счастье уже было так близко, так возможно!

И вот - О, Моя Врождённая Чудо-Наблюдательность! - я обнаружил, что требуется пакет "com.google.zxing.client.android.SCAN". Ага! По этому поводу интернет делится на 2 лагеря: одни рассказывают, как пришить этот пакет в приложение, а другие, коих, как мне показалось, большинство - говорят, что не надо ни в коем случае пришивать, т.к. пакет обновляется часто, и лучше всегда отдельно скачивать свежачок - он же бесплатный!

Я всё понял! Я пошёл в АппСторр... Ой! Я зашёл в Гугл-Плэй, задал в поиске zxing и установил его. У меня появилось приложение, которое чего-то сканирует. Так вот, мой (ну, я же тоже его дописывал) MobileWrapers сразу же повеселел и стал ловко пикать совсем уже не зелёным, а полупрозрачным окошком с красивой красной полоской для прицеливания. Ура!

Теперь проблема сканирования штрих-кодов для Андроид-приложений - уже не проблема. И я верю, что для йОса тоже уже всё готово. Но я пока не компилил - может, там тоже что-то с юнитами поменялось со времён XE5.

Для желающих подкрученные мной исходники здесь - https://drive.google.com/file/d/0B8ZLqV359_X5R1ZKS3ktdF9FLXc/view?usp=sharing

Спасибо

 Fernando Rizzato!