IsMultiThread — Функция Delphi

Содержание

IsMultiThread — Функция Delphi

Потоки в Delphi выполняют функцию имитации псевдопараллельной работы приложения. Как известно, для организации многозадачности операционная система выделяет каждому приложению, выполняющемуся в настоящий момент, определённые кванты времени, длина и количество которых определяется его приоритетом. Поэтому объём работы, который приложение может выполнить, определяется тем, сколько таких квантов оно сможет получить в единицу времени. Для операционной системы каждый поток является самостоятельной задачей, которой выделяются кванты времени на общих основаниях. Поэтому приложение Delphi, умеющее создать несколько потоков, получит больше времени операционной системы, и соответственно сможет выполнить больший объём работы.

Создать дополнительный поток в Delphi поможет объект TThread. Ввести объект TThread в программу можно двумя способами:

  1. с помощью Мастера;
  2. вручную.

1. Мастер создания дополнительного потока в Delphi создаёт отдельный модуль, в рамках которого выполняется поток. Выполним:

File -> New -> Other.

В появившейся табличке выбора найдём TThread Object . Появится окошко, в верхнюю строку которого ( >MyThread . В результате будет создан модуль, содержащий заготовку кода, реализующего дополнительный поток Delphi: unit Unit2; // Имя модуля, содержащего поток. При сохранении его можно изменить.

type
MyThread = >MyThread — заданное нами имя потока.
private

protected
procedure Execute; override;
end;

and UpdateCaption could look like,

procedure MyThread.UpdateCaption;
begin
Form1.Caption := ‘Updated in a thread’;
end; >

end. 2. В первом способе класс MyThread был создан мастером в дополнительном модуле. Второй способ состоит в том, что мы сами создаём такой класс в рамках одного из уже существующих модулей программы, например, в модуле Unit1:

unit Unit1; //Обычный модуль в котором описывается основная программа

uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls;

type
TForm1 = class(TForm)
Button1: TButton;
procedure Button1Click(Sender: TObject);
private

public

end;

//Здесь необходимо описать класс TMyThread:
TMyThread = class(TThread)
private

protected
procedure Execute; override;
end;

var
Form1: TForm1;
//Нужно ввести переменную класса TMyThread
MyThread: TMyThread;

//Нужно создать процедуру Execute, уже описанную в классе TMyThread
procedure TMyThread.Execute;
begin
//Здесь описывается код, который будет выполняться в потоке
end;

Если поток создаётся мастером, т.е. в другом модуле, то не забудьте в основном модуле описать переменную — экземпляр потока, как указано выше. Также, поскольку класс потока описан в другом модуле, имя этого модуля необходимо добавить в секцию uses. Теперь можно запускать поток, даже если в его процедуре Execute нет ни единого оператора.

//Запускать поток будем нажатием на кнопку:
procedure TForm1.Button1Click(Sender: TObject);
begin
//Вначале нужно создать экземпляр потока:
MyThread:=TMyThread.Create(False);
//Параметр False запускает поток сразу после создания, True — запуск впоследствии , методом Resume
//Далее можно указать параметры потока, например приоритет:
MyThread.Priority:=tpNormal;
end;

Применение потоков

Если в основной программе попробовать выполнить такой цикл:

то приложение зависнет. А теперь поместите его в процедуру Execute. При нажатии на кнопку наш бесконечный цикл будет непрерывно выполняться в потоке, однако и приложение как целое не зависнет.

В предыдущем примере поток выполняет бесконечный цикл. Однако, поток также обладает возможностями, позволяющими из основной программы передать ему приказ прекратить работу. Метод потока Terminate устанавливает свойство Terminated потока в True. Анализируя это свойство, поток может понять, что он должен завершить работу. Пример:

procedure TMyThread.Execute;
begin
while True do
if MyThread.Teminated then break;
end;

Этот код выполняет бесконечный цикл. Однако, при выполнении в основной программе оператора

цикл завершается, и поток прекращает свою работу.

При работе с потоками необходимо учитывать приоритет создаваемых потоков. Так, если в предыдущем примере запустить не один поток, а два или больше, нажав на кнопку несколько раз, то компьютер станет очень заметно «тормозить». Это происходит потому, что приоритет по умолчанию новых потоков — нормальный. Можно уменьшить его, задав

Этого достаточно, чтобы компьютер чувствовал себя более свободно. Вот таблица приоритетов:

Приоритет Описание
tpIdle Низший приоритет. Поток получает время только тогда, когда операционая система находится в состоянии простоя.
tpLowest Приоритет на два пункта ниже нормального
tpLower Приоритет на один пункт ниже нормального
tpNormal Нормальный приоритет
tpHigher Приоритет на один пункт выше нормального
tpHighest Приоритет на два пункта выше нормального
tpTimeCritical Максимальный приоритет. Приоритет на уровне функций ядра операционной системы.

Вот вам готовый проект работы c потоками, для экспериментов.

перехват функции в другой программе

19.09.2013, 01:06

Перехват текста одной программой из окна другой программы
Добрый вечер :) Недавно родилась идея программы, которая работает с текстом :) Саму рабочую часть.

Как сделать кнопку на другой программе
Всем привет:) Как можно поменять Button1.Visible:=False; на Button1.Visible:=True; через другое.

Как включить таймер в другой программе
Собственно запущено 2 две проги написаны на Delphi в проге 2 есть таймер Timer1.Enabled:=false.

Перехват ошибок windows в другой программе
Доброго времени суток! Запускаю приложение из своего при помощи Process. К сожалению в.

Перехват нажатия кнопки в сторонней программе
Ребят, подскажите, как можно реализовать небольшую утилиту для перехвата нажатия одной кнопки в.

Запуск консольных приложений и перехват потока ввода/вывода в Delphi XE3

Автор: Alex. Опубликовано в Программирование 27 Июнь 2014 . просмотров: 44914

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

Запуск консольных приложений или любых других приложений в Delphi делается очень просто, с помощью функции CreateProcess. Сложнее обстоит дело, если вам нужно запустить консольное приложение и при этом произвести перехват потока ввода/вывода. Для этого вам нужно создать три канала, два для перехвата потоков вывода (один для ошибок, второй для всего остального) и один для входного потока, а затем запустить процесс, передав ему эти каналы. В этом случае вы сможете считывать информацию, которую отдаёт консольное приложение и управлять им.

Как написать этот код с нуля можно найти в Интернете (например, на этом форуме), здесь же я остановлюсь на компоненте TPipeConsole, который как раз и создан, чтобы перехватывать потоки ввода/вывода в Delphi. Причём он позволяет это делать комфортно.

Компонент входит в состав юнита Pipes.pas, про который я писал в статье «Обмен данными между процессами в Delphi XE3». Там же написано, как лучше установить компоненты. Мой исправленный вариант можно скачать здесь:

(Старая версия. Лучше использовать версию Pipes.pas (Win32 и Win64), см. ниже). В юните реализация классов TPipeServer (Pipe-сервер), TPipeClient (Pipe-клиент) и TPipeConsole (класс для запуска консольных приложений, управления ими и перехвата потока вывода). Работает только на платформе Win32. Юнит с моими правками для работы с Delphi до версии XE3. Функция TPipeConsole.Execute с моими правками. Источник здесь.

Pipes.pas Версия:от 12.01.2010
31.01.2014 Windows 135.7 KB 2028

Есть вариант юнита c поддержкой всех версий Delphi и платформы Win64:

Юнит Pipes.pas с примерами, с runttime и designtime библиотеками и поддержкой платформы Win64. В юните Pipes.pas реализация классов TPipeServer (Pipe-сервер), TPipeClient (Pipe-клиент) и TPipeConsole (класс для запуска консольных приложений, управления ими и перехвата потока вывода). Должна работать с Delphi всех версий, но тестирование не проводилось. Функция TPipeConsole.Execute с моими правками. Источник здесь.

27.04.2020 Добавлены мои правки в функции TPipeConsole.Execute и TPipeConsole.Start, чтобы параметр CommandLine 100%-но не был константой, чтобы избежать AV, см. MSDN.

Pipes.pas (Win32 и Win64) Версия:от 04.10.2013
08.05.2015 147.99 KB 1868

Запуск консольного приложения и перехват потока ввода/вывода

Собственно компонент TPipeConsole всё что нам нужно уже делает. Для того, чтобы опробовать как работает запуск консольных приложений с помощью этого компонента создадим консольное приложение и скомпилируем его:

Теперь сделаем проект оконного приложения, на форму положим компонент TRichEdit, растянем его на всю форму (Align -> alClient), включим вертикальную прокрутку (ScrollBars -> ssVertical). Теперь положим на форму компонент TPipeConsole и обработаем все его события:

Теперь по событию формы OnCreate сделаем запуск нашего консольного приложения:

Выполним наше оконное приложение. Вот результат:

Как мы видим, всё отлично отработало, но есть одна ложка дёгтя при работе с компонентом. Если перемешать обычные сообщения с сообщениями об ошибке, то получать мы их будем в произвольном порядке. Вот пример такого консольного приложения:

Здесь сообщение об ошибке записывается между сообщениями 1 и 2. А вот как будет выглядеть результат:

Как видите, сначала вывелись все обычные сообщения, а потом сообщение об ошибке. Почему так происходит? Так происходит потому, что компонент TPipeConsole принимает сообщения из двух каналов по очереди: сначала из основного выходного канала, затем из канала для ошибок. Это нужно учитывать при написании консольного приложения, например, после вывода ошибки работа приложения сразу завершается. Или выводить все сообщения в основной канал.

Также здесь я отмечу, что мы использовали асинхронное выполнение консольного приложения. Если вам нужно выполнить консольное приложение синхронно, то нужно воспользоваться функцией Execute.

Теперь попробуем отправить что-нибудь консольному приложению. Для этого слегка перепишем консольное приложение:

Как видите, консольное приложение ждёт число в интервале от 0 до 9, чтобы вычислить квадрат этого числа. Значит нужно передать консольному приложению число:

Попробуем теперь передать строку вместо числа:

И, после запуска, увидим ошибку:

Остановка консольного приложения

Теперь пара слов о том, как остановить консольное приложение, которое долго выполняется. Здесь есть 2 способа: прервать выполнение или оповестить приложение, что ему нужно завершить работу. Второй способ предпочтительнее, т.к. в этом случае консольное приложение перед завершением работы может освободить занимаемые ресурсы, удалить временные файлы и т.п.

Прерывание выполнения делается с помощью функции Stop:

Оповещение приложения можно сделать с помощью функций SendCtrlC и SendCtrlBreak. Вот пример вызова:

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

Илон Маск рекомендует:  Iis включение клиентских сертификатов

Теперь положим на форму нашего оконного приложения кнопку и по событию OnClick вызовем функцию SendCtrlC:

Теперь протестируем, как работает отправка сигнала консольному приложению. Запустим нашу программу для тестирования, посмотрим, как приходят сообщения от консольного приложения, и нажмём на кнопку, которую мы сделали. Приложение завершило работу? выдав сообщение «Прерывание! Выход из цикла»:

Как видите, ничего сложного нет. Единственный момент, который нужно учесть, вызов функции Ctrl_Handler в консольном приложении происходит в отдельном потоке, поэтому при работе с глобальными переменными нужно использовать критические секции, чего в примере не сделано, чтобы не замусоривать код примера.

Итак, как вы убедились, с помощью компонента TPipeConsole можно легко контролировать консольное приложение, не углубляясь в тонкости этого процесса.

IsMultiThread — Функция Delphi

Я создаю через CreateThread поток в своем процессе , и вручную устанавливаю IsMultiThread в True.Однако в этом потоке я не могу присваивать значение строкам(динамическим).Грубо говоря , когда пишу

Var
SomeStr,SomeAnotherStr:String;
.
Begin
SomeStr:=SomeAnotherStr;
.

, то это отрабатывает , но в результате нарушается работа FindNext в том же потоке (структуры для FindNext определены совсем в другой области программы и по идее присваивание значения SomeStr никак не должно влиять на работу FindNext ,однако после этого присваивания FindNext не находит больше ни одного файла , хотя должна).В чем может быть дело , есть соображения у кого-нибудь?

не думаю, что ошибку надо искать тут, ошибка в другом

Не понял , что BeginThread()?
В справке по winapi :
A thread that uses functions from the C run-time libraries should use the beginthread and endthread .
Я так понимаю ,что beginthread и endthread не определены в winapi ,и вообще ,причем здесь C ?

>не думаю, что ошибку надо искать тут, ошибка в другом
Если перед запуском потока поставить SetLength(SomeStr,MAX_PATH)
,а в потоке вместо

SomeStr:=SomeAnotherStr;

написать

Move(PCHar(SomeAnotherStr)^,PCHar(SomeStr)^,Length(SomeAnotherStr)+1);

и работать с SomeStr как с PCHar , то работа FindNext как ни странно , не нарушается.

1) Код твоей процедуры-потока приведи
2) Var
SomeStr,SomeAnotherStr:String;
.
Begin
SomeStr:=SomeAnotherStr;
— Это откуда? Из потока присваивается? Как объявлены структуры для FindNext? Код присваивоения им значений?


> и работать с SomeStr как с PCHar , то работа FindNext как
> ни странно , не нарушается.

вы думаете это доказательство?

приведите больше кода: как запускаете поток, фунцию потока .


> и вручную устанавливаю IsMultiThread в True.

«IsMultiThread is set to True by BeginThread in order to allow the memory manager to execute safely if a VCL application has more than one thread. If a second thread attempts to allocate memory, it is blocked until the first thread exits the memory manager.»

Я вручную устанавливаю IsMultiThread в True потому , что читал в какой-то книжке по Dephi , что менеджер памяти в Delphi будет корректно работать , если запущено несколько потоков , только если IsMultiThread=True , а запуская поток через api — CreateThread из kernel32.dll , а не через класс TThread , я по-моему должен поставить IsMultiThread=True.
Я сейчас поэкспериментировал с тем кодом и выяснил : FindNext и CreateThread по большому счету ни при чем. Там происходит выход из цикла с FindNext из-за того , что портится значение одной строковой переменной (SomeAnotherStr), которая не должна меняться(так).А портится она из-за того , что после присваивания

SomeStr:=SomeAnotherStr;

оказывается , что

LongInt(PCHar(SomeStr))-LongInt(PCHar(SomeAnotherStr))=0

Это нормально. Строки ведь должны физически копироваться!!
Это я глючу или компилятор??


> Я вручную устанавливаю IsMultiThread в True потому , что
> читал в какой-то книжке по Dephi , что менеджер памяти в
> Delphi будет корректно работать , если запущено несколько
> потоков , только если IsMultiThread=True , а запуская поток
> через api — CreateThread из kernel32.dll , а не через класс
> TThread , я по-моему должен поставить IsMultiThread=True.

абсолютно верно.

> Это нормально. Строки ведь должны физически копироваться!!

начну издалека.
в дельфи есть несколько типов с управляемым (компилятром) временем жизни:
1. СТроки
2. Дин массивы
3. Интерфейсы.
Есть еще, но не суть.

Все это указатели.
При присваивании одного другому в любом случае происходит копирование указателя с увеличением счетчика ссылок. Но нальше для строки и дин массивов все по-разному. Если в копии строки поменять значение, то строка скопируется в другое место со счетчиком ссылок 1, у старой строки счетчик уменьишстя на 1. У дин массивов такого нет — меняшь в копии, меняешь в оригинале.

Вообще то это все в хелпе описано в разделе object pascal reference.

>>vertal © (18.06.04 18:50) [8]

>Строки ведь должны физически копироваться!!

Длинные строки не должны. Читай справку/книгу.


> vertal © (18.06.04 18:50) [8]

если вам нужно заставить строки скопироваться, то используйте uniquestring

А может все-таки кусок кода привести?
Ну так. на всякий случай — вдруг еще не все поняли логику работы программы.

Речь идет о динамических дельфийских строках , о чем я по-моему сразу сказал.В справке сказано , что при присваивании происходит физическое копирование.Чаще всего так и есть.
Я понял .Это ребята из борланда дооптимизировались, мля.Суть в том , что при присваивании значения строке физически она копируется только тогда , когда компилятор считает , что ее копия может быть изменена. Если копия будет изменяться как PChar , то компилятор не обратит на это внимание.Вот пример:

<$APPTYPE CONSOLE>
Var
s,t:String;
Begin
s:=»1″;
t:=s;
t[1]:=»2″;
//Pchar(t)[0]:=»2″;
writeln(LongInt(PCHar(s))-LongInt(PCHar(t)),»:»,t,»-«,s);
End.

Если закомментировать строку «t[1]:=»2″;» вместо «Pchar(t)[0]:=»2″;» , то writeln напишет «0:2-2».
Я в своей программе слишком буквально полагался на справку(там дело в вызове CharToOEM(Pchar(SomeStr),PCHar(SomeStr))).


> Я понял .Это ребята из борланда дооптимизировались

Вряд ли проблема в ребятах из Borland.

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


> vertal © (18.06.04 21:45) [15]
В справке сказано , что при присваивании происходит физическое копирование

врете.

супер вешь — object pascal reference (раздел шлатного хелпа). Там есть все о паскале, который лежит в основе дельфи.

Delphi. Потоки (TThread). Создание и синхронизация.

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

Что такое потоки? Зачем нужны потоки и как это работает?

Понятие потоков в Delphi перекочевало из Windows. Очень хорошо об этом написано в книгах Рихтера и Калверта. Но если своими словами и по простому – поток это участок кода, который выполняется параллельно.

Потоки нужны для параллельной работы разных задач приложения. Часто приложение выполняет продолжительные во времени операции. Это как минимум занимает главный поток приложения, который обрабатывает его GUI, скажем. То есть в этот момент нельзя передвинуть форму приложения, нельзя нажать на кнопку. Нельзя практически ничего, пока приложение не закончит начатую задачу.

В Windows работает механизм процессов и потоков. Процесс, согласно Чарльзу Калверту, подобен булеву выражению, он либо есть, либо нет. Потоки же выполняют всю основную работу.

Согласно Чарльзу Калверту это подобно игровому колесу в казино. Допустим оно поделено на 360 частей-секторов. Эти части и есть, образно, потоки. В каждый момент времени какая-то из частей пролетает под стрелкой, которая и укажет в конце выиграли вы или нет. Но выигрыш тут ни при чем. Нас интересует сам механизм. Аналогия в том, что в каждый момент времени одному из потоков выдается квант времени на то, чтобы он выполнил часть задачи. И все это происходит настолько быстро, что человек не успевает замечать. Вот так это работает.

Как создать поток (TThread)?

Теперь посмотрим как это работает в Delphi. Для простоты будем работать с обычными VCL приложениями.

Для начала объявим класс потока в секции type

IsMultiThread — Функция Delphi

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

Function Func1(param:type):restype;stdcall;external ‘libname.dll’;

Вы импортируете функцию статически. Адрес функции прописывается в таблице импорта вашего приложения (допустим, что адрес нашей функции $7BC56010).
адрес значение
……
$00405F56 7BC56010
……

А при вызове функции происходит так
Push …
push …
call dword ptr [$00405F56]

Следовательно, для перехвата функции нам надо только подменить значение по адресу $00405F56 на своё, а для вызова оригинальной функции получать адрес функции через GetProcAddress. Но приложение может также получить адрес функции через GetProcAddress и вызывать перехватываемую функцию минуя, перехватчик. Данный метод бесперспективен.

Идём дальше. Что такое сплайсинг? Наша функция находится по адресу $7C80B529 и допустим, что там такой код
7C80B529 8BFF mov edi, edi
7C80B52B 55 push ebp
7C80B52C 8BEC mov ebp, esp
7C80B52E 837D 08 00 cmp dword ptr ss:[ebp+8], 0

Для перехвата функции от нас требуется только переписать начальный код функции, так чтобы он передавал управление нашему обработчику. Для передачи управления нашему обработчику достаточно всего лишь одной инструкции jmp на абсолютный адрес, на адрес нашего обработчика . Эта инструкция займёт всего лишь 5 байт – сам опкод этой инструкции ($E9) и значение для прыжка. Это значение вычисляется так
v=0-(s-d)
s — Смещение следующей команды
d — Требуемый адрес для jmp, т.е. адрес обработчика

Если немного переделать эту формулу, то она будет выглядеть так
v=d-FunctionAddress-5

Теперь при каждом вызове целевой функции, всегда будет передаваться управление нашему обработчику. А как теперь вызвать оригинальную функцию? При установке перехвата нам надо сохранять первые 5 байт функции. Для вызова оригинала надо восстанавливать начало функции и вызывать ее, потом снова устанавливать перехват. Объявим структуру в которой будем сохранять первые 5 байт функции:
PFunctionRestoreData = ^ TFunctionRestoreData;
TFunctionRestoreData = packed record
Address:Pointer;
val1:Byte;
val2:DWORD;
end;

Поле Address фактически в этой структуре не нужен (он просто не к чему), поле нужно только для того чтобы было удобнее снимать перехват. Назовём эту структуру «мост» к старой функции.

Теперь напишем функцию, которая будет устанавливать перехват:
function SetCodeHook(ProcAddress, NewProcAddress: pointer; RestoreDATA:PFunctionRestoreData):boolean;
var
OldProtect, JMPValue:DWORD;
begin
Result:=False;
if not VirtualProtect(ProcAddress,5,PAGE_EXECUTE_READWRITE,OldProtect) then exit;
JMPValue := DWORD (NewProcAddress) — DWORD (ProcAddress) — 5;
RestoreDATA^.val1:= Byte(ProcAddress^);
RestoreDATA^.val2:= DWORD(Pointer(DWORD(ProcAddress)+1)^);
RestoreDATA^.Address:=ProcAddress;
byte(ProcAddress^):=$E9;
DWORD(Pointer(DWORD(ProcAddress)+1)^):=JMPValue;
Result:=VirtualProtect(ProcAddress,5,OldProtect,OldProtect);
end;

Мы сначала устанавливает атрибуты доступа к коду функции, так чтобы можно было его переписывать. Потом вычисляем значение для прыжка. Сначала сохраняем начало функции в запись, потом переписываем начало функции. В конце устанавливаем старые атрибуты доступа.
Теперь напишем функцию, которая будет снимать перехват:
function UnHookCodeHook(RestoreDATA:PFunctionRestoreData):Boolean;
var
ProcAddress:Pointer;
OldProtect,JMPValue:DWORD;
begin
Result:=False;
ProcAddress:=RestoreDATA^.Address;
if not VirtualProtect(ProcAddress,5,PAGE_EXECUTE_READWRITE,OldProtect) then exit;
Byte(ProcAddress^):=RestoreDATA^.val1;
DWORD(Pointer(DWORD(ProcAddress)+1)^):=RestoreDATA^.val2;
Result:=VirtualProtect(ProcAddress,5,OldProtect,OldProtect);
end;

Илон Маск рекомендует:  Управление ассетами

Я думаю, что здесь всё понятно — просто восстанавливаем начало перехватываемой функции. Адрес перехватываемой функции берём из структуры указатель, на которую передаётся функции в качестве единственного параметра.

Теперь напишем функцию, которая будет устанавливать перехват по имени функции.
function SetProcedureHook(ModuleHandle: HMODULE; ProcedureName : PChar; NewProcedureAddress :Pointer; RestoreDATA : PFunctionRestoreData) :Boolean;
var
ProcAddress:Pointer;
begin
ProcAddress:=GetProcAddress(ModuleHandle,ProcedureName);
Result:=SetCodeHook(ProcAddress,NewProcedureAddress,RestoreDATA);
end;

Едем далее. Описанные выше функции могут перехватывать функции только в текущем процессе. А как нам перехватывать функции в других процессах? Наиболее простой метод это засунуть перехватчик функции в DLL и в коде библиотечной функции устанавливать перехват, если DLL загружается в процесс и снимать перехват, если она выгружается. Тут ещё одна проблема: как заставить другой процесс загрузить нашу DLL. Наиболее простое решение это создание удалённых потоков. Теперь всё по порядку.
Удалённый поток создаётся функцией CreateRemoteThread.
HANDLE CreateRemoteThread(
HANDLE hProcess, // хендл процесса в котором создаётся поток
LPSECURITY_ATTRIBUTES lpThreadAttributes,//атрибуты безопасности
DWORD dwStackSize, //размер стека
LPTHREAD_START_ROUTINE lpStartAddress,//адрес функции потока
LPVOID lpParameter, // параметр для функции
DWORD dwCreationFlags, // флаги создания
LPDWORD lpThreadId // указатель на переменную, в которой будет сохранён ID потока
);

Функция потока должна иметь следующие атрибуты
DWORD WINAPI ThreadFunc(PVOID pvPararn)

У функции только один параметр –это обычный указатель. Теперь проанализируем ситуацию… Думаем…Шевелим мозгами…Функция LoadLibraryA имеет такие же атрибуты. Она принимает указатель на первый символ имени файла DLL (строка должна кончаться символом #0). Следовательно, функция LoadLibraryA полностью подходит для того, что бы она могла выступать в качестве функции потока. Так как она принимает указатель на строку в своём процессе, нам надо будет записать в память чужого процесса нашу строку и именем файла DLL. Это делается функцией WriteProcessMemory. Вот её описание
BOOL WriteProcessMemory(
HANDLE hProcess, // хендл процесса
LPVOID lpBaseAddress,// адрес по которому надо писать
LPVOID lpBuffer, // указатель на буфер
DWORD nSize, // количество байт для записи
LPDWORD lpNumberOfBytesWritten //количество реально записанных байт
);

Адрес функции LoadLibraryA мы получаем с помощью функции GetProcAddress так библиотеки kernel32.dll и ntdll.dll грузятся во все процессы всегда по одним и тем же адресам, следовательно, адрес, полученный в адресном пространстве нашего процесса, будет действителен и адресном пространстве любого другого процесса. Теперь напишем функцию, которая загружает вашу DLL в адресное пространство чужого процесса.
function LoadLibrary_Ex(ProcessID:DWORD;LibName:PChar):boolean;
var
pLL,pDLLPath:Pointer;
hProcess,hThr:THandle;
LibPathLen,_WR,ThrID:DWORD;
begin
Result:=False;
LibPathLen:=Length(string(LibName));
hProcess:=OpenProcess(PROCESS_ALL_ACCESS,false,ProcessID);
if hProcess=0 then exit;
pDLLPath:=VirtualAllocEx(hProcess,0,LibPathLen+1,MEM_COMMIT,PAGE_READWRITE);
if DWORD(pDLLPath)=0 then exit;
pLL:=GetProcAddress(GetModuleHandle(kernel32),’LoadLibraryA’);
WriteProcessMemory(hProcess,pDLLPath,LibName,LibPathLen+1,_WR);
hThr:=CreateRemoteThread(hProcess,0,0,pLL,pDLLPath,0,ThrID);
if hThr=0 then exit;
Result:=CloseHandle(hProcess);
end;

Таким образом, мы загрузили свою DLL в чужой процесс. Вообще, внедрение своего кода в чужие процессы это совсем другая история и требует написания отдельной статьи. Вышеприведённый пример это самый простой способ внедриться в чужой процесс. Обычному процессу не разрешается изменение памяти системных процессов, таких как winlogon.exe, lsass.exe, smss.exe, csrss.exe и др. для этого нужна привилегия SeDebugPrivilege. В приложенных к статье исходниках есть функция EnableDebugPrivilege, которая включает эту привилегию для текущего процесса.

Идём дальше. Теперь у нас мы научились загружать свою DLL в чужой процесс. Но для должного эффекта нам надо перехватывать DLL во всех процессах системы. Но как это сделать. Можно просто перечислять процессы через ToolHelp32 и загружать свою DLL в каждый найденный процесс. Но не приемлемо, так как во вновь созданных процессах функции не будут перехвачены. Но можно каждую секунду перечислять процессы, короче это тоже неприемлемо и очень долгая история. Самый простой метод это воспользоваться тем что предоставляет нам механизм хуков. Когда мы ставим какой-либо глобальный хук с помощью функции SetWindowsHookEx то DLL, в которой находится функция обработчик хука, загружается во все процессы, которые получают сообщения от системы через функции GetMessage и PeekMessage. Каркас DLL с перехватом функций будет выглядеть так примерно так
library HideDLL;

uses
Windows,
ExtendedAPIFunctions,
apihooktools;

function MsgProc(code:DWORD;wParam,lparam:DWORD):DWORD;stdcall;
begin
Result:=CallNextHookEx(SH,code,wParam,lparam);
end;

procedure SetWindowsHook(e:Boolean); stdcall;
var
M:THandle;
begin
if e then
begin
M:=CreateMutex(0,false,MutexName);
if GetLastErrorERROR_ALREADY_EXISTS then
begin
SH:=SetWindowsHookEx(WH_GETMESSAGE,@MsgProc,HInstance,0);
MutexHandle:=M;
end else
CloseHandle(M);
end
else
begin
UnhookWindowsHookEx(SH);
CloseHandle(MutexHandle);
end;
end;

procedure DLLEntryPoint(dwReason:DWord);
begin
case dwReason of
DLL_PROCESS_ATTACH:
begin
// StopProcess(GetCurrentProcessId);
SetWindowsHook(true);
SetProcedureHook(GetModuleHandle(‘ntdll.dll’),’ZwQuerySystemInformation’,@NewSystemFunction,@SystemFunctionBridge);
// ResumeProcess(GetCurrentProcessId);
end;
DLL_PROCESS_DETACH:
begin
// StopProcess(GetCurrentProcessId);
UnHookCodeHook(@SystemFunctionBridge);
SetWindowsHook(false);
//ResumeProcess(GetCurrentProcessId);
end;
end;
end;

begin
DllProc:= @DLLEntryPoint;
DLLEntryPoint(DLL_PROCESS_ATTACH);
end.

многопоточность downloadstring delphi

в этом случае только один поток будет загружать весь контент URL, но я бы хотел, чтобы он создавал поток для каждого элемента.

как это сделать? Есть ли способ сделать это? Я считаю, что этот метод будет более эффективным.

Чтобы создать отдельные потоки, вам необходимо привязать значение переменной url следующим образом:

Вы можете попробовать использовать шаблон ForEach для omnithreadlibrary:

Черновик выглядит так:

PS. обратите внимание, что онлайн-версия книги устарела, вам, возможно, придется обновить ее до функций в текущей версии источников omnithreadlibrary.

PPS: ваш код имеет небольшую ошибку:

Учитывая вашу реализацию DownloadString которая означает в случае ошибки HTTP, ваша функция снова и снова возвратит предыдущее значение LResult и. до тех пор, пока не произойдет загрузка без ошибок. Вот почему я изменил определение функции, чтобы быть ясным, когда происходит ошибка, и выходные данные не выдаются.

Перехват вызовов функций из динамических библиотек

Delphi , Файловая система , DLL и PlugIns

Перехват вызовов функций из динамических библиотек

Автор: Den is Com

В России запущенно производство принципиально новых программно-аппаратных средств для взлома систем под названием: «Plug & Fuck». Устройства и софт работают отлично.

// проверено и работает для WIN 9x и WinNT
// в юните на первый взгляд, есть структуры , описание которых уже есть в Дельфи , но к сожалению не
// всех и не во всех версиях Дельфи эти структуры присутствуют. Да уж, это не С

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

и сам перехват происходит так

теперь все вызовы MessageBox будут ссылаться на нашу функцию :) Надеюсь вы помните, что функции MessageBox в user32.dll нет — это только оболочка для MessageBoxА

и обратная операция — восстановление работоспособности старой функции

Привожу полностью все файлы проекта:

Статья Перехват вызовов функций из динамических библиотек раздела Файловая система DLL и PlugIns может быть полезна для разработчиков на Delphi и FreePascal.

Комментарии и вопросы

Материалы статей собраны из открытых источников, владелец сайта не претендует на авторство. Там где авторство установить не удалось, материал подаётся без имени автора. В случае если Вы считаете, что Ваши права нарушены, пожалуйста, свяжитесь с владельцем сайта.

Библиотека OmniThreadLibrary — простая многопоточность в среде Delphi

Написать интересную статью на техническую тему очень сложно. Приходится балансировать между тем, чтобы не скатиться в технические дебри и тем, чтобы совсем ничего не сказать. Сегодня я попробую в общих словах (без деталей) поговорить о том, как обстоят дела с разработкой многопоточных desktop-приложений в не столь популярной на сегодняшний день, но наверняка знакомой многим российским разработчикам среде Delphi . Статья ориентирована на НЕ новичков в программировании, являющихся при этом новичками в области создания многопоточных приложений.

Затронутая в заголовке тема очень обширна. Все, что будет написано ниже, — это даже не верхушка айсберга, это скорее полет на высоте 10000 метров над океаном, в котором эти айсберги плавают. Зачем писать такую статью? Скорее для того, чтобы обратить внимание на широкие возможности, которые уже давно доступны, но которых почему-то многие побаиваются и сторонятся.

Почему Delphi?


Зачем нам многопоточность?


Почему сложно писать многопоточный код?


Есть ли способ хоть немного упростить себе жизнь?

Где найти информацию по OTL?

  • На этом форуме.
  • В блоге автора библиотеки.
  • На странице проекта в GoogleCode.

Также автор библиотеки сейчас занимается наполнением wiki-книги про OmniThreadLibrary и многопоточность, уже готовы статьи про большинство высокоуровневых примитивов OTL.

Какие возможности предоставляет OTL?


Future

Обратите внимание, что именно обращение к vFuture.Value является моментом синхронизации основного потока с дополнительным, т.е. до тех пор, пока мы не обратимся к Value мы вообще ничего не знаем о состоянии другого потока. Как только мы вызвали Value, основной поток приостанавливается до момента завершения расчета в дополнительном потоке.

Если требуется, можно в основном потоке реализовать неблокирующее ожидание результата:

Таким образом, примитив Future позволяет асинхронно выполнить какую-то задачу и вернуть результат в основной поток именно в тот момент, в который он там потребуется.

Pipeline

В данном примере первый этап генерирует миллион чисел, передавая их по одному на следующий этап. Второй этап умножает каждое число на 3 и передает на третий этап. Третий этап суммирует результаты и возвращает одно число. Каждый Stage выполняется в своем потоке. Более того, Otl позволяет указать, какое число потоков для каждого Stage’а использовать (если одного мало) за счет простого модификатора .NumTasks(N). Возможности OTL действительно очень широки.

Базовым классом для поддержки обмена данными между этапами конвейера является класс потокозащищенной очереди — TOmniBlockingCollection. Данный класс позволяет нескольким потокам одновременно добавлять и считывать элементы. Высокая скорость работы коллекции достигается за счет хитрого управления памятью и применения блокировок на основе потокозащищенных инструкций процессора вместо блокировок на основе объектов синхронизации ОС. О подробностях реализации класса TOmniBlockingCollection можно почитать тут, тут и тут.

Заключение

Чтобы развеять опасения по поводу зрелости и надежности данной библиотеки, скажу лишь, что за счет использования Pipeline в сложном коммерческом многопользовательском приложении (не web) удалось сократить время выполнения операции над группой файлов на клиенте почти в два раза за счет разнесения по отдельным потокам обработку файлов на клиенте и их передачу на сервер. Использовать ли связку Delphi + OmniThreadLibrary в ваших проектах — решать вам ;)

Популярные статьи
Информационная безопасность Microsoft Офисное ПО Антивирусное ПО и защита от спама Eset Software


Бестселлеры
Курсы обучения «Atlassian JIRA — система управления проектами и задачами на предприятии»
Microsoft Office 365 для Дома 32-bit/x64. 5 ПК/Mac + 5 Планшетов + 5 Телефонов. Подписка на 1 год. Электронный ключ
Microsoft Windows 10 Профессиональная 32-bit/64-bit. Все языки. Электронный ключ
Microsoft Office для Дома и Учебы 2020. Все языки. Электронный ключ
Курс «Oracle. Программирование на SQL и PL/SQL»
Курс «Основы TOGAF® 9»
Microsoft Windows Professional 10 Sngl OLP 1 License No Level Legalization GetGenuine wCOA (FQC-09481)
Microsoft Office 365 Персональный 32-bit/x64. 1 ПК/MAC + 1 Планшет + 1 Телефон. Все языки. Подписка на 1 год. Электронный ключ
Windows Server 2020 Standard
Курс «Нотация BPMN 2.0. Ее использование для моделирования бизнес-процессов и их регламентации»
Антивирус ESET NOD32 Antivirus Business Edition
Corel CorelDRAW Home & Student Suite X8

О нас
Интернет-магазин ITShop.ru предлагает широкий спектр услуг информационных технологий и ПО.

На протяжении многих лет интернет-магазин предлагает товары и услуги, ориентированные на бизнес-пользователей и специалистов по информационным технологиям.

Хорошие отзывы постоянных клиентов и высокий уровень специалистов позволяет получить наивысший результат при совместной работе.

Многопоточность в своих приложениях. Часть 1

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

Можно конечно обойти вопрос использования потоков, применяя в «затяжных» циклах метод Application.ProcessMessage , позволяя приложению периодически обрабатывать очередь сообщений. Но это значительно замедлит выполнение цикла, а при работе с сетью и вовсе не эфективно, поскольку большинство сетевых функций порою очень долго выполняют свои запросы.

Благо еще с WindowsNT и Delphi 6, у нас есть возможность простой и удобной реализации многопоточности. В Delphi существует две возможности работы с потоками:

1. Взаимодействие через идентификатор, полученный при создании потока функцией createthread .

2. Создание потомка класса TThread и использование его методов.

1-ый способ, в силу особенностей работы потоков, и синхранизации их с главным потоком, как правило не удобен и потому используется не часто. Поэтому мы воспользуемся более наглядным и простым в реализации 2-ым способом.

Работа с TThread

Первое и самое важное что необходимо помнить, то что каждый поток, в том числе и главный, выполняется в отдельном адресном пространстве. Это вовсе не означает что в потоке нельзя использовать константы, переменные и даже функции объявленные в модулях программы. Без их использования тело потока выросло бы до непростительно больших размеров. Однако необходимо помнить что при инициализации потока, в его область памяти, помещаются все используемые им переменные со своими начальными значениями, и их изменение ни каким образом не влияет на содержимое их аналогов в других потоках. Более того все методы самого TThread , которые находятся в адресном пространстве родительского потока, тоже «вне его компетенции». Тут вполне логично появление вопроса, зачем вообще нужен объект, который не может повлиять ни на работу программы, ни даже на самого себя в программе?

Ответ элементарный до смешного — «не нужен». Видимо поэтому, разработчики любезно предоставили нам возможность передачи переменных в поток через его метод Create , просто объявив их в методе потомка.

constructor Create(CreateSuspennded: Boolean; MyVar: Integer); override;

( CreateSuspennded переменная объявленная в методе TThread, о ее назначении чуть ниже. )
Не стоит забывать что использовать такую возможность, для каждого потока, можно лишь однажды — при его создании. Вопрос с вводом исходных данных в поток решен, а как быть с выводом? И тут все просто. Передавать потоку следует не сами переменные (как мы помним, их изменение будет отражаться лишь внутри потока), а указатель на адрес в памяти, pointer , по которому находится переменная. И работать с переменной (тип переменной значения не имеет), используя этот указатель. Удобнее всего создать для нее «сиамского близнеца», «поселив» свою переменную того же типа, по тому же адресу. Простой пример:

list1, list2: tstrings;

list1 := TStringList.Create; //создаем первый лист

pointer(list2) := pointer(list1); //меняем адрес у лист2 на лист1

list2.Add(‘Hello World!’); //добавляем строку вроде-бы как во второй лист

ShowMessage(list1.strings[0]); //выводим содержимое из первого листа, оно-же содержимое второго)

list2.destroy; //уничтожаем второй лист, он же первый, повторный вызов предыдущей строки вызовет ошибку

Основной принцип работы с данными вродебы понятен. Вводим данные с помощью pointer , обрабатываем и выводим. Стоп, а как обрабатывать-то?)) И опять все очень просто. При создании потока, в его тело помещается его же метод Execute . Вот в нем то и нужно производить все необходимые вычисления. И снова предостережение! Вызов метода Execute напрямую не запустит новый поток а просто выполнит его в текущем, что в большинстве случаев хоть и не вызовет ошибки, но как минимум «подвесит» программу на время его выполнения. Чтобы запустить этот метод в отдельном потоке, необходимо вызвать другой его метод, метод Resume который проделывает всю необходимую работу по инициализации и запуску потока. В случае если необходимо временно приостановить работу потока используется метод Suspend . При этом поток будет находится в состоянии «Паузы», до повторного вызова Resume .
Вернемся к конструктору, который уже упоминался выше. Изначально, для TThread, он выглядит так:

constructor Create(CreateSuspennded: Boolean);

Параметр CreateSuspennded предназначен, как следует из названия, для создания потока в приостановленном виде. Для чего это нужно? Для задания некоторых методов создаваемого экземпляра класса. Например, такого как FreeOnTerminate: Boolean который, также следуя из названия, указывает классу на необходимость высвобождения памяти, занимаемой потоком после его уничтожения.

Ну, вроде все нюансы, по работе с данными, решены. Обрабытавать данные в потоке, мы уже умеем. Можно идти дальше? Можно, но есть еще один момент, забывать о котором нельзя ни в коем случае, это «совместный доступ к памяти». Вероятность того что несколько разных потоков (включая главный) одновременно будут работать с одной переменной в отдельных случаях достаточно мала, но когда это происходит наступает «время чудес», и «истина» с завидной легкостью обращается в «ложь».))) И на деле это совсем не смешно.
Это тот подводный камень, который при отладке зачастую не вызывает никаких ошибок и редко дает о себе знать. Поверьте конечному пользователю, по общеизвестному закону, «волшебство» обеспечено. Но только в том случае, если у разработчика достаточно кривые руки, что-бы он не позаботился об организации корректной совместной обработки данных в потоках.

Для того что-бы сообщить, вызвавшему потоку, об определенном этапе работы потока используется метод Synchronize(AMethod: TThreadMethod) , вызываемый внутри потока. При этом параметром AMethod выступает метод нашего потомка TThread не имеющий параметров. Этот метод будет выполнен в «родительском» потоке, при этом значение свойств объекта TThread будут синхронизированы для этих потоков, а работа нашего потока приостановлена до завершения метода Synchronize . Таким образом можно сообщать программе о возникающих ошибках, передавать промежуточные значения вычислений, сообщать о завершении работы потока, а также вносить коррективы в его работу. После завершения метода синхронизации поток возвращается к своей работе, если конечно в Synchronize он не был приостановлен или уничтожен)).

Следует отметить что уничтожение потока очень грубая и крайняя мера. Но иногда приходится идти и на это, так-что вот вам функция для уничтожения:

function TerminateThread(hThread: THandle; dwExitCode: DWORD): BOOL;

Хэндл потока можно получить методом ThreadID , а в dwExitCode достаточно указать «0′, в случае успешного уничтожения функция вернет True .

Корректно завершить работу потока можно лишь дождавшись окончания работы метода Execute . Но что если нам не очень хочется ждать пока поток полностью отработает свою «программу». Для этого предусмотрен метод Terminate . Но представьте себе, не смотря на свое звучное название, он не делает ничего для завершения потока, ну или почти ничего). Все что он делает это задает свойству Terminated , нашего компонента в потоке, значение True . И что же это дает? Да практически все что нам необходимо. Достаточно в каждом затяжном цикле, или после особо продолжительных функций и процедур, вставить проверку значения этой переменной, и вслучае если она равна True завершать работу корректно, высвободить память от созданных объектов, закрыть открытые сетевые подключения, или просто сразу вызвать Exit .

Пример реализации потока. Компонент TDownloader

В качестве примера выбран компонент для скачивания файлов из Internet, в связи с продолжительностью выполнения сетевых запросов.

Для скачивания файла в поток необходимо передать как минимум два значения — Урл файла и Контейнер для полученных данных. Ссылку логично передавать в переменной типа String , а для контейнера лучше использовать TMemoryStream , так-как он хранит данные в неизменном виде, и позволяет легко выводить их в файл или TStrings . Но для совместной обработки контейнера передаем только указатель на него.

fURL: String; //урл

MemoryStream: TMemoryStream; //наш «Сиамский близнец»

constructor Create(CreateSuspennded: Boolean; const URL: String; Stream: PMemoryStream);

constructor TDownloadThread.Create(CreateSuspennded: Boolean; const URL: String; Stream: PMemoryStream);

inherited Create(CreateSuspennded); //метод предка

FreeOnTerminate := True; //очистка при уничтожении

Pointer(MemoryStream) := Stream; //прописываем memorystream по полученному адресу

fURL := URL; //запоминаем урл

Далее определяемся с самой скачкой, пишем метод Execute .

pInet, pUrl: Pointer; //дескрипторы для работы с WinInet

Buffer: array[0..1024] of Byte; //буфер для получения данных

BytesRead: Cardinal; //количество прочитанных байт

begin //тело потока

pInet := InternetOpen(‘Dowloader’, INTERNET_OPEN_TYPE_PRECONFIG, nil, nil, 0);//открываем сессию

pUrl := InternetOpenUrl(pInet, PChar(URL), nil, 0, INTERNET_FLAG_PRAGMA_NOCACHE or INTERNET_FLAG_RELOAD, 0);//стучимся по ссылке.

repeat //качаем файл

if Terminated then //если поток необходимо завершить

Break; //в данном случае просто выходим из цикла

FillChar(Buffer, SizeOf(Buffer), 0); //заполняем буфер нолями

if InternetReadFile(pUrl, @Buffer, Length(Buffer), BytesRead) then //читаем кусок в буфер

MemoryStream.Write(Buffer, BytesRead) //пишем буфер в поток

until (BytesRead = 0); //прочитано все

MemoryStream.Position := 0; //позицию потока в ноль

if pUrl nil then //открывалось?

if pInet nil then //открывалось?

pointer(MemoryStream) := nil; //обрываем связь

Вроде-бы все? Все да не все), а как-же Synchronize ? А их я написал немного много), и решил сюда не выкладывать. Так-что для завершения изучения данного вопроса, качайте файлик. Кроме самого компонента в архиве пример его использования, и пример работы с pointer описанный выше.

Илон Маск рекомендует:  Что такое код ocicollmax
Понравилась статья? Поделиться с друзьями:
Кодинг, CSS и SQL