понедельник, 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;
Получаемый результат: