Spread the love

Drag&drop — это модель переноса данных общего назначения, не привязанная к каталогам Проводника

Это перевод Drag and drop is a general purpose transfer model, not exclusive to Explorer directories. Автор: Реймонд Чен.

Если вы спроектировали свою программу таким образом, что она подразумевает, что пользователь может перетаскивать ваш объект только в каталог файловой системы, то вы уже проиграли.

piers хочет определить назначение операции перетаскивания. Однако из постановки вопроса становится ясно, что piers, на самом деле, заинтересован в пути назначения. Но кто сказал, что назначением будет каталог? Пользователь может перетащить объект в сообщение e-mail, бросить на FTP-сайт, на Web-сайт (через Web-папки), или даже в каталог операционной системы внутри виртуальной машины!

Следующее уточнение ещё больше запутывает. Если пользователь перетащит файл на FTP или другую виртуальную папку, то как ваша программа может перезапустить закачку? Вы же не знаете пароля для FTP. Вы не знаете как перезапустить виртуальную машину и войти в неё тем же пользователем. А даже если вы сможете это сделать, то вы понятия не имеете, как писать в папку внутри виртуальной машины — только менеджер виртуальной машины знает, как это сделать. В мире сегодня существует бесчисленное множество вариантов виртуальных папок. Я крайне сомневаюсь, что вы знаете как запихать свои данные в каждый вариант виртуальной папки.

Как только пользователь перетащит объект, сам процесс переноса — это личное дело только источника (source) и назначения (target). И вы, как источник, не можете просто взять и сказать: «теперь командую я», потому что даже если бы назначение согласилось, то вы всё ещё остаётесь с проблемой, а как же это осуществить.

Какова моя рекомендация?†

Объект перетаскивания (data object) в цикле drag/drop должен следовать стандартному протоколу Оболочки, так что пользователь сможет перетащить объект в e-mail, FTP и т.д.‡

Для дополнительного шика, я бы создал ловушку drag/drop (drag/drop hook). Пользователь сможет перетащить объект, используя правую кнопку мыши°. Когда пользователь отпустит кнопку мыши, появится контекстное drop-меню — вместе с вашей ловушкой. Ловушка может создать пункт вроде «Скопировать с КрутойПрограммой» (конечно же, пункт создаётся ловушкой только в случае, если объект данных идентифицировал себя как созданный в КрутойПрограмме). Если пользователь выбирает «Скопировать с КрутойПрограммой», то вы можете выполнить своё копирование с дополнительными возможностями.

Уголок зануды

† Заметьте, что я не утверждаю, что все продукты Microsoft следуют моим рекомендациям. Также заметьте, что это мои личные рекомендации, не являющиеся официальной позицией Корпорации Microsoft.

‡ И, прежде чем реализовывать не стандартный протокол передачи объекта, вам нужно понимать стандартный протокол.

° Точнее: вторичную кнопку мыши, поскольку вы могли поменять кнопки мыши местами.

Читать на сайте автора.

Списывание остатков

Это перевод Scrapping the Scraps. Автор: Реймонд Чен.

Хотя это случается не часто, но иногда всё же из Windows удаляется какая-то старая функциональность (что, конечно же, празднуется всей командой).

К примеру, в Windows XP был удалён интерфейс по установке фонового узора рабочего стола. Сама функциональность сохранена, и вы можете написать программу, которая вызывает SystemParametersInfo(SPI_SETDESKPATTERN) — просто теперь нет интерфейса пользователя для этой функциональности. Почему интерфейс для установки узоров был удалён из апплета Экран Панели управления?

Наши исследования показали, что практически никто не использует сегодня узоры. Они были очень популярны в старые времена, когда памяти было мало, она была дорогой, и пользователи не хотели тратить четверть своей памяти на мегабайтный растровый рисунок обоев. Крохотный монохромный рисунок 8х8, повторённый по всему экрану, предоставлял возможности по персонализации экрана 640х480 за гораздо меньшую «цену».

Однако время делало узоры всё менее привлекательными. Во-первых, конечно же, сегодня компьютеры имеют намного больше памяти, чем в 1983 году. Во-вторых (также конечно же), сегодня мониторы имеют гораздо большее разрешение экрана. Симпатичный для экрана 640х480 узор 8х8 будет выглядеть серым пятном на мониторе 1900х1080.

Это не единственная возможность, которую мы отправили в мусорную корзину за эти годы. Фрагменты (scraps, .shs-файлы) — вот ещё один пример удалённой функциональности. Впервые представленные в Windows 95, фрагменты являются частями документа, сохранёнными в файл. Файл в действительности является OLE-объектом. Идея была в том, что вы могли, к примеру, выделить текст в редакторе и перетащить его на рабочий стол, где создавался файл фрагмента. Потом вы могли взять этот фрагмент и перетащить его обратно в редактор или любую другую программу, и сохранённый в фрагменте текст вставился бы в новый документ. Это что-то вроде копирования/вставки, только вместо временного хранения в буфере обмена, данные сохранялись в постоянный файл.

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

Я не знаю, была ли эта возможность добавлена как отладочный инструмент или как реальная возможность для пользователей посмотреть содержимое фрагмента без создания пустого документа и перетаскивания в него фрагмента. В любом случае, эту возможность сразу полюбили писатели вирусов, потому что она позволяла им создавать фрагменты с данными вида «приложение, которое создало этот фрагмент — это cmd.exe, а сам фрагмент документа — вот этот .bat-файл». И когда пользователь дважды-щёлкал по такому файлу фрагмента, просмотрщик фрагментов передавал .bat-файл из фрагмента командному интерпретатору: «Пожалуйста, открой вот это и покажи его пользователю».

Учитывая историю просмотра фрагментов, команде Оболочки (Shell) было поручено удалить эту функциональность из Windows Vista. Чтобы выполнить назначенную задачу, команда провела исследование: насколько популярной была эта функциональность в реальном мире. Для этого команда Оболочки попросила команду поддержки продукта поднять свои записи и сказать им, сколько людей позвонило с вопросами по фрагментам. Идея состояла в том, что по популярной функциональности будет много вопросов, а по не популярной — не будет (поскольку её никто и не использует). В частности, фрагменты не совсем относятся к категории «абсолютно очевидно», поэтому малое число вопросов нельзя будет объяснить «ну, это же так просто, поэтому нам никто и не звонит!»

Команда поддержки продуктов подготовила ответ. За прошедший год она получила целых четыре вопроса по фрагментам. Все они были заданы в форме «Я как-то создал вот такой странный файл. Что это такое и как мне его удалить?»

Читать на сайте автора.

самой задачей

Если вы вдруг заинтересовались самой задачей, и работаете над ее решением, то вам лучше не читать данный пост, так как в нем я по шагам

http://alexander-bagel.blogspot.com/2015/01/blog-post.html

Жизнь и восстановление после StackOverflow

Распространено мнение, что StackOverlow — это приговор. Все, что остается приложению — это красиво схлопнуться, не оставив возможности пользователю продолжить работу, сохранить важные данные, и даже информацию об ошибке.

Дальше я расскажу, зачем и как можно обрабатывать ошибку переполнения стека, а так же какую «свинью» подложила EurekaLog — инструмент, который должен был обеспечивать нас информацией о том, почему клиент снова в бешенстве.

Сначала немного справочной информации о том, как устроен стек.

Устройство стека 

В каждом потоке под стек  резервируется по умолчанию 1 МБ адресного пространства. Зарезервированная память физически еще не выделена. Все адресное пространство разбито на страницы — минимальные блоки, которыми оперирует системный менеджер памяти. Стек растет от большего адреса к меньшему постранично. Память выделяется (становится commited) в момент роста стека. Для того, что бы определить необходимость выделения еще одной страницы из зарезервированных, используется защищенная страница памяти. Это такая страница, которая уже выделена (закомичена)  и помечена флагом Page_Guard. Защищенная страница всегда располагается следом за страницей, в которой находится текущая вершина стека. При росте стека происходит обращение к защищенной странице, система реагирует, снимается флаг Page_Guard со страницы и возбуждается исключение доступа к защищенной странице.  Обработав исключение система понимает, что необходимо выделить еще страницу памяти из зарезервированного пространства и пометить ее флагом Page_Guard. Уже выделенная память стека обратно не возвращается, и страницы остаются commited даже после полной раскрутки стека.
Последняя (в сторону роста стека) зарезервированная страница адресного пространства никогда не выделяется. Она служит буфером, что бы вершина стека не вышла за границы сегмента.

Механизм возбуждения StackOverflow 

В какой-то момент в процессе роста стека остается только одна защищенная страница перед последней зарезервированной. Приложение пытается нарастить стек. С защищенной страницы снимается флаг защиты и происходит попытка выделить еще одну защищенную страницу. Система не разрешает и возбуждает исключение переполнения стека (StackOverflow). Как правило, после этого у нас остается еще почти целая страница для обработки исключения.
С этого времени система снимает с себя ответственность за контроль стека. Page_Guard  в дальнейшем больше не выставляется страницам, и если стек снова достигнет предела, произойдет обращение к последней зарезервированной странице. В результате система убъет поток и приложение. Для пользователя — приложение просто схлопнется.

Почему StackOverflow это проблема

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

procedure FmtStr(var Result: string; const Format: string;
const Args: array of const; const FormatSettings: TFormatSettings);
var
Len, BufLen: Integer;
Buffer: array[0..4095] of Char;
begin
..
end;

Лишь один такой вызов израсходует всю оставшуюся доступную память для того, что бы расположить на стеке массив Buffer. 
В рабочем проекте мы используем EurekaLog для сбора информации о стеке исключений и формирования отчета об ошибке. Но при обработке исключения StackOverflow, эврика обращалась к методам rtl, которые не оставляли нам шанса узнать причину, почему у клиентов на проекте наш продукт в момент работы схлопывается.
Была еще одна причина, которая побудила решить эту проблему кардинально. Большую часть нашего продукта занимает скриптовый движок для исполнения паскаль скриптов. Клиентская логика пишется на паскаль скрипте. Паскаль скрипт динамически подгружается. Таким образом, если программист допустит ошибку при написание скрипта и получит StackOverflow, то не останется другого варианта как снова перезапустить развесистый фреймворк.

Обработка StackOverflow

В MSDN есть функция
  BOOL WINAPI SetThreadStackGuarantee(_Inout_  PULONG StackSizeInBytes);
с помощью которой можно установить размер памяти, который останется доступным после возбуждение StackOverflow для обработки исключения. Функция выполняет простую вещь. Если с помощью ее установить гарантированный размер 8 килобайт, то в этом случае система будет держать перед вершиной стека 2 защищенные страницы по 4 килобайта.  
Теперь мы можем гарантированно собрать информацию об ошибке. Эвристическим путем было выяснено, что для эврики надо 32 кБ. Для перестраховки мы задаем 64кб. Однако следующее переполнение стека будет фатальным. 
Но для полного восстановления после StackOverflow необходимо вернуть защищенные страницы. Когда это сделать? Когда стек свернется, и вершина стека уйдет со страниц, на которых мы хотим восстановить Page_Guard. При этом остается вероятность, что при обработке исключения переполнения стека, мы получим новое переполнение стека. Для этого сразу же после первого переполнения стека выставим на одной верхней странице флаг Page_Guard.  И в случае нового переполнения стека при обработке предыдущего, мы сможем хотя бы сообщить о переполнение стека при обработке переполнения стека и закрыть приложение.
Вот так это выглядит в теории:
На практике будут следующие шаги:
  1. Установить гарантированный размер для обработки стека равный 68 кБ;
  2. Необходимо повесить хук на возникновение исключения. В случае StackOverflow начать процесс восстановления. Установить на 1 страницу Page_Guard. В Delphi можно повесить для этих целей обработчик на System.ExceptObjProc;
  3. Дождаться раскрутки стека. Это самый интересный момент. Тут возможны разные варианты. Один из самых простых вариантов —  установка хука на оконные сообщения и на onIdle. В VCL приложение стек как правило всегда раскручивается до Application.ProcessMessage. Вот тут и попытаемся восстановить остальные 16 защищенных страниц;
  4. Если в момент восстановления стека, система возбудила новое исключение StackOverflow, то ничего не остается, как сделать хорошую мину: информируем пользователя об исключении и выполняем перезапуск приложения.
Приведу простейший пример восстановления после StackOverflow. Все начинается с Button1Click и секция initialization:
unit main;

interface

uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes,
Vcl.Graphics, Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls;

type
TForm1 = class(TForm)
Button1: TButton;
procedure Button1Click(Sender: TObject);
private
{ Private declarations }
public
procedure DoOnMessage(var Msg: TMsg; var Handled: Boolean);
end;

function SetThreadStackGuarantee(var StackSizeInBytes:
Cardinal): LongBool; stdcall; external 'kernel32.dll' name 'SetThreadStackGuarantee';
type
proc = procedure();
TRaiseExceptionProc = procedure(ExceptionCode, ExceptionFlags: LongWord;
NumberOfArguments: LongWord; Args: Pointer); stdcall;
TExceptObjProc = function(P: PExceptionRecord): Pointer;
var
Form1: TForm1;
oldRaiseExceptionProc: TExceptObjProc = nil;
restoringAfterStackOverlow: boolean = false;
stackGuaranteeSize: Cardinal;
const
PageSize = 4096;

function _RaiseExeceptionProc(P: PExceptionRecord): Pointer{Exception};
implementation

{$R *.dfm}

{$POINTERMATH ON}

function GetSegmentStackTop: Pointer; assembler;
type
NT_TIB32 = packed record
ExceptionList: DWORD;
StackBase: DWORD;
StackLimit: DWORD;
SubSystemTib: DWORD;
case Integer of
0 : (
FiberData: DWORD;
ArbitraryUserPointer: DWORD;
Self: DWORD;
);
1 : (
Version: DWORD;
);
end;
asm
MOV EAX, FS:[0].NT_TIB32.StackLimit
end;

function GetESP: Pointer; assembler;
asm
mov result, esp
end;

function GetMemoryInfo(addr: NativeUInt): TMemoryBasicInformation; inline;
begin
VirtualQuery(Pointer(addr), Result, SizeOf(Result));
end;

procedure TerminateRecoveringAfterStackOverflow;
begin
Assert(restoringAfterStackOverlow);
MessageBox(0, 'Не удалось восстановится после StackOverflow, приложение будет закрыто.',
'Все пропало',
MB_ICONERROR);
Halt(0);
end;

function RestoreStackPageGuard(APageAddress: Pointer): boolean;
var
oldProtectFlag: dword;
begin
result := VirtualProtect(APageAddress, PageSize, PAGE_READWRITE or PAGE_GUARD, oldProtectFlag);
end;

procedure StartRecoveringAfterStackOverflow;
begin
Assert(not restoringAfterStackOverlow);
restoringAfterStackOverlow := true;
RestoreStackPageGuard(GetSegmentStackTop);
Application.OnMessage := Form1.DoOnMessage;
end;

procedure StopRecoveringAfterStackOverflow;
begin
Assert(restoringAfterStackOverlow);
restoringAfterStackOverlow := false;
Application.OnMessage := nil; // Так конечно лучше не делать, а использовать для этих
// целей вместо обработчика OnMessage хук на сообщения
end;

procedure ValidateStackPageGuards;
var
memoryInfo: TMemoryBasicInformation;
address, stackLimit: NativeUInt;
guarded: Boolean;
begin
address := NativeUInt(GetESP);

guarded := False;
address := NativeUInt(GetMemoryInfo(address).BaseAddress);
stackLimit := NativeUInt(GetSegmentStackTop);


while not(guarded) and (address >= stackLimit) do
begin
memoryInfo := GetMemoryInfo(address);
guarded := (memoryInfo.Protect and PAGE_GUARD) = PAGE_GUARD;
address := address - PageSize;
end;

if not guarded then
begin
TerminateRecoveringAfterStackOverflow;
end;
end;


function _RaiseExeceptionProc(P: PExceptionRecord): Pointer{Exception};
begin
if (GetCurrentThreadId = MainThreadID) then
begin
if not restoringAfterStackOverlow then
begin
if Assigned(P) and (P.ExceptionCode = STATUS_STACK_OVERFLOW) then
begin
StartRecoveringAfterStackOverflow();
end
end else
ValidateStackPageGuards;
end;

if Assigned(oldRaiseExceptionProc) then
result := oldRaiseExceptionProc(P);
end;



procedure WeNeedToGoDeeper(var i : integer);
var
a:array[0..4000] of char;
begin
inc(i);
WeNeedToGoDeeper(i);
end;

procedure TForm1.Button1Click(Sender: TObject);
var
i: integer;
begin
i := 0;
try
WeNeedToGoDeeper(i);
finally
MessageBox(0, PChar(Format('На %d вложение',[i])), 'Ошибка StackOverflow', mb_ok);
end;
end;

procedure TForm1.DoOnMessage(var Msg: TMsg; var Handled: Boolean);
var
i: integer;
begin
Assert(restoringAfterStackOverlow);
ValidateStackPageGuards;

for i := 1 to 16 do
begin
if not RestoreStackPageGuard(Pointer(NativeInt(GetSegmentStackTop) + PageSize * i)) then
begin
TerminateRecoveringAfterStackOverflow();
end;
end;
StopRecoveringAfterStackOverflow();
end;

initialization
stackGuaranteeSize := PageSize * 16;
SetThreadStackGuarantee(stackGuaranteeSize);

oldRaiseExceptionProc := ExceptObjProc;
ExceptObjProc := @_RaiseExeceptionProc;
end.

Проверить, что восстановление после StackOverflow работает можно с помощью утилиты  vmmap из SysInternalsSuite.

Первый скриншот после запуска приложения. 16 кБ используется стеком из 1МБ. 68 кБ защищенная область.
Сразу после StackOverflow. Вся память выделена.
После восстановления работоспособности.

Более подробно о работе стека можно почитать тут:

  1. MSDN http://msdn.microsoft.com/ru-ru/library/windows/desktop/ms686774(v=vs.85).aspx
  2. У Джефри Рихтера в книге «Создание эффективных WIN32-приложений»
  3. У Марка Руссиновича в блоге http://blogs.technet.com/b/markrussinovich/archive/2009/07/08/3261309.aspx и книге «Внутреннее устройство Microsoft Windows»

Читать на сайте автора.

Предновогодняя задачка для разминки мозга

Пришлось тут на днях решать для студента (первого курса) задачку.Дословно выглядит вот так:2014 ACM-ICPC China Hunan Invitational Programming Contest
There is a simple problem. Given a

http://alexander-bagel.blogspot.com/2014/12/blog-post.html

SQLite в Delphi. Работа с LiteDAC #2

Как известно, для работы с различными базами данных в Delphi есть достаточно возможностей — от dbExpress до библиотеки FireDAC (ex. AnyDAC). Каждый набор компонентов по-своему

Читать на сайте автора.

Укрощение автопрокрутки

Люблю я Delphi. И нравятся мне в ней такие фичи, как: Ctrl+Shift+Enter (Find Local References), Ctrl+Shift+E (Refactor Rename), и многие другие. Вот окошки озвученных фич у меня расположены как-то так:
image
Мне так удобнее – слева от редактора кода. А когда результаты поиска большие (или список для рефакторинга большой) – то на экране так больше помещается. Есть один минус – при клике в строчку, которая не помещается на экране по ширине – происходит автоматическая прокрутка по горизонтали (на картинке выше такая строчка выделена подчёркиванием). Т.е. дерево уезжает влево и родительские узлы прячутся за прокруткой – меня это сильно отвлекает и раздражает.
Не секрет, что эти деревья реализованы на VirtualTreeView-компоненте, поэтому такое “лечится” в одно действие:

  VTV.TreeOptions.AutoOptions := VTV.TreeOptions.AutoOptions + [toDisableAutoscrollOnFocus];

Добавляем в пакет, устанавливаем – и готово!
Исходники доступны по ссылке.
P.S.: Однажды я писал, как боролся со шрифтом Object Inspector’а Delphi XE7. Чуть позже я доработал тот пакет, чтобы он исправлял шрифт и в остальных окошках/вкладках. Но оно не прижилось – среда время-от времени восстанавливает свой шрифт – получается вообще ерунда – шрифт скачет туда-сюда. Странное поведение. Но оно исчезает, если отключить модную тему оформления, которая установлена как обычный пакет (ModernTheme210.bpl). Отключить можно через реестр:
image
Отключив этот пакет, все иконки вернутся к старому стилю, зато IDE будет работать чуть стабильнее (у меня было несколько ошибок с указанием на ModernTheme210.bpl), и не надо мудрить со шрифтами.

Читать на сайте автора.

Собираем базу Android приложений разработанных с использованием RAD Studio

Частенько, в различных местах вижу просьбы "Покажите примеры приложений написанных с использование FMX" от разработчиков. Так совпало, что я решил немного освежить свои знания по

Читать на сайте автора.

Кроссплатформенный квест

Читать на сайте автора.

Заначка

 — что-либо прибережённое, припрятанное про запас ◆ Опять же всё расставив по местам, как и было, достал из-за трюмо тёткину заначку — вскрытую пачку «Любительских», — закурил. Андрей Битов, «Сад», 1960–1963 г. (цитата из Национального

http://alexander-bagel.blogspot.com/2014/11/store.html