воскресенье, 22 мая 2022 г.

Утечка FastReport

Как же мне надоело, что в сторонних продуктах для Delphi постоянно всплывают ошибки! Когда я работал с экосистемой .NET, тоже сталкивался с ошибками, но гораздо (!) меньше. Интересно, как обстоят дела с экосистемой Java?
За последний месяц ине пришлось исправить одну ошибку в модулей сериализации, одну в библиотеке EhLib, а теперь вот проблемы с FastReport!
Работая с отчетом, содержимое которого экспортируется в Excel, я заметил, что после закрытия программы FastMM ругается на утечки памяти:
FF-FMM

Я решил попробовать поискать в исходниках по строчке "TfrxIEMStyle.Create" и вот что обнаружилось
Resize-of-FF-find

В конструкторе этого объекта как раз выделялась память, которая очень была похожа по описанию, которое выдал FastMM
FF-const

Потом я просто стал проверять, все найденные участки кода и в самом последнем обнаружил выделение памяти, которое впоследствии не освобождалось
Resize-of-FF-bug

Вылечилось это элементарным добавлением вызова деструктора
Resize-of-FF-desct

Ну и конечно же надо пересобрать компоненты FastRerpot'а с помощью их утилиты
7

понедельник, 17 мая 2021 г.

Проблема с IP*Works

Заметил, что при скачивании вложенных файлов из письма (которое в моем сервисе реализовано через Ip*Works, версия 20.0.7447) иногда имя вложенного файла определяется неверно. Каждый раз как "xls". Если смотреть содержимое письма в Outlooke, то там указано другое имя вложения.
Исследование под дебагером подтвердило, что проблема в стороннем софте, т.е. в компоненте Ip*Works
Далее я экспортировал письмо из Аутлука в файл и открыл его в HEX-редакторе.
В текстовой части (справа) мне удалось найти заголовки вложения. Они были закодированы по BASE-64. Когда я их раскодировал, оказалось, что это и было необходимое мне имя вложения.
Написав в тех. поддержку, я стал обдумывать как обойти эту проблему. Первая мысль была самостоятельно парсить заголовки и вместо "name" брать "filename". Но тут подоспел ответ от тех. поддержки Ip*Works, предлагали добавить магическую строчку ".Config('UseAttachmentNameParam=false');"

Таким образом, демонстрационный пример для скачивания вложений в письме с помощью Ip*Works выглядит так:

program Demo;

{$APPTYPE CONSOLE}

{$R *.res}

uses
  ipwCore,
  ipwIMAP,
  ipwtypes,
  System.SysUtils;


var
  ImapHandle:  TipwIMAP;

procedure InitImap();
begin
  ImapHandle.MailServer := 
  ImapHandle.MailPort   := 
  ImapHandle.User       := 
  ImapHandle.Password   := 
  ImapHandle.SSLEnabled := False;
  ImapHandle.AutoDecodeParts := True;
  ImapHandle.Config('UseAttachmentNameParam=false');
end;

begin
  ImapHandle := TipwIMAP.Create(nil);
  try
    try
      InitImap();
      ImapHandle.Connect();
      ImapHandle.Mailbox := 'INBOX';
      ImapHandle.SelectMailbox();

      for var I := ImapHandle.MessageCount - 1 downto ImapHandle.MessageCount - 1 do
      begin
        ImapHandle.MessageSet := i.ToString();
        ImapHandle.FetchMessageInfo();

        WriteLn('Id         : ' + ImapHandle.MessageId);
        WriteLn('From       : ' + ImapHandle.MessageFrom);
        WriteLn('Subject    : ' + ImapHandle.MessageSubject);
        WriteLn('Flags      : ' + ImapHandle.MessageFlags);
        WriteLn('ContentType: ' + ImapHandle.MessageContentType);
        WriteLn('Size       : ' + ImapHandle.MessageSize.ToString);
        WriteLn('');

        for var j := 0 to ImapHandle.MessageParts.Count - 1 do
        begin
          if ImapHandle.MessageParts[j].Filename <> '' then
          begin
            WriteLn('    Id          : ' + ImapHandle.MessageParts[j].Id);
            WriteLn('    ContentId   : ' + ImapHandle.MessageParts[j].ContentId);
            WriteLn('    Filename    : ' + ImapHandle.MessageParts[j].Filename);
            WriteLn('    ContentType : ' + ImapHandle.MessageParts[j].ContentType);
            WriteLn('    Size        : ' + ImapHandle.MessageParts[j].Size.ToString);
            WriteLn('');
          end
        end
      end;

    except
      on E: Exception do
        Writeln(E.ClassName, ': ', E.Message);
    end;

  finally
    FreeAndNil(ImapHandle);
  end;

  Write(#13#10 + 'press any key to continue...');
  ReadLn;
end.

среда, 12 мая 2021 г.

Сборка OpenCV

0) Проблема: требуется установить opencv 2.4.13 и настроить для работы в VS 2017 

(в дистрибутиве OpenCV собранные бинарники для VC11, VC12 не прилинкуются в новой студии, значит надо собирать из исходников)

1) Качаем последний cmake с сайта https://cmake.org/download/

https://github.com/Kitware/CMake/releases/download/v3.18.0/cmake-3.18.0-win64-x64.msi

2) Устанавливаем cmake в каталог студии (у меня там стоял предыдущий cmake версии 3.12.0, поставленный инсталлятором),

0 просто заменяя папку CMake (если такой папки нет, то можно ставить куда угодно)

C:\Program Files (x86)\Microsoft Visual Studio\2017\Enterprise\Common7\IDE\CommonExtensions\Microsoft\CMake

3) Качаем opencv-2.4.13.exe и распаковываем в C:/opencv-2-4-13 

4) Запускаем "Командная строка для разработчика VS 2017" (если CMake был поставлен в другую папку, то надо в Path прописать до неё путь)

5) Набираем 

> cmake --version

Должно выдать 

> cmake version 3.18.0

6) Проверяем генераторы, набираем

> cmake -G

Должно выдать полный список генераторов. Поскольку у нас VS 2017 и Win32, то там это должно быть

(за подробностями в документацию https://cmake.org/cmake/help/v3.18/generator/Visual%20Studio%2015%202017.html)

7) Переходим в каталог C:/opencv2/sources, там должен быть файл CMakeLists.txt, набираем (если хотим собрать Win32 для opencv)

> cmake . -G "Visual Studio 15 2017" -A Win32

8) В студии открыть OpenCV.sln, пересобрать проект ALL_BUILD (Win32 Debug)

9) Создать 

C:\opencv-2-4-13\build\Debug\Win32\bin

C:\opencv-2-4-13\build\Debug\Win32\lib

C:\opencv-2-4-13\build\include

10) Скопировать в эти каталоги содержимое

C:\opencv-2-4-13\build\Debug\Win32\bin:

C:\opencv-2-4-13\sources\bin\Debug

C:\opencv-2-4-13\build\Debug\Win32\lib:

C:\opencv-2-4-13\sources\lib\Debug

C:\opencv-2-4-13\build\include:

C:\opencv-2-4-13\sources\include

C:\opencv-2-4-13\sources\modules\calib3d\include

C:\opencv-2-4-13\sources\modules\contrib\include

C:\opencv-2-4-13\sources\modules\photo\include

(Если прикомпиляции не будут найдены ещё какие-то файлы .h или .hpp надо их найти и прописать сюда путь до них)

11) Создать пустой проект С++, добавить новый файл main.cpp

12) Внутри файла:

#include <opencv2\opencv.hpp>
using namespace cv;
int main()
{
	namedWindow("Win");
	waitKey(0);
	return 0;
}

13) Правой кнопкой мыши по проекту

Проект -> Свойства -> Каталоги VC++

Каталоги исполняемых файлов -> C:\opencv-2-4-13\build\Debug\Win32\bin

Включаемые каталоги -> C:\opencv-2-4-13\build\include

Каталоги библиотек -> C:\opencv-2-4-13\build\Debug\Win32\lib

Компоновщик

Общие -> Дополнительные каталоги библиотек -> C:\opencv-2-4-13\build\Debug\Win32\lib

Ввод -> Дополнительные зависимости -> указываем всё, что есть в /lib, а именно:

opencv_calib3d2413d.lib

opencv_contrib2413d.lib

opencv_core2413d.lib

opencv_features2d2413d.lib

opencv_flann2413d.lib

opencv_gpu2413d.lib

opencv_haartraining_engined.lib

opencv_highgui2413d.lib

opencv_imgproc2413d.lib

opencv_legacy2413d.lib

opencv_ml2413d.lib

opencv_nonfree2413d.lib

opencv_objdetect2413d.lib

opencv_ocl2413d.lib

opencv_photo2413d.lib

opencv_stitching2413d.lib

opencv_superres2413d.lib

opencv_ts2413d.lib

opencv_video2413d.lib

opencv_videostab2413d.lib

События сборки -> События после сборки -> Командная строка -> xcopy /y /d "C:\opencv-2-4-

13\build\Debug\Win32\bin\*.dll" "$(OutDir)"

14) Проверка

#include <opencv2\opencv.hpp>
using namespace cv;
int main()

using namespace cv;
using namespace std;

int main()
{
	namedWindow("Win");
	waitKey(0);
	// задаём высоту и ширину картинки
	int height = 620;
	int width = 440;
	// задаём точку для вывода текста
	CvPoint pt = cvPoint(height / 4, width / 2);
	// Создаёи 8-битную, 3-канальную картинку
	IplImage* hw = cvCreateImage(cvSize(height, width), 8, 3);
	// заливаем картинку чёрным цветом
	cvSet(hw, cvScalar(0, 0, 0));
	// инициализация шрифта
	CvFont font;
	cvInitFont(&font, CV_FONT_HERSHEY_COMPLEX, 1.0, 1.0, 0, 1, CV_AA);
	// используя шрифт выводим на картинку текст
	cvPutText(hw, "OpenCV Step By Step", pt, &font, CV_RGB(150, 0, 150));
	// создаём окошко
	cvNamedWindow("Hello World", 0);
	// показываем картинку в созданном окне
	cvShowImage("Hello World", hw);
	// ждём нажатия клавиши
	cvWaitKey(0);
	// освобождаем ресурсы
	cvReleaseImage(&hw);
	cvDestroyWindow("Hello World");
	//cout << "\r\npress any key to continue...";
	//cin.get();
	return 0;
}

вторник, 12 января 2021 г.

Создаем системный хинт

Начиная с Delphi 2009 у компонента TEdit появилось замечательное булевое свойство "NumbersOnly". Его роль понятна из названия - ограничения вводимых символов (только цифры). При попытке ввести иной символ всплывает системный хинт:


Данный хинт является системным, т.е. генерируется самой ОС Windows. Если мы хотим повесить такой хинт на определенные события, придётся создавать его "вручную". Т.е. с помощью API-функций для работы с окнами.

Создадим три процедуры: для создания хинта, для его показа и для сокрытия/удаления:

const
  TOOLTIPS_CLASS    = 'tooltips_class32';

  TTS_ALWAYSTIP     = $01;
  TTS_NOPREFIX      = $02;
  TTS_BALLOON       = $40;

  TTF_SUBCLASS      = $0010;
  TTF_TRACK         = $0020;
  TTF_TRANSPARENT   = $0100;
  TTF_CENTERTIP     = $0002;

  TTM_ACTIVATE      = WM_USER + 1;
  TTM_ADDTOOL       = WM_USER + 50;
  TTM_TRACKACTIVATE = WM_USER + 17;
  TTM_TRACKPOSITION = WM_USER + 18;
  TTM_SETTITLE      = WM_USER + 32;

var
  ToolTipHandle: Cardinal;
  ToolInfo: TToolInfo;

procedure CreateToolTips();
var
  vRect: TRect;

begin
  ToolTipHandle := CreateWindowEx(WS_EX_TOPMOST,
                                  TOOLTIPS_CLASS,
                                  nil,
                                  WS_POPUP or TTS_NOPREFIX or TTS_BALLOON or TTS_ALWAYSTIP,
                                  Integer(CW_USEDEFAULT),
                                  Integer(CW_USEDEFAULT),
                                  Integer(CW_USEDEFAULT),
                                  Integer(CW_USEDEFAULT),
                                  Application.Handle,
                                  0,
                                  Application.Handle,
                                  nil);

  GetWindowRect(Application.Handle, &vRect);
end;

procedure AddToolTip(Sender: TObject; IconType: Integer; Title: AnsiString; Text: PWideChar);
var
  vControl: TWinControl;
  vCaretPos: TPoint;
  X, Y: Integer;

begin
  vControl := Sender as TWinControl;

  if ToolTipHandle = 0 then
    exit();

  SetWindowPos(ToolTipHandle, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOMOVE or SWP_NOSIZE or SWP_NOACTIVATE);

  ToolInfo.cbSize := SizeOf(TToolInfo);
  ToolInfo.uFlags := {TTF_CENTERTIP or} TTF_TRACK or TTF_TRANSPARENT or TTF_SUBCLASS;
  ToolInfo.hwnd   := vControl.Handle;
  ToolInfo.hInst  := Application.Handle;

  GetClientRect(ToolInfo.hwnd, ToolInfo.Rect);
  ToolInfo.lpszText := Text;

  GetCaretPos(vCaretPos);
  X := vControl.ClientOrigin.X + vCaretPos.X;
  Y := vControl.ClientOrigin.Y + vControl.ClientHeight - vCaretPos.Y;

  SendMessage(ToolTipHandle, TTM_ADDTOOL,       WPARAM(1), LPARAM(@ToolInfo));
  SendMessage(ToolTipHandle, TTM_SETTITLE,      IconType,  LPARAM(PWideChar(Title)));
  SendMessage(ToolTipHandle, TTM_TRACKPOSITION, WPARAM(0), LPARAM(MakeLong(X, Y)));
  SendMessage(ToolTipHandle, TTM_TRACKACTIVATE, WPARAM(1), LPARAM(@ToolInfo));
end;

procedure HideToolTip();
begin
  SendMessage(ToolTipHandle, TTM_TRACKACTIVATE, WPARAM(0), LPARAM(@ToolInfo));
  SendMessage(ToolTipHandle, TTM_DELTOOL,       WPARAM(0), LPARAM(@ToolInfo));
end;

При создании формы создаем наш хинт, показывать его будем в событии OnKeyPress у TEdit'а, ну а закрывать по таймеру

procedure TForm1.edtLogKeyPress(Sender: TObject; var Key: Char);
begin
  if not charinset(Key,['0'..'9', ',', #8]) then
  begin
    MessageBeep(MB_ICONWARNING);
    AddToolTip(Sender, 3, 'Недопустимый символ', 'Разрешены только цифры и запятая');
    Timer.Enabled := True;
    Key := #0;
  end;
end;

procedure TForm1.FormCreate(Sender: TObject);
begin
  CreateToolTips();
end;

procedure TForm1.TimerTimer(Sender: TObject);
begin
  HideToolTip();
  Timer.Enabled := False;
end;
Получаемый результат:

понедельник, 21 декабря 2020 г.

Про частично определенные модули

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

Рассмотрим случай, когда требуется использовать библиотеку OExport в консольном проекте (Embarcadero RAD Studio 10.3.3). Для этого в тестовом примере консольного приложения я подключил модули OExport.pas и OExport_VCL.pas (также я добавил модуль MemTableEh.pas библиотеки EhLib).

Код тестового примера:

При компиляции получаю ошибку, связанную с неразрешенностью компилятором частично определенных имен модулей в OExport_VCL:
Компилятор не понимает к чему относится модуль Graphics - к Vcl.Graphics или FMX.Graphics (аналогичная ошибка, если закомментировать OExport_VCL в секции uses, будет с модулем Controls в модуле DBGridEh.pas).

Для того, чтоб решить эту проблему, мне пришлось вручную править файл .dproj.
Откроем файл в текстовом редакторе. В самом начале находим узел "FrameworkType".

Необходимо заменить значение с "None" на "VCL"
Чуть ниже найдем "PropertyGroup Condition="'$(Base)'!=''", а внутри него узел "DCC_Namespace"
Добавим в его значение следующие строчки: "Vcl;Vcl.Imaging;Vcl.Touch;Vcl.Samples; Vcl.Shell"
Готово! Теперь при компиляции ошибок не будет.





воскресенье, 29 ноября 2020 г.

Не работает отладчик для x64

Отлаживая на работе проект под x64 (Embarcadero Delphi 10.3.3), заметил, что точки останова становятся не активны после запуска проекта под дебаггером.

Выглядит это вот так (на тестовом примере):

Спустя N-ое количество времени проблема была найдена и устранена. Оказывается, если в полном пути до проекта есть русские буквы, то отладчик для x64 не работает (для x32 всё тип-топ). Соответственно проблема была решена переименованием директорий из русских букв в английские.Вот такой вот бажок!

пятница, 18 сентября 2020 г.

Установка IP*Works

На днях возникла необходимость подключиться к рабочему IMAP-серверу. Почитав отзывы в Интернете, я остановился на двух вариантах работы с IMAP в Delphi:

1. TIdIMAP4. Компонент Indy.

2. TipwIMAP из библиотеки IP*Works.

Первый вариант не подошел, ибо 1) ранее в  Инди часто бывали утечки, 2) Инди тащат за собой VCL.

Скачав с одного известного сайта установщик IP*Works и запустив его, Я заметил, что на шаге выбора студии нет моей версии. Я использую Delphi 10.3 Rio, а в списке предложенных вариантов самая последняя версия была 10.1 Berlin. Её и выбрал.

После установки в папке C:\Program Files\nsoftware\IPWorks V9 Delphi Edition (далее "папка IP*Works") появляется всё необходимо для работы с компонентами - каталоги \pas и \lib (кстати, библиотеки находятся в \pas и имеют разрешение .dru). Не было только пакета bpl. Можно, конечно, работать с невизуальными компонентами и так, но всё-таки хочется, чтобы они были в палитре.

А это значит, что придётся ставить компоненты вручную. Далее идёт инструкция, как это сделать. 

1. Запустить Delphi 10.3 Rio, выбрать "File" → "New"→ "Package Delphi"

2. Далее создать в папке IP*Works папку project, сохранить туда проект пакета под именем "IPWorks"

3. В дереве проекта ПКМ по "Contains" → "Add...", выбрать все pas'овские файлы из папки \pas

4. В дереве проекта ПКМ по "IPWorks.bpl" → Build → Install. Получить сообщение, что компоненты установлены успешно

5. Выбрать в верхнем меню Tools → Options → Language → Library → (Selected platform="Windows 32-bit")

Добавить в Library path
C:\Program Files\nsoftware\IPWorks V9 Delphi Edition\lib
C:\Program Files\nsoftware\IPWorks V9 Delphi Edition\pas

Добавить в Browsing path
C:\Program Files\nsoftware\IPWorks V9 Delphi Edition\pas

6. Настройка завершена! Теперь можно добавлять невизуальные компоненты на форму из палитры.