IE9 in edit Mode & TWebBrowser = EZeroDivide

  В начале предисловия предисловие

  Приветствую тебя случайный гость. Данный блог давно задумывался, но все что то мешало, то не было темы с которой можно начать, то природная лень брала верх над намеченным. Основная цель преследуемая блогом это упорядочивание знаний, закрепление опыта, развитие навыков изложения материала, вообщем как и у всех, т.е. для себя. Если такие цели, то «какого черта» простите ждать? Садимся и пишем! Так и появилась первая запись в этом блоге, посмотрим что в дальнейшем из этого может выйти.

  На этой недели вылезла не самая тривиальная ошибка которой и хочу поделиться в первую очередь с собой=) и с возможным читателем. 

  В программной системе на моей текущей работе для отображения HTML разметки используется компонент TWebBrowser, который как известно является оберткой для COM ядра Internet Explorer’а. С недавних пор у некоторых клиентов нашего приложения  фрейм содержащий TWebBrowser стал валиться c исключением EZeroDivide — ошибка деления на ноль. В дальнейшем выяснилось, что ошибка имеет место лишь при установленном IE9 находящемся в режиме редактирования в OS Windows 7 64bit. И как только пользователь касался полосы прокрутки всплывало исключение. 
Место генерации исключения было не в Delphi обертке и находилось где то внутри IE.  Гугление показало, что я не одинок в своей проблеме, и нас как минимум трое=). Удалось установить и причину проблемы.   
  Все дело в том, что по умолчанию в MSVC и библиотеках написанных на нем при выполнение операций в FPU не возбуждаются исключения на ошибки, а возвращает значения NaN, +INF, -INF.  За настройку данного поведения в FPU отвечает так называемый регистр управляющее слово — Control Word (CW).Кроме того, через него можно задать и другие параметры влияющие на вычисление операций над числами с плавающей запятой, например точность. 
Для решения проблемы достаточно установить нужную маску в данный регистр перед созданием TWebBrowser и восстановить значение регистра после закрытия. Это может выглядеть как то так:

var 
CW: word;

procedure TForm1.FormCreate(Sender: TObject);
begin
CW := Get8087CW(); // Запоминаем предыдущее состояние регистра
System.Set8087CW($133f); // Выключаем исключения
end;

procedure TForm1.FormDestroy(Sender: TObject);
begin
System.Set8087CW(CW); // Восстанавливаем предыдущее значение регистра
end;


  Но данный способ приемлем если у вас форма с TWebBrowser открыта модально или приложение не большое. В случае же если приложение MDI, большое и пользователь может динамически выполнять скрипты, то ни как не хотелось бы отключать генерацию исключений на все время работы приложения. Но как сделать так, что бы исключения были выключены только для TWebBrowser? 
К счастью для меня TWebBrowser используется у нас для отображения статической HTML разметки и проблема была замечены только когда пользователь трогал полосу прокрутки, потому было решено менять значение управляющего регистра только когда мышь появляется над TWebBrowser и восстанавливать обратно, когда покидает границы компонента.


TEventObject = class(TInterfacedObject,IDispatch)
private
FOnEvent: TProc;
protected
function GetTypeInfoCount(out Count: Integer): HResult; stdcall;
function GetTypeInfo(Index, LocaleID: Integer; out TypeInfo): HResult; stdcall;
function GetIDsOfNames(const IID: TGUID; Names: Pointer;
NameCount, LocaleID: Integer; DispIDs: Pointer): HResult; stdcall;
function Invoke(DispID: Integer; const IID: TGUID; LocaleID: Integer;
Flags: Word; var Params; VarResult, ExcepInfo, ArgErr: Pointer): HResult; stdcall;
public
constructor Create(const OnEvent: TProc);
property OnEvent: TProc read FOnEvent write FOnEvent;
end;

var
webBrowser: IWebBrowser;
iDocument2: IHTMLDocument2;
eoMouseLeave, eoMouseEnter: TEventObject;
iElement2: IHTMLElement2;
j: Integer;
CW: Word;

implementation

function TEventObject.Invoke(DispID: Integer; const IID: TGUID;
LocaleID: Integer; Flags: Word; var Params; VarResult, ExcepInfo,
ArgErr: Pointer): HResult;
begin
if (Dispid = DISPID_VALUE) then
begin
if Assigned(FOnEvent) then
FOnEvent; // Вызываем обработчик события
Result := S_OK;
end
else Result := E_NOTIMPL;
end;

procedure TForm1.FormCreate(Sender: TObject);
begin
{Создание объектов обработчиков реализующих IDispatch}
eoMouseEnter := TEventObject.Create(self.OnMouseEnter);
eoMouseLeave := TEventObject.Create(self.OnMouseLeave);
end;

procedure TForm1.WebBrowser1DocumentComplete(Sender: TObject;
const pDisp: IDispatch; var URL: OleVariant);
begin
{Устанавливаем обработчики на события после загрузки страницы}
webBrowser := pDisp as IWebBrowser;
if Assigned(webBrowser.Document) then
begin
iDocument2 := webBrowser.Document as IHTMLDocument2;
iDocument2.DesignMode := 'On';

for j:=0 to iDocument2.All.Length-1 do
begin
iElement2 := iDocument2.All.item(j,EmptyParam) as IHTMLElement2;
iElement2.AttachEvent('onmouseenter', eomouseEnter);
iElement2.AttachEvent('onmouseleave', eoMouseLeave);
end;
end;
end;

procedure TForm1.WebBrowser1BeforeNavigate2(Sender: TObject;
const pDisp: IDispatch; var URL, Flags, TargetFrameName, PostData,
Headers: OleVariant; var Cancel: WordBool);
begin
{отсоединяем обработчики от событий, перед загрузкой новой страницы}
if Assigned(webBrowser.Document) then
begin
iDocument2 := webBrowser.Document as IHTMLDocument2;
for j:=0 to iDocument2.All.Length-1 do
begin
iElement2:=All.item(i,EmptyParam) as IHTMLElement2;
iElement2.detachEvent('onmouseenter', eoMouseEnter);
iElement2.detachEvent('onmouseleave', eoMouseLeave);
end;
end;
end;


procedure TForm1.OnMouseEnter;
begin
CW := Get8087CW();
System.Set8087CW($133f); // Отключаем исключения
end;

procedure TForm1.OnMouseLeave;
begin
System.Set8087CW(CW); // Восстанавливаем значение регистра
end;


  Здесь мы вешаем обработчики на события OnMouseEnter и OnMouseLeave, т.к. MSDN говорит нам, что события не всплывающие(не проходят по всей иерархии DOM до верхнего уровня), то обработчики устанавливаем на все объекты документа.  В качестве обработчика метод IHTMLDocument.AttachEvent просит объект с IDispatch, для этого реализуем TEventObject. И не забываем, что eoMouseEnter, eoMouseleave надо удалить перед закрытием формы.

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

Изменение свойств Read-Only

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

Проверка корректности адреса электронной почты

   Проверить корректность адреса электронной почты очень просто (не только синтаксис, но и его реальное существование). Для этого можно воспользоваться компонентой clEmailValidator из библиотеки Clever Internet Suite. Напишем с ее использованием простую функцию:

Function ValidateMail(const sAddress: String): Boolean;
Var
  clEV: TclEmailValidator;
begin
  clEV := TclEmailValidator.Create(nil);
  Try
    clEV.ValidationLevel := vlMailbox;
    clEV.DnsServer := ‘8.8.8.8’;
    Result := clEV.Validate(sAddress) = vrMailboxOk
  Finally
     clEV.Free;
  End;
end;

Я задал всего лишь два свойства clEmailValidator (остальные можно пропустить). Первое и основное, это ValidationLevel — метод (уровень) проверки правильности адреса, их пять:

  1. vlBlacklist — проверка на вхождение адреса в ваш «чёрный список» (чёрный список хранится у clEmailValidator в свойстве BlackList типа TStrings);
  2. vlSyntax — проверка синтаксиса (проверяется при помощи шаблона RegEx);
  3. vlDomain — проверка существования домена (проверяется запросом DNS-информации для почтового домена);
  4. vlSmtp — проверка доступности почтового сервера (проверяется попыткой установления SMTP-соединения с хостом, указанным в MX-записи DNS);
  5. vlMailbox — проверка существования почтового ящика (проверяется попыткой отправить SMTP-запрос почтовому серверу с указанием проверяемого адреса в поле «RCPT TO»).

   Я расположил эти значения в списке по очереди (в порядке) выполнения. Т.е. если вы хотите проверить существование домена, то clEmailValidator выполнит проверки нижнего уровня — сначала на вхождение в чёрный список, потом проверку синтаксиса адреса, а уж затем проверку существования домена.
   Проверка корректности с уровнем vlDomain и выше требует активного internet-подключения и у clEmailValidator необходимо задать значение свойству DnsServer. DnsServer — это IP-адрес name-сервера вашего internet-провайдера. В примере выше я использовал адрес Google Public DNS.
   При желании для уровня vlSmtp (и выше) можно задать имя которое используется в команде «SMTP HELO» (свойство HostName). А для уровня vlMailbox можно задать еще и адрес электронной почты, который используется в SMTP-запросе свойство EmailFrom). Я написал «при желании», т.к. у меня проверка корректности работает и без них.
   Метод Validate возвращает уровень, до которого проверка адреса электронной почты выполнилась корректно (vrBlacklistOk, vrSyntaxOk, vrDomainOk, vrSmtpOk, vrMailboxOk) или, если вы совсем ерунду подсунули clEmailValidator, то «vrInvalid».

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

Описание у службы Windows

   При написании очередной службы для MS Windows, меня посетила мысль: «А почему у TService нет свойства «Description«?». При этом из своего опыта знаю, что подобное свойство есть у аналогичных компонент: у TNtService из библиотеки SvCom и даже у бесплатной компоненты TDDService от Arno Garrels, а у стандартного TService его нет даже под Delphi XE. Я понимаю, что описание — это не важно, но служба без описания выглядит на фоне других как-то неполноценно.
   Метода для установки описания у TService я не нашел, поэтому ничего другого не оставалось, как добавить описание службе самому.
   Оказалось, у MS Windows для этого есть специальная функция — ChangeServiceConfig2 (в advapi32.dll), которая изменяет дополнительные параметры конфигурации службы. Вместо реализации вызова ChangeServiceConfig2 на Delphi я сделал проще – в событии TService.ServiceAfterInstall записал описание службы напрямую в реестр:

procedure TxyzSvc.ServiceAfterInstall(Sender: TService);
begin
  With TRegistry.Create(KEY_READ or KEY_WRITE) do
    Try
      RootKey := HKEY_LOCAL_MACHINE;
      If OpenKey(‘SYSTEMCurrentControlSetServices’ + Name, False) then
        begin
          WriteString(‘Description’, ‘XYZ Service’);
          CloseKey;
        end;
    Finally
      Free;
    End;
end;

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

РАО N 2?

На днях по электронной почте пришло УВЕДОМЛЕНИЕ от ФГУП МНИИ «Интеграл», о том что

«Согласно Федеральному закону № 77-ФЗ «Об обязательном экземпляре документов» от 29.12.1994 г производители программ для ЭВМ и баз данных обязаны доставлять в МНИИ «Интеграл» один обязательный бесплатный экземпляр программ.«

Штрафы за непредоставление, кстати (в письме не указано), для юрлиц от 10000 до 20000 тысяч рублей.

Я тут же открыл этот закон на сайте Консультанта, и обнаружил вот что.

Статья 5. Виды документов, входящих в состав обязательного экземпляра
1.
…программы для электронных вычислительных машин и базы данных на материальном носителе;

К примеру, мы не поставляем свое ПО на материальных носителях.

Кстати, если сравнить этот текст с текстом, размещенным на сайте «Интеграла»,
http://indepo.ru/laws_1.htm
то кто-то из двух врет. Потому что Эта же 5-ая статья в редакции Интеграла содержит отличное описание пункта об электронных изданиях:
электронные изданияпрограммы для электронных вычислительных машин и базы данных, а также электронные документы, прошедшие редакционно-издательскую обработку, имеющие выходные сведения, тиражируемые и распространяемые на машиночитаемых носителях;
(подчеркнуто то, чего нет на Консультанте и других сайтах, цитирующих закон в самой последней редакции.)

Отсюда впечатление, что некто там встрепенулся, что можно заработать денег, штрафуя российских разработчиков ПО.
Я мог бы прислать наши «экземпляры», но подчеркиваю, что мы их не распространяем на материальных носителях, и кроме того, наше ПО требует регистрации и защищено от взлома. И такого ПО сейчас 99% (исключая бесплатное).
Сам закон написан безобразно (в т.ч. юридически, много к чему есть придраться), и смысл его от меня ускользает. Есть же ФИПС, в конце-концов.

p.s. Например, Статья 13, пункт 1 — «Производители документов доставляют в МНИИ «Интеграл» один обязательный экземпляр программ для электронных вычислительных машин и баз данных». А в Статье 1 написано, что документ — это «материальных носитель с зафиксированной на нем в любой форме информацией….». Там же в определении «производитель документов» ничего про ПО не написано. Там даже «Электронные издания — документы, …которые … тираюируются и распространяются на машинночитаемых носителях».

Ну и самое главное: «Настоящий Федеральный закон не распространяется на …. электронные документы, распространяемые исключительно в сетевом режиме»

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