delphi (XE8 x64, 10, 7)

Описание: Разработка и отладка приложений. Упор на 3D-графику.

dyvniy M
Автор темы, Администратор
Администратор
Аватара
dyvniy M
Автор темы, Администратор
Администратор
Возраст: 41
Репутация: 1
Лояльность: 1
Сообщения: 3579
Зарегистрирован: Ср, 10 октября 2012
С нами: 11 лет 6 месяцев
Профессия: Программист
Откуда: Россия, Москва
ICQ Сайт Skype ВКонтакте

#1 dyvniy » Сб, 19 декабря 2015, 10:42:23

Как использовать С++ dll в делфи
http://www.gamedev.ru/code/forum/?id=138090&page=3

Код: Выделить всё

// C++
#define DLL_Export extern "C" __declspec(dllexport)

DLL_Export unsigned int* Particles()
{
    return &ps.p[0];
}

// delphi  7
function Particles(): PUnsigned; cdecl; external 'dll_try.dll'

Код: Выделить всё

#define EXPORT __declspec(dllexport)

extern "C"
{
  class EXPORT test2
  
{
  public:
    virtual void __cdecl Function1(char* str)
    {
      ::MessageBoxA(0,"test2::Function1","",0);
    }
    virtual int __cdecl Function2(int i )
    { 
      
::MessageBoxA(0,"test2::Function2","",0);
      return i;
    }

    ~test2()
    {
      ::MessageBoxA(0,"test2::~test2","",0);
    }
    test2()
    {
      ::MessageBoxA(0,"test2::test2","",0);
    }
    
  
};
  EXPORT void __cdecl CLASSFACTORY2(test2** t)
  { 
    
*= new test2();
  }
  EXPORT void __cdecl CLASSKILL2(test2* t)
  {
    delete t;
  }
}

//////////////////////////  Delphi  ///////////////////////////////
unit Unit1;
interface
uses
  Windows
, Messages, SysUtils, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls, ExtCtrls;

type
  TFoo2 
= class
    function Function1
(Str1: PChar): integer; virtual; cdecl; abstract;
    function Function2(i: integer): integer; virtual; cdecl; abstract;
  end;

type
  TForm1 
= class(TForm)
    Button2: TButton;
    Button1: TButton;
    procedure Button2Click(Sender: TObject);
    procedure FormClose(Sender: TObject; var Action: TCloseAction);
    procedure FormCreate(Sender: TObject);
    procedure Button1Click(Sender: TObject);
  private
    Foo2
: TFoo2;
    { Private declarations }
  public
    
{ Public declarations }
  end;

var
  MainForm: TForm1;

implementation

{$R *.DFM}

procedure ClassFactory2(var Foo: TFoo2); cdecl; external 'cdll.dll'
  name 'CLASSFACTORY2';

procedure ClassKill2(var Foo: TFoo2); cdecl; external 'cdll.dll'
  name 'CLASSKILL2';

procedure TForm1.Button2Click(Sender: TObject);
begin
  Foo2
.Function2(10);
end;
procedure TForm1.Button1Click(Sender: TObject);
begin
  Foo2
.Function1('huh huh, cool.');
end;

procedure TForm1.FormClose(Sender: TObject; var Action: TCloseAction);
begin
  ClassKill2
(Foo2);
end;

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

end.
 
Изображение

dyvniy M
Автор темы, Администратор
Администратор
Аватара
dyvniy M
Автор темы, Администратор
Администратор
Возраст: 41
Репутация: 1
Лояльность: 1
Сообщения: 3579
Зарегистрирован: Ср, 10 октября 2012
С нами: 11 лет 6 месяцев
Профессия: Программист
Откуда: Россия, Москва
ICQ Сайт Skype ВКонтакте

#2 dyvniy » Сб, 19 декабря 2015, 11:49:14

Про интерфейсы в delphi
http://habrahabr.ru/post/181107/
Спойлер
Особенности применения интерфейсов в Delphi
Delphi*
Интерфейсы в Delphi появились не сразу, а когда появилась необходимость поддержать работу с COM и на мой взгляд они не очень стройно вписались в язык.

Скажу честно, я как правило пользуюсь интерфейсами не для взаимодействия с внешним миром посредством механизма СОМ. И, подозреваю, что не только я. В Delphi интерфейсы нашли себе другое полезное применение.

Фактически, интерфейсы полезны в двух случаях:
Когда необходимо использовать множественное наследование;
Когда ARC (автоматический подсчет ссылок) серьезно облегчает управление памятью.

В Delphi исторически нет и не было множественного наследования в той форме, как это принято в некоторых других языках программирования (например, С++). И это хорошо.

В Delphi проблемы множественного наследования решаются интерфейсами. Интерфейс — это полностью абстрактный класс, все методы которого виртуальны и абстрактны. (GunSmoker)

И это практически так, но не совсем так! Интерфейсы очень похожи на абстрактные классы. Очень похожи, но в конечном итоге классы и интерфейсы ведут себя очень по-разному.

В связи с грядущими изменениями, то есть по мере появления ARC в новом компиляторе, тема управления жизнью Delphi-объектов получает новую актуальность, так как прогнозируемо будут новые «священные войны». Мне бы не хотелось именно сейчас резко вставать на ту или иную сторону, хочется лишь поисследовать существующие области пересечения «классического» подхода и «ссылочных» механизмов управления жизнью объекта как программисту-практику.

Тем не менее, позволю себе выразить надежду на то, что ARC в новом компиляторе даст возможность действительно воспринимать интерфейсы всего-лишь как абстрактные классы. Хотя я отношусь к подобным революционным изменениям с опаской.

Часто программисты «интерфейсных морд» к БД игнорируют вопросы управления памятью объектов, что не умаляет важность темы. По моему мнению, смешивать в работе классы и интерфейсы следует крайне осторожно. Всему виной счетчик ссылок. Для понимания этого давайте проделаем простое упражнение.

В качестве примера – форма с одной кнопкой. Сугубо тестовый пример. Не повторяйте это дома.

unit Unit1;

interface

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

type
IMyIntf = interface
procedure TestMessage;
end;
TMyClass = class(TInterfacedObject, IMyIntf)
public
procedure TestMessage;
destructor Destroy; override;
end;

TForm1 = class(TForm)
Button1: TButton;
Memo1: TMemo;
procedure Button1Click(Sender: TObject);
public
procedure Kill(Intf: IMyIntf);
end;

var
Form1: TForm1;

implementation

{$R *.dfm}

procedure TForm1.Button1Click(Sender: TObject);
var
MyClass: TMyClass;
begin
Memo1.Clear;
try
MyClass := TMyClass.Create;
try
Kill(MyClass);
finally
MyClass.Free;
end;
except
on E: Exception do
Memo1.Lines.Add(E.Message);
end;
end;

procedure TForm1.Kill(Intf: IMyIntf);
begin
Intf.TestMessage;
end;

{ TMyClass }

destructor TMyClass.Destroy;
begin
Form1.Memo1.Lines.Add('TMyClass.Destroy');
inherited;
end;

procedure TMyClass.TestMessage;
begin
Form1.Memo1.Lines.Add('TMyClass.TestMessage');
end;

end.

Запускаем, нажимаем кнопку и в Memo1 появляется следующий текст:
TMyClass.TestMessage
TMyClass.Destroy
TMyClass.Destroy
Invalid pointer operation

Destroy вызывается два раза и как результат – «Invalid pointer operation». Почему?

Один раз – это понятно. В обработчике Button1Click вызывается MyClass.Free. А второй раз откуда? Суть проблемы кроется в процедуре Kill. Разберем ход ее выполнения.

// Изначально Intf.RefCount = 0, это нормальное состояние для TInterfacedObject
// Интерфейс Intf заходит в область видимости процедуры Kill
// Выполняется Intf._AddRef, теперь RefCount = 1
procedure TForm1.Kill(Intf: IMyIntf);
begin
Intf.TestMessage;

// Интерфейс выходит из области видимости, выполняется Intf._Release
// И, так как RefCount стал равень нулю, объект уничтожается: TMyClass.Destroy
// Это и становится причиной того, что дальше все идет не так, как ожидалось.
// Дальнейшая работа с этим классом невозможна.
end;

То есть проблема в том, что у TInterfacedObject и его наследников значение счетчика ссылок равно нулю. Для объекта это нормально, но для интерфейса это признак скорой и неминуемой смерти.

Кто виноват и что делать?

Думаю, никто не виноват. Не нужно так делать. Врядли в языке без сборщика мусора можно было бы реализовать интерфейсы с управляемым временем жизни более удобно. Разве что принудить программиста явно вызывать _AddRef и _Release. Сомневаюсь, что это было бы удобнее.

Так же можно было ввести два типа интерфейсов – со счетчиком ссылок и без, но это внесло бы еще больше путаницы.

Следует понимать, что счетчик ссылок принадлежит не интерфейсам, а объекту. Интерфейсы этим счетчиком лишь управляют. Если в Delphi будет два типа интерфейсов, то как в такой ситуации вести себя объекту, который реализует два интерфейса разных типов? Здесь большой простор для поиска потенциальных подводных камней.

От счетчика ссылок объекта можно избавиться самостоятельно переопределив методы _AddRef и _Release таким образом, чтобы обнуление счетчика ссылок не вызывало освобождение объекта. Например, изменив класс из примера таким образом (чтобы класс мог наследовать интерфейс он должен реализовать три метода: _AddRef, _Release и QueryInterface):

TMyClass = class(TObject, IMyIntf)
protected
function _AddRef: Integer; stdcall;
function _Release: Integer; stdcall;
function QueryInterface(const IID: TGUID; out Obj): HResult; stdcall;
public
procedure TestMessage;
destructor Destroy; override;
end;

function TMyClass.QueryInterface(const IID: TGUID; out Obj): HResult;
begin
if GetInterface(IID, Obj) then
Result := 0
else
Result := E_NOINTERFACE;
end;

function TMyClass._AddRef: Integer;
begin
Result := -1;
end;

function TMyClass._Release: Integer;
begin
Result := -1;
end;

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

Тем не менее, в VCL переопределение счетчика ссылок используется. У наследников TComponent счетчик ссылок работает весьма замысловато.

function TComponent._AddRef: Integer;
begin
if FVCLComObject = nil then
Result := -1 // -1 indicates no reference counting is taking place
else
Result := IVCLComObject(FVCLComObject)._AddRef;
end;

function TComponent._Release: Integer;
begin
if FVCLComObject = nil then
Result := -1 // -1 indicates no reference counting is taking place
else
Result := IVCLComObject(FVCLComObject)._Release;
end;

Можно подойти к ситуации с другой стороны и немного изменить процедуру Kill, добавив const в определение параметра. В этом случае все начнет работать как следует, так как счетчик ссылок просто не будет задействован:

procedure TForm1.Kill(const Intf: IMyIntf);
begin
Intf.TestMessage;
end;

Теперь результат будет таким, то есть абсолютно ожидаемым:
TMyClass.TestMessage
TMyClass.Destroy

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

И если раньше при работе с VCL многие могли вообще никогда не сталкиваться по-настоящему с необходимостью использовать интерфейсы, то в свете новой библиотеки FireMonkey, дающей вроде-как кроссплатформенность, нужно очень внимательно следить за использованием интерфейсов внутри неё самой, не полагаясь на «идеологическую стройность» языковых возможностей, предлагаемых Embarcadero.
интерфейсы
–2

16,1k

29


@RomanYankovsky карма3,0 рейтинг0,0
Похожие публикации
+146
Видео лекций всего курса первой Школы разработки интерфейсов Яндекса 161k 1761 64
+4
Обобщенные интерфейсы в Delphi 3k 4 0
+27
Интервью с Дональдом Норманом об интерфейсах 1k 17 44
Самое читаемое
Сейчас Неделя Месяц
Facebook угрожает специалисту по безопасности, взломавшему Instagram 17
Как сделать пароль надежным и запоминающимся 22
Укрощаем UEFI SecureBoot 2
Сравниваем swift и rust 9
Как попасть на дачу президента в пять часов утра 392
Комментарий к «Как попасть на дачу президента в пять часов утра» 15
Релиз DataGrip (экс-0xDBE) 1.0 — новой IDE для SQL 42
Бэкдоры в файрволах Juniper 4
Тектограммы — новое поколение майндмапов 15
Angular2 теперь «бета» 3
Вопросы по теме
Какие есть визуальные инструменты для создания архитектуры БД? 4
Как красиво сделать список услуг? 2
Интерфейс слайдера для управления диапазонами букв в русском алфавите - как и чем? 1
С чего начать углубление в Ux/Ui? 2
Как делать такую gif-анимацию? 5
Комментарии (51)

GunSmoker 27 мая 2013 в 14:45 +8
Если основное назначение объекта — реализация интерфейса, то с этим объектом нужно работать исключительно через интерфейсные переменные.

Иными словами, вместо:
procedure TForm1.Button1Click(Sender: TObject);
var
MyClass: TMyClass;
begin
MyClass := TMyClass.Create;
try
Kill(MyClass);
finally
MyClass.Free;
end;
end;

должно быть:
procedure TForm1.Button1Click(Sender: TObject);
var
MyClass: IMyClass;
begin
MyClass := TMyClass.Create;
Kill(MyClass);
end;


Это стандартное правило хорошего тона при работе с интерфейсами в Delphi.
RomanYankovsky 27 мая 2013 в 14:47 –6
Спасибо, кэп! Но я хотел показать ситуации, когда так красиво все не получается. И они вполне реальны, я не так уж и долго их придумывал.
GunSmoker 27 мая 2013 в 15:04 +5
Так вот дело в том, что в статье не написано, зачем нужен именно MyClass: TMyClass. И поэтому решение в виде переопределения _AddRef/_Release вызывает недоумение.

P.S. Кстати «Kill(const Intf: IMyIntf)» — не решение в общем случае, поскольку код метода Kill может передавать интерфейс в другие методы, которые изменяют счётчик ссылок.
RomanYankovsky 27 мая 2013 в 15:08 (комментарий был изменён) –2
Это статья, а не научный трактат. Я вполне ясно написал, что смешивать работу с классами и интерфейсами — это идея обычно не очень хорошая и наглядно показал почему. И дальше показал способы обхода этой проблемы, когда это действительно нужно.

Вот P.S. — это первая реплика по существу. Спасибо.
Rouse 27 мая 2013 в 19:00 –1
Все-же перекрывать счетчик ссылок огромный моветон, да и чревато.
Тут лучше переосмыслить подачу материала, а то так и получается что суть статьи — попытка исправить изначально неправильный код, причем не в месте возникновения ошибки.
А это, по сути — костыль :)
RomanYankovsky 27 мая 2013 в 19:04 0
>> Все-же перекрывать счетчик ссылок огромный моветон, да и чревато.

Расскажи это авторам класса TComponent :) Этого примера не достаточно? Слишком мелкий?

В целом, проблема появляется там, где изначально не было сделано все на интерфейсах (ну вот так бывает) и вдруг понадобилось множественное наследование в той или иной форме.
Rouse 27 мая 2013 в 19:11 0
Хм, Ром, я не думаю что в данном случае имеет смысл ровняться на авторов VCL :)
У них немного другие задачи :)
RomanYankovsky 27 мая 2013 в 19:20 +1
Дело-то не в равнении на кого-то. Это просто пример, когда вроде как неглупые люди, имея огромную готовую иерархию классов и столкнувшись с необходимостью приделать к ней интерфейсы, не бросились VCL целиком переписывать, исправляя «изначально неправильный код», а пошли на такую вот сделку с собственной совестью. И с такими ситуациями сталкиваются не только они. Не все готовы позволить себе роскошь выбросить все и переписать.

И вроде как из статьи ясно следует, что «так делать не стоит, но если вдруг реально нужно, то вот чем это чревато и вот как можно это узкое место малой кровью обойти». Видимо, действительно что-то непонятно у меня получилось, раз возникла такая неожиданная реакция. Ладно, это мой первый пост на хабре, в следующем (если он будет) постараюсь писать подробнее.
Rouse 27 мая 2013 в 19:25 0
Да, тут действительно немного сумбурно получилось. Я достаточно много раз расширял уже готовые и работающие десяток лет классы интерфейсами и ни разу не потребовались такие вот «допманевры» :)
Не переживай — первый блин все таки :)
RomanYankovsky 27 мая 2013 в 19:34 0
Ну у всех разная практика. Кто-то вообще никогда интерфейсы не использовал, кто-то только через них все и делает, кому-то приходится на костылях бегать. Это все не повод игнорировать проблемы «инвалидов» :)
Nashev 27 мая 2013 в 20:12 +1
Если объекту с интерфейсами нужно, чтобы на него ссылались и как на экземпляр класса, его конструктор должен сам вызвать AddRef, а для завершения его жизни как экземпляра и опускание в свободное интерфейсное плавание должен быть метод, который вызовет release.
Rouse 27 мая 2013 в 20:25 0
Немного неверный подход, объекту ничего не должно быть нужно — он вещь в себе, реализующая некий функционал, который ничего не знает о вызывающем его коде. Точка.
К примеру, если по такому принципу реализовывать код для расширений оболочки, то можно наткнутся на большие неприятности из-за игр со счетчиком ссылок.
Если внешний код использует объект не правильно, это проблема только внешнего кода.
Nashev 27 мая 2013 в 20:30 0
Вызывающий код знает всё о нуждах объекта и правилах его использования. Если объект рассчитан на то, что на него будут ссылаться и как на экземпляр, и как на интерфейсы, он будет иметь метод для отпускания прямой ссылки, и вызывающий код должен будет им пользоваться.
Rouse 27 мая 2013 в 20:32 (комментарий был изменён) 0
Но позвольте, любой объект, реализующий интерфейс, будучи применен правильным образом не нуждается ни в каких дополнительных методах. Где я не прав? :)
Взять тот-же TInterfacedObject
Nashev 27 мая 2013 в 20:38 0
В том, что заранее сами решаете за любые объекты, каков правильный способ их применения, лишь на основании того, что они реализуют интерфейс.

А на самом деле, правильное применение диктуется назначением объекта, его спецификой.
Rouse 27 мая 2013 в 20:42 0
Допустим, тогда зачем реализация компонента должна мне мешать?
Разве конструктор класса должен решать где увеличить количество ссылок на экземпляр, или все-же программист, создающий экземпляр данного класса?
Nashev 27 мая 2013 в 20:53 0
Почему мешать? Помогать же! Сама за Вас изначально подкрутит, и удобный метод для освобождения даст…
Rouse 27 мая 2013 в 21:09 0
Помогать чем? Вмешательством в логику кода?
Допустим на пальцах, есть DCU, доступа к исходникам у меня нет, поправить ничего не могу.
Я создаю наследника от такого вот «дружественного класса» в котором реализую мьютекс (или любой другой объект синхронизации).
Вы действительно хотите мне сказать, что за удаление объекта синхронизации реализованного в деструкторе класса, должен отвечать ваш код, который сам решит когда ему делать _Release?

Хорошо, другой пример — Вы когда либо реализовывали расширения оболочки?
Я периодически сталкиваюсь с различными их вариациями, где люди делают принудительный _AddRef самому себе, думая что в деструкторе спасет _Release.
А потом появляются вопросы — а почему проводник с моим расширением падает :)
Nashev 27 мая 2013 в 21:27 0
Если Вы делаете использование готового объекта — следуйте правилам этого объекта.

Не пользуетесь таким объектом иначе, чем через интерфейс — значит, нарушаете правила его использования. Воспользуйтесь прямой ссылкой хоть раз, как минимум чтобы освободить её.

Если Вы делаете объект под конкретное использование — следуйте правилам этого использования. Если оболочка пользуется расширением только через интерфейс, и предполагает освобождение расширения по своему вызову release — расширение должно это обеспечить. Не обеспечило — ССЗБ.

Вопросы появляются, когда документация непонятна, когда не понятно то ожидание, которое нужно обеспечить…
Rouse 27 мая 2013 в 21:40 0
Эх…
Видимо у меня не получилось донести суть.
Попробую по другому.
У объекта есть конструктор и деструктор. Это все что он предоставляет.
Нет более никаких правил его использования.
Если используется работа с интерфейсной частью объекта — используйте ее.
Но не нужно вмешиваться во время жизни объекта — сие есть плохо.
Правило простое: «Я тебя породил — я тебя и убью».
Птицы фениксы, рождающиеся посредством инкремента ссылок в конструкторе — есть жестко и болезненно.
Nashev 27 мая 2013 в 21:52 0
Вы говорите с точки зрения «стандартного» подхода. Вот этого Вашего «простого правила». Конечно, следовать ему хорошо. Когда его всё знают, все ему следуют и все случаи в него укладываются.

Но вот есть конкретная ситуация, в него не укладывающаяся: объекты Delphi, для которых есть два способа на них сослаться. Тут вполне резонно отказаться от стандартного подхода, и воспользоваться другим подходом, обладающим соответствующей двойственностью.

Я к тому, что не стоит ожидать, что все и везде пользуются тем подходом, который Вы себе заучили как стандарт, и тогда случаи несоответствия этому подходу будут лишь местным колоритом, а не жестоким и болезненным ударом в спину. Это вопрос ожиданий и готовности, вопрос восприятия.
Rouse 27 мая 2013 в 22:21 0
Ну это не я себе заучил, а как Вы сами сказали — это «стандарт».
В принципе возможность выстрелить себе в ногу не запрещается, но и не приветствуется :)
Nashev 27 мая 2013 в 23:20 0
В том то и дело, что у любого утверждения, в том числе и у стандарта, есть область применимости и её границы. Их надо знать. Абсолютных правил нет. Бывает, даже и в ногу приходится выстрелить.

Будьте гибче, не костенейте.

Например ещё бывают фабрики объектов, которые порождают объекты, но не убивают их. И это тоже законный подход.
Nashev 27 мая 2013 в 20:45 0
Кстати, яркий пример назначения объектов, которое может потребовать двойного подхода — это когда интерфейс для каких-либо целей реализует компонент, который нужно положить на форму в в дизайн-тайме, и отвечать за срок его жизни должен его Owner. И при этом нужно иметь возможность попользоваться его интерфейсами. В этом случае и метод освобождения не понадобится, объект так и проживёт всю свою жизнь с лишней единичкой в счётчике.
Nashev 27 мая 2013 в 20:50 0
Или без счётчика вовсе (с заблокированной реализацией), что тоже не всегда удобно.
Rouse 27 мая 2013 в 20:55 0
Ну как-же без счетчика-то?
В статье то именно он и упоминается :)
Nashev 27 мая 2013 в 21:00 0
Без счётчика — это как в TComponent сделано, когда он всё время -1 хранит и показывает.

В статье расписан случай, когда счётчик всё же нужен, и ссылки нужны обоих видов. И я про него же говорил.
Rouse 27 мая 2013 в 21:12 0
Да не хранит он ничего :)
Вы с какой версией Delphi работаете? Я просто перед глазами Classes от семерки держу и соответственно по нему и описываю картинку :)
Nashev 27 мая 2013 в 21:30 0
Ну, не хранит, да. Оговорился.
-1 возвращает как результат вызова addref и release, ничего не считая. Поэтому и говорю, что счётчик заблокирован.
Rouse 27 мая 2013 в 20:53 0
Ну собственно именно этим и занимается TComponent, была в свое время хорошая статья даже о том, почему перекрывался _AddRef в данном случае на кодецентрале (или как он там назывался, когда еще площадкой не эмбаркадеро и не кодегир владели)
Rouse 27 мая 2013 в 20:39 0
Немножко дополню, ибо я не раскрыл суть вопроса.
TForm — всем известный и понятный класс наследуемый от…

TComponent = class(TPersistent, IInterface, IInterfaceComponentReference)


… дальним предком, разумеется.
Он как-бы никогда ни у кого не вызывал вопросов, хотя вот оно самое — интерфейсы в наличии.
Как-то ни у кого не появилась мысль в перекрытии счетчика ссылок данного экземпляра класса.
Nashev 27 мая 2013 в 20:48 0
Там счётчик ссылок заблокирован по умолчанию… если он нужен работающий— перекрывают…
Rouse 27 мая 2013 в 20:59 (комментарий был изменён) 0
Судя по коду VCL — ваше утверждение не верно :)
Nashev 27 мая 2013 в 21:02 0
Этот код в статье приведён. У Вас часто FVCLComObject в проектах инициализирована? У меня — нет…
RomanYankovsky 27 мая 2013 в 21:28 0
Насколько я понимаю, этот код не может привести к уничтожению самого объекта независимо от значения FVCLComObject.
Nashev 27 мая 2013 в 21:33 0
Да. Тут компонент — обёртка над FVCLComObject, стабильная и неуничтожаемая с этим ком-объектом. Это для поддержки обёрток над ActiveX сделано.
RomanYankovsky 27 мая 2013 в 21:24 0
В том-то и дело! Вот возьмем простой пример:

type
ISerializable = interface;

TSomeClass = class(TInterfacedObject, ISerializable);
TSerializableForm = class(TForm, ISerializable);

var
SerClass, SerForm: ISerializable;
begin
SerClass := TSomeClass.Create; {1}
SerForm := TSerializableForm.Create(nil); {2}
// etc
end;

Ты ведь понимаешь, что интерфейс здесь один и тот же (ISerializable), но в первом случае у него будет счетчик ссылок, а во втором — нет! И если про это забыть, последствия могут быть весьма интересными.

Интерфейсы в Delphi — это не такая простая тема, чтобы пытаться свести ее к разговору о пользе всего хорошего против всего плохого :)
Nashev 27 мая 2013 в 21:36 0
Да, про то, что наследники TComponent — изначально задуманы как обёртки над реализациями интерфейса, а не сами реализации — забывать нельзя. Как и про то, что у конкретных компонентов это может быть и иначе…
Nashev 30 мая 2013 в 20:36 0
У меня, кстати, это вот так вот оформлено:

TMyObject = class(какой-то базовый класс, IUnknown, какие-то нужные интерфейсы)
private
FReferenceCount: Integer;
FOnlyInterfaseReferences: Boolean;
.....
procedure SetOnlyInterfaseReferences(const Value: Boolean);
public
{ IUnknown }
function _AddRef: Integer; stdcall;
function _Release: Integer; stdcall;
function _ForgetRef: Integer;
.....
class function NewInstance: TObject; override;
procedure BeforeDestruction; override;
.....
property OnlyInterfaseReferences: Boolean read FOnlyInterfaseReferences write SetOnlyInterfaseReferences;
end;

class function TMyObject.NewInstance: TObject;
begin
Result := inherited NewInstance;
with TMyObject(Result) do
begin
FOnlyInterfaseReferences := True; // как-будто по-умолчанию оно так...
OnlyInterfaseReferences := False; // а в нашем случае мы меняем. И тем самым увеличиваем счётчик.
end;
end;

procedure TMyObject.BeforeDestruction;
begin
inherited BeforeDestruction;
// Release the constructor's (NewInstance) implicit refcount
OnlyInterfaseReferences := True;
end;

procedure TMyObject.SetOnlyInterfaseReferences(const Value: Boolean);
begin
if FOnlyInterfaseReferences <> Value then
begin
FOnlyInterfaseReferences := Value;
if Value then
_ForgetRef
else
_AddRef;
end;
end;

function TMyObject._AddRef: Integer;
begin
Result := InterlockedIncrement(FReferenceCount);
end;

function TMyObject._ForgetRef: Integer;
begin
Result := InterlockedDecrement(FReferenceCount);
end;

function TMyObject._Release: Integer;
begin
Result := _ForgetRef;
if (Result = 0) and not (csDestroying in ComponentState) then
Destroy;
end;


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

А если интерфейсные ссылки делались, и ещё будут нужны — нужно присвоить OnlyInterfaseReferences := True; Тогда объект освободится при освобождении последней интерфейсной ссылки.
Maccimo 31 мая 2013 в 03:02 0
И зачем вот это всё?
Нужен подсчёт ссылок — наследуемся от TInterfacedObject, не нужен — наследуемся от TInterfacedPersistent.
Использование полей/переменных с интерфейсным типом и подсчёт ссылок вещи ортогональные.
Nashev 31 мая 2013 в 03:12 (комментарий был изменён) 0
Базовый класс может быть нужен существенно более функциональный, нежели пустышка TInterfacedObject, а работа с экземпляром ещё и через интерфейсы с подсчётом ссылок — нужна.

Вещи хоть и ортогональные, но вполне элегантно совместимые.
Maccimo 31 мая 2013 в 04:13 (комментарий был изменён) 0
Мой вопрос был в том, зачем вам нужна возможность включать/выключать подсчёт ссылок в runtime, если нужное поведение можно определить в подклассе, перекрыв AddRef / Release.
Использовать подсчёт ссылок где-то, кроме COM, где без него никак, IMHO лишняя головная боль.
Nashev 31 мая 2013 в 11:28 0
У меня в этом коде нет возможности выключать подсчёт.
DmitryKoterov 27 мая 2013 в 23:29 +1
ИМХО, в Delphi явные проблемы с архитектурой языка (видимо, с архитектором?).

Начались они еще тогда, когда была зачем-то выбрана ошибочная идеология «конструктор класса не только инициализирует объект, но вначале и выделяет для него память». В результате нее в каждом конструкторе самые первые ассемблерные команды говорят примерно следующее: «память для меня уже выделил производный класс? если нет, то выделить» (в этом несложно убедиться, дизассемблер в IDE отличный). В других яэыках выделение памяти и инициализация разделены (например, в c++ выделение — operator new, тоже переопределяемый, кстати, а инициализация — конструктор), и там этой корявости с if-ом нет просто по определению. И это очень логично. Но нет, им надо было идти «своим путем».

Теперь вот с интерфейсами чего наворотили… Ну при чем тут, скажите на миллсть, интерфейсы и счетчик ссылок? Это же совершенно разные концепции. Почему тогда не скрестили интерфейсы с, например, дефрагментатором диска — а что, создаешь объект класса, имплементирующего интерфейс, и у тебя диск дефрагментируется, удобно ведь!

Жаль, что при таком потрясающе низком барьере на вход и настолько удобной IDE, быстром компиляторе, элегантной концепции модульности и в целом позитивной карме в языке допускают такие ляпы, которые невозможно будет исправить в будущем с сохранением совместимости. Они просто вгоняют его в могилу. Вирта на них нет!
Maccimo 28 мая 2013 в 03:04 +1
Теперь вот с интерфейсами чего наворотили… Ну при чем тут, скажите на миллсть, интерфейсы и счетчик ссылок? Это же совершенно разные концепции. Почему тогда не скрестили интерфейсы с, например, дефрагментатором диска — а что, создаешь объект класса, имплементирующего интерфейс, и у тебя диск дефрагментируется, удобно ведь!
Толсто.
Претензии не по адресу, подсчёт ссылок навязан контрактом IUnknown за авторством Microsoft.
Nashev 30 мая 2013 в 20:10 0
Подсчёт ссылок навязан тем, что интерфейс, базовый для интерфейсов COM, сделали базовым для всех интерфейсов вообще. Но ведь не COM-ом единым!

Изначально, в C++, интерфейсом мог служить любой абстрактный класс, который вовсе не обязан поддерживать IUnknown.
GunSmoker 28 мая 2013 в 07:58 +1
Архитектор Delphi — это как бы Андерс Хейлсберг. Тот самый, который «архитектор C#». Интерфейсы в Delphi появились в аккурат в момент его ухода в Microsoft. И введены они были ради поддержки COM (выше про IUnknown уже сказали), так что ничего удивительного в их реализации нет.
Nashev 30 мая 2013 в 20:06 0
Забавно, кстати, что сейчас на тех же интерфейсах всё MacOS API в дельфи XE4 портировано. Не знаете случаем, там так получилось из-за одинаковой опоры на C++, или что-то в дельфовых инетрфейсах подкручивать под макось пришлось?
DmitryKoterov 28 мая 2013 в 08:31 0
Хорошо еще, что они решили добавить конструкцию в язык, предназначенную для COM, а не для кассетного магнитофора ZX Spectrum. А то могли бы добавить метод «перемотать пленку» в каждый объект, и пришлось бы всем с этим жить.
vpbar 28 мая 2013 в 10:01 +1
По-моему, слишком сложно все. И текст, и подход.
Если по хорошему не получается и надо оставить доступ к объекту и через интерфейс и через переменную объекта, то не проще ли явно вызвать _AddRef? Такой подход избавит от необходимости другим клиентам помнить как там создается и работает со счетчиком этот класс TMyClass. Можно будет перед ручным освобождением объекта проверить счетчик ссылок и ругнуться если остались еще интерфейсы ссылающиеся на объект. А можно сделать _Release обнулить переменную объекта и отдать объект в руки автоматического подсчета ссылок. Больше гибкости и можно выбирать в зависимости от конкретной задаче.
Жаль вы не написали пример зачем вам понадобился такой двойной доступ к объекту.
Мне в моей практике всего пару раз пришлось навешивать интерфейс на уже существующий класс. Переносить все используемые методы в интерфейсы и переделывать код работающий с этим классом не было времени и я обошелся ручным вызовом _AddRef/_Release.
RomanYankovsky 28 мая 2013 в 10:38 +1
Хорошо, помимо примера с TComponent из VCL приведу еще и от себя пример.

Могу привести пример простой очень. Однажды понадобилось из существующих классов «доставать» интерфейс с помощью Supports, а затем вызывать метод этого интерфейса. Задача вроде простая, но как мы оба понимаем все это ведет к смерти объекта. У меня тоже не было времени переделывать работающий код и я решил вопрос просто убрав счетчик ссылок. Ручное управление счетчиком тоже вариант, но мне мой показался проще. Тем более что если классы унаследованы не от TInterfacedObject (а так часто бывает), то в любом случае счетчик ссылок пришлось бы реализовывать самостоятельно.

Такие ситуации действительно бывают. И глупо сводить все «вот так положено и ни шагу в сторону». Проблема есть. А то что вы предлагаете другой способ ее решить — это здорово. Такие комментарии добавляют ценности ко всему обсуждению.
Изображение

dyvniy M
Автор темы, Администратор
Администратор
Аватара
dyvniy M
Автор темы, Администратор
Администратор
Возраст: 41
Репутация: 1
Лояльность: 1
Сообщения: 3579
Зарегистрирован: Ср, 10 октября 2012
С нами: 11 лет 6 месяцев
Профессия: Программист
Откуда: Россия, Москва
ICQ Сайт Skype ВКонтакте

#3 dyvniy » Пн, 11 января 2016, 21:46:14

Указатели и ссылки
В сравнении с С: @ вместо &, ^ вместо *
Спойлер
> Значит, @P и P^ - одно и то же?

Нет, конечно. @P - это адрес, по которому расположена переменная P, причем сама эта переменная может быть любого типа. P^ - это содержимое памяти по адресу, который содержится в переменной P, причем сама эта переменная должна быть типа "указатель". Если он типизированный, то компилятор сам знает, как ему надо трактовать это содержимое, а если нетипизированный, то компилятору надо об этом сообщить (например, приведением типа).

Пример. Пусть мы имеем такой кусок кода:

Код: Выделить всё

var 
I
: Integer; 
P
: Pointer; 
G
: ^Integer;
begin 
:= 27; 
P
^ := I; 
:= @I;
end;

И пусть переменную I компилятор расположил в памяти по адресу, например $C8E6F4D2. Тогда выражение @I даст $C8E6F4D2 (и это же число будет содержать переменная P), а выражение Integer(P^) даст 27 (и это же число будет содержать переменная I).
Изображение

dyvniy M
Автор темы, Администратор
Администратор
Аватара
dyvniy M
Автор темы, Администратор
Администратор
Возраст: 41
Репутация: 1
Лояльность: 1
Сообщения: 3579
Зарегистрирован: Ср, 10 октября 2012
С нами: 11 лет 6 месяцев
Профессия: Программист
Откуда: Россия, Москва
ICQ Сайт Skype ВКонтакте

#4 dyvniy » Пн, 11 января 2016, 22:10:09

Про mysql на локальном компьютере
http://delphi-z.ru/195-mysql-na-lokalnom-kompyutere.html
Спойлер
MySQL на локальном компьютере
Дата: 29.10.2011 | Комментариев: 0 | Просмотров: 6705
Программисты, работающие с базами данных, хорошо знают возможности MySQL-сервера. А возможности эти почти безграничны. На MySQL работает 90% сайтов в интернете. Связка PHP + MySQL не имеет равных по популярности. Поддержка хранимых процедур, триггеров, движки InnoDB и MyISAM и многое-многое другое... На крупных предприятиях работают серверы MySQL с огромными объемами данных.

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

Но бывают программы, которым не нужен сервер. В программе работает локально один человек, но использует очень большие объемы данных. "Заменители" BDE типа Absolute DB, Accuraser, Turbo DB, EasyTable и прочие для таких целей слишком слабы. Они не расчитаны для больших объемов данных. SQLite имеет мало типов полей и слишком ограниченные возможности. Для подобных целей хорошо подходит Firebird Embedded server. Но если про настройку Firebird Embedded написана куча статей, то про MySQL Embedded информации нет вообще, не считая нескольких строк в справке компонентов MyDAC.
Можно ли написать программу с использованием MySQL, которая будет запускаться с флешки на ЛЮБОМ компьютере?

Что понадобится

1. Непосредственно IDE (я использую Delphi 2010)

2. Установленные в IDE компоненты Data Access Components for MySQL Developer Edition (MyDAC). Скачать их можно у нас.

3. Файл libmysqld.dll. Это и будет наш сервер. Скачать его можно у нас.

4. Из папки ранее установленного сервера MySQL следующие папки:
\share\charsets\ с файлами кодировок. Кодировки хранятся в файлах *.xml. Ненужные можно удалить для уменьшения веса.
\share\english\ с файлом errmsg.sys для сообщений об ошибках.
\data\mysql\ - это база данных mysql, в которой содержатся данные сервера, пользователи, зоны времени, хранимые процедуры, справка и т.д. Кроме того в папке \data\ будут храниться все базы данных.

Где взять эти папки? Из любого установленного ранее MySQL-сервера. Например, из установленного Денвера. Свою базу mysql я не могу выложить на сайт, т.к. производил в ней много изменений в настройках.

Внимание! На сайте Devart написано, что официально MySQL Embedded не поддерживается для MySQL версии 5.0. Они рекомендуют использовать версии 4.1, 5.1 и другие. Может быть сейчас что-то изменилось и с версией 5.0 все будет работать как положено, но я не знаю, не проверял.

Создание приложения

1. Запустите Delphi, создайте новый проект и сохраните его, например, в папке D:\Test.

2. Положите файл libmysqld.dll в папку с проектом (туда где будет exe-файл)

3. В папке проекта создайте папку data (D:\Test\data\) и скопируйте в нее папку mysql вместе с содержимым (D:\Test\data\mysql\)

4. В папке проекта создайте папку share и скопируйте в нее две папки charsets (D:\Test\share\charsets\) и english (D:\Test\share\english\) вместе с содержимым.

5. На форму проекта положите компоненты MyEmbConnection, MyQuery, DBGrid, DataSource и Button

MySQL на локальном компьютере
(рис. 1).


Для наглядности я здесь не изменял имена компонентов, но вы никогда так не делайте. Давайте компонентам осмысленные имена.

6. В свойствах компонентов свяжите их

DBGrid1.DataSource => DataSource1;
DataSource1.DataSet => MyQuery1;
MyQuery1.Connection => MyEmbConnection1;


7. Создайте текстовый файл с таким текстом

CREATE DATABASE DB_TEST;

CREATE TABLE DB_TEST.TEST_TABLE (
`ID` int(11) NOT NULL AUTO_INCREMENT,
`NAME` varchar(50) DEFAULT NULL,
PRIMARY KEY (`ID`)
) ENGINE=MyISAM AUTO_INCREMENT=1 DEFAULT CHARSET=cp1251;

INSERT INTO DB_TEST.TEST_TABLE VALUES
(1, 'Запись 1'),
(2, 'Запись 2'),
(3, 'Запись 3'),
(4, 'Запись 4'),
(5, 'Запись 5');


8. Сохраните этот файл в папке проекта с именем abc.sql.

9. Установите следующие свойства компонента MyEmbConnection

LoginPromt = > False
BaseDir = > '.'
DataDir = > 'data'
Option-Charset = > 'cp1251'


10. Так как вы будете работать только с высокоскоростными таблицами MyISAM, таблицы InnoDB вам не понадобятся. Кликните два раза по компоненту MyEmbConnection и установите галочку в пункте Disable the InnoDB storage engine.

MySQL на локальном компьютере
Рис. 2


Строка подключения должна выглядеть примерно так:

--basedir=.
--datadir=data
--skip-innodb


Увидеть ее можно кликнув по кнопке Advanced на закладке Params (Рис. 2).

11. Проверьте возможность соединения с сервером, установив свойство компонента
MyEmbConnection1.Connected = > True;

Если соединение установилось - все ОК. Верните свойство Connected в False, т.к. в скомпилированном приложении сервер не разрешит соединиться второй раз.

Для события onclick кнопки Button1 напишите следующий код:

procedure TfrmMain.Button1Click(Sender: TObject);
begin
with MyQuery1 do
begin
Close;
SQL.LoadFromFile('abc.sql');
ExecSQL;

Close;
SQL.Text := 'SELECT * FROM DB_TEST.TEST_TABLE';
Open;
end;
end;


Если все сделали правильно, в папке \data\ создалась папка базы данных \DB_TEST\ с файлами таблиц. Закройте скомпилированное приложение и установите свойство компонента
MyQuery1.DataBase = > 'DB_TEST';

Это сделано для того, чтобы в sql-запросах в дальнейшем обращаться непосредственно к таблице без указания базы данных.
(select * from Table вместо select * from Database.Table).

MySQL на локальном компьютере
Рис. 3


Стоила ли овчинка выделки?

Давайте проверим для чего все это делалось.

Измените код кнопки Button1 на такой

procedure TfrmMain.Button1Click(Sender: TObject);
var
i: Integer;
begin
with MyQuery1 do
begin
Close;
SQL.Text := 'SELECT * FROM DB_TEST.TEST_TABLE';
Open;
DisableControls;
for i := 0 to 50000 do
begin
Insert;
FieldByName('name').AsString := 'Запись ' + inttostr(i);
Post;
end;
EnableControls;
end;
ShowMessage('Готово');
end;


Скомпилируйте приложение и выполните код. У меня добавление 50 000 записей заняло около 4-х секунд. Согласитесь, что для локальной БД это более чем хорошо.

Итак, вся мощь МySQL у вас в одной папке. Пишите, дерзайте...
Удачи.

Скачать исходник

Delphi_Coder
Октябрь, 2011
Изображение

dyvniy M
Автор темы, Администратор
Администратор
Аватара
dyvniy M
Автор темы, Администратор
Администратор
Возраст: 41
Репутация: 1
Лояльность: 1
Сообщения: 3579
Зарегистрирован: Ср, 10 октября 2012
С нами: 11 лет 6 месяцев
Профессия: Программист
Откуда: Россия, Москва
ICQ Сайт Skype ВКонтакте

#5 dyvniy » Ср, 20 января 2016, 22:04:30

Потокобезопасный код
http://tregets.narod.ru/str8.html
Спойлер
Глава 8. Потокобезопасные классы в Дельфи и приоритеты.

Содержание:
* Для чего писать потокобезопасные классы?


* Типы потокобезопасных классов.

* Потокобезопасная инкапсуляция или наследники существующих классов.

* Классы управления потоками данных.

* Мониторы.

* Классы Interlock (взаимоблокировки).

* Поддержка потоков в VCL.

* TThreadList

* TSychroObject

* TCriticalSection

* TEvent и TSimpleEvent

* TMultiReadExclusiveWriteSynchroniser.

* Руководство разработчика потокобезопасных классов.

* Управление приоритетами.

* Что такое приоритет? Как это делается в Win32.

* Какой приоритет дать моему потоку?
Для чего писать потокобезопасные классы?
В простых программах на Delphi, написанных начинающими работать с потоками, синхронизация часто является частью логики приложения. Как продемонстрировано в предыдущей главе, очень легко допустить неуловимые ошибки в логике синхронизации, а разработка отдельной схемы синхронизации для каждого приложения требует большого труда. Лишь немногие механизмы синхронизации используется неоднократно: почти весь потоки, связанные с вводом-выводом, обмениваются данными через общие буферы, и часто используются списки и очереди без встроенной синхронизации. Эти факторы указывают, что следует уделить внимание построению библиотеки потокобезопасных объектов и структур данных: проблемы, возникающие в межпотоковом обмене- непростые, но несколько общих решений подойдут почти во всех случаях.
Иногда необходимо написать потокобезопасный класс, поскольку никакой другой метод неопустим. Код в DLL, который имеет доступов к уникальным системным данным, должен содержать синхронизацию потоков, даже если DLL и не содержит никаких объектов потоков. Так как большинство программистов на Delphi использует средства языка (классы) для обеспечения возможности модульной разработки и повторного использования кода, эти DLL будут содержать классы, и эти классы должны быть потокобезопасными. Некоторые могут быть довольно простыми, как, например,вышеописанные общие буферные классы. Тем не менее, вполне вероятно что некоторые из этих классов могли осуществлять блокировку ресурсов или другие механизмы синхронизации специфическими средствами ради решения конкретной задачи.
Типы потокобезопасных классов.
Классы могут быть самыми разными, программисты с определенным опытом в Delphi знают, что концепция класса используется многими способами. Некоторые классы используются в основном как структуры данных, другие - как обертки для упрощения сложной внутренней структуры. Иногда семейства совместно работающих классов использутся, чтобы обеспечивать гибкость в достиженеии общей цели, как хорошо демонстрирует механизм потков данных (streams) в Delphi. Аналогичное разнообразие существует и среди потокобезопасных классов. В некоторых случаях классификация может получиться немного расплывчатой, но тем не менее, можно выделить четыре различных типа потокобезопасных классов.
Потокобезопасная инкапсуляция или наследники существующих классов.
Это самый простой тип многопоточного класса. Обычно расширяемый класс имеет довольно ограниченную функциональность и самодостаточен. В простейшем случае создание поттокобезопасного класса может состоять просто из добавления мьютекса и двух дополнительных функций - методов класса, Lock (блокировка) и UnLock. Кроме того, функции, манипулирующие данными класса, могут выполнять блокировку и операции разблокировки автоматически. Какой метод использовать, зависит в основном от количества возможных операций с объектом, и желания программиста самостоятельно создать функции блокировки для обеспечения атомарности составных действий.
Классы управления потоками данных.
Это небольшое расширение вышеуказанного типа, обычно они состоят из буферных классов: списков, стеков и очередей. Дополнительно к поддержке атомарности, эти классы могут выполнять автоматическое управление потоками данных в работающем с буфером потоке. Это часто состоит в задержке потоков, пытающихся читать из пустого буфера или писать в заполненный. Разработка таких классов более полно обсуждается в Главе 10. Множество оперыций может поддерживаться одним классом: с одной стороны, будут обеспечиваться полностью неблокировки действия, с другой стороны, все действия могут блокироваться если они не могут успешно завершить. Компромисс часто достигается, когда действия асинхронны, но обеспечивают обратную связь или обмен сообщениями, когда прежде неудачное действие, вероятно, достигнет цели. Сокеты Win32 API является хорошим примером интерфейса потока данных, которые осуществляют все вышеуказанные возможности в том,что касается управление потоками.
Мониторы.
Мониторы являются логическим шагом вперед от классов управления потоками данных. Они обычно допускают параллельный доступ к данным, которые требуют более сложной синхронизации и блокировки, чем обеспечивает простая потокобезопасная инкапсуляция существующего класса Delphi. Системы управления базами данных относятся к наиболее высокой категории мониторов: обычно в них предусмотрена сложная блокировка и схема управления транзакциями для обеспечения максимальной степени параллелизма при доступе к общим данным с минимальным ущербом производительности из-за конфликтов потоков. СУБД работают довольно специфическим образом, используя управление транзакциями для тщательного контроля над составными операциями, и при этом они также обеспечивают гарантии непрерывности выполняемых операций до самого их завершения. Другим хорошим примером монитора является файловая система. Файловые системы Win32 позволяют нескольким потокам иметь доступ ко многочисленным файлам, которые могут быть открыты несколькими разными процессами одновременно в различных режимах. Большая часть хорошей файловой системы состоит из управления дескрипторами и блокировочных схем, которые обеспечивают оптимальную производительность, гарантируя при этом сохранение атомарности и непрерывности операций. Все потоки могут обращаться к файловой системе, и она гарантирует, что никакие операции не будут вступать в конфликт, и как только операция завершится, ее результат непременно будет записан на диск. В частности файловая система NTFS базируется на журнале событий ("log based"), и обеспечивает сохранность данных даже в случае отказа питания или зависания операционной системы.
Классы Interlock (взаимоблокировки).
Классы взаимоблокировки в этой классификации стоят особняком, посколькуони не содержат никаких данных. Некоторые механизмы блокировки полезны тогда, когда код блокировки легко отделить от кода манипуляции с общими данными. Лучший пример этого в Delphi - класс "Multiple reader single writer interlock", который разрешает разделяемое чтение и атомарную запись некоего ресурса. Его работа будет рассмотрена ниже, в внутренняя реализация класса будет изучена в следующей главе.
Поддержка потоков в VCL.
В Delphi 2 не было классов для поддержки многопоточного программирования, всю синхронизацию приходилось делать только полагаясь на собственные силы. С тех пор библиотека VCL в этом отношении была значительно улучшена. Я опишу классы, имеющиеся в Delphi 4, так как у меня именно эта версия. Пользователи Delphi 3 и 2 увидят, что некоторые классы отсутствуют в этих версиях, а пользователи Delphi 5 и более новых версий найдут многочисленные расширения этих классов. В этой главе я представлю краткое описание этих классов и их использования. Замечу, что в общем-то многие из встроенных в Delphi классов не слишком полезны: они предоставляют слишком мало преимуществ по сравнению с механизмами, обеспечиваемыми Win32 API.
TThreadList
Как уже упоминалось ранее, списки, стеки и очереди часто применяются при реализации задачи взаимодействия потоков. Класс TThreadList осуществляет базовый вид синхронизации, требующийся для потоков. Вдобавок ко всем методам, имеющимся в TList, предусмотрены два дополнительных: Lock и UnLock. Использование их должно быть довольно очевидно для читателей, которые проработали предыдущие главы: список блокируется перед манипуляциями с данными и разблокируется после них. Если поток выполняет над списком многочисленные действия , который должны быть атомарны, то список должен оставаться блокированным. Список не осуществляет никакой неявной синхронизации для принадлежащих ему объектов. Программист может при желании разработать механизмы дополнительной блокировки для обеспечения таких возможностей, а кроме того, использовать блокировку списка для контроля всех операций со структурами данных, принадлежащими ему.
TSychroObject
Этот класс предоставляет виртуальные методы Acquire и Release, которые используются во всех главных классах синхронизации Delphi, предоставляя основу для реализации концепции владения, блокировки или захвата простых объектов синхронизации, подобно тому, как уже обсуждалось ранее. Классы критической секции и классы событий (event) являются его наследниками.
TCriticalSectionЭтот класс не нуждается в подробном описании. Я подозреваю, что его включение в Delphi - просто дань тем программистам, кто испытывает антипатию к Win32 API. Следует только отметить, что он предоставляет четыре метода: Acquire, Release, Enter и Leave. Два последних только вызывают два первых, для удобства программистов, предпочитающих один из наборов терминов.
TEvent и TSimpleEvent
События (Event) - несколько другой способ обеспечения синхронизации. Вместо осуществления взаимного исключения, они применяются, чтобы заставить какое-то количество потоков ожидать, пока что-то не произойдет, а при возникновении этого события освобождать один или все эти потоки. TSimpleEvent - класс события, который определяет различные параметры по умолчанию, наиболее вероятно использующиеся в приложениях Delphi. События тесно связаны с семафорами и обсуждаются в последующей главе.
TMultiReadExclusiveWriteSynchroniser.
Это объект синхронизации полезен в ситуациях, когда многим потокам нужно читать из коллективного ресурса, но запись в него ведется сравнительно редко. В таком случае часто нет необходимости полностью блоктровать ресурс. В первых главах я утверждал, что любое несинхронизированное использование общих ресурсов, вероятно, приведет к конфликтам потоков. Хотя это и верно, не всегда обязательно использовать полное взаимное исключение. Полное взаимное исключение подразумевает, что в каждый момент только один поток осуществляет какую-то операцию. Мы можем ослабить это требование, если понимаем, что есть два основных типа конфликтов потоков:

* запись после чтения

* запись после записи
Конфликты первого рода происходят, если один поток пишется в раздел ресурса после того, как другой поток прочитал это значение, и считает его верным. Этот тип конфликтов проиллюстрирован в Главе 3. Конфликт второго рода бывает, когда два потока пишут в общий ресурс, один после другого, причем читающий поток не знает о более ранней записи. Это приводит к уничтожению первой записи. Конечно, некоторые действия вполне допустимы: чтение после чтения, чтение после записи. Эти две операции постоянно выполняются в однопоточных программах! Это, очевидно, указывает, что мы можем немного ослабить критерии согласованности работы с данными. Минимальные условия:

* Несколько потоков могут читать одновременно.

* Только один поток может писать в каждый момент.

* Если поток пишет, ни один из потоков не может читать.
Синхронизатор MultiReadExclusiveWriteSynchroniser обеспечивает эти условия с помощью четырех функций: BeginRead, BeginWrite, EndRead и EndWrite. При вызове их до и после записи достигается необходимая синхронизация. Что же касается программиста, то он может рассматривать этот синхронизатор как очень похожий на критическую секцию, с тем исключением, что поток использует его или для чтения или для записи.
Руководство разработчика потокобезопасных классов.
Хотя в последующих главах и рассматриваюся детали создания потокобезопасных классов и различные преимущества и ловушки, в которые можно попасть при их проектировании, видимо, стоит отметить несколько простых пунктов, которые нужно будет всегда учитывать.

* Кто блокирует?

* Экономия блокировочных ресурсов.

* Обработка ошибок.
Ответственность за блокировку в потокобезопасном классе можно отдать на откуп или разработчику класса, или программисту - пользователю класса. Если класс обеспечивает только простую функциональность, обычно лучше, чтобы за блокировку отвечали пользователи класса. Они, вероятно, будут используют несколько экземпляров данного класса, и дав им ответственность за блокировку, мы гарантируем, что не будет неожиданных зацикливаний, а также даем им выбор уровня блокировки, чтобы добиться либо простоты, либо эффективности. Для более сложных классов, например, мониторов, ответственность за блокировку обычно перекладывают на сам класс (или набор классов), скрывая таким образом сложность блокировки от конечного пользователся класса
В целом, ресурсы должны блокироваться по возможности минимально, и их блокировка должна тщательно настраиваться. Хотя упрощенные схемы блокировки схем и уменьшают шансы внесения в код трудноуловимых ошибок, они могут значительно снизить преимущества в производительности при использовании потоков. Конечно, нет ничего плохого в том,чтобы начинать с простого, но при возникновении проблем с производительностью схему блокировки придется изучатьи проверять более тщательно.
Ничто не работает всегда без ошибок. При использовании вызовов Win32 API, учтите возможность неудачи операции. Если вы относитесь к тем программистам, кто не против проверять тысячи кодов ошибок, то это выполнимо. Иначе вы можете захотеть написать класс-обертку, который инкапсулирует возможности объектов синхронизации Win32, вызывая исключения, когда происходят ошибки. В любом случае хорошо подумайте об использовании блоков try... finally, чтобы в случае неудачи гарантировать, что объекты синхронизации останутся в предсказуемом состоянии.
Управление приоритетами.
Вся потоки созданы равными, но некоторые более равны, чем другие (Orwell, Animal Farm). Планировщик должен поделить время CPU между всеми потоками, в любой момент работающими на машине. Для этого ему нужно иметь некоторое представление о том, насколько каждый поток будет использовать CPU, и как важно исполнять конкретный поток, когда он готов работать. Большинство потоков ведут себя одним из двух способов: во время выполнения они задействуют в основном или CPU или ввод/вывод.
Интенсивно использующие CPU потоки обычно выполняют долгие численные расчеты в фоновом режиме. Они будут занимать все отведенные им ресурсы процессора, но редко будут приостанавливаться для ожидания ввода-вывода или взаимодействия с другими потоками. Довольно часто время их выполнения не особенно критично. Например, поток в программе компьютерной графики может осуществлять долгую операцию по обработке изображения (фильтрация или вращение картинки), и она может занять несколько секунд или даже минут. С точки зрения планировщика, выделяющего кванты процессорного времени, этому потоку никогда не нужно запускаться безотлагательно, так как пользователя не волнует, двенадцать или тринадцать секунд займет выполнение этой операции, и никакой другой поток в системе не ждет результатов этого действия как можно скорее.
По другому обстоит дело с выделением времени потокам, связанным с вводом-выводом. Они обычно не занимают много процессорного времени, и могут состоять из сравнительно небольших кусков кода обработки. Они очень часто приостанавливаются (блокируются) устройствами ввода-вывода, а когда они получают информацию, то обычно запускаются на короткое время, обрабатывают ввод, и почти немедленно приостанавливаются снова, если больше нет доступной для обработки информации. Примером может служить поток, обрабатывающий действия по перемещению мыши и коррекции положения курсора. Каждый раз при передвижении мыши поток запускается на очень малую долю секунды, обновляя курсор, и затем приостановливается. Потоки подобного типа обычно гораздо более критичны ко времени: они не запускаются надолго, но их запуск должен происходить немедленно. В большинстве GUI систем неприемлемо, чтобы курсор мыши не откликался на ввод даже в течение короткого периода времени, и, следовательно, поток, отвечающий за работу с мышью, является довольно критичным ко времени. Пользователи WinNT могут обратить внимание, что даже когда компьютер занимается интенсивными вычислениями, курсор мыши реагирует немедленно. Весь операционные системы с вытесняющей могозадачностью, включая Win32, обеспечивают поддержку этих концепций, разрешая программисту назначать "приоритеты" потоков. Обычно потоки с более высокими приоритетами связаны со вводом-выводом, а потоки с более низкими приоритетами связаны с вычислительными задачами процессора. Реализация приоритетов потоков в Win32 слегка отличается от реализации, например, в UNIX, так что обсуждаемые здесь детали относятся только к Win32.
Что такое приоритет? Как это делается в Win32.
Большинство операционных системы назначают потокам приоритет для определения, сколько времени CPU должен получать каждый поток. В Win32 фактический приоритет каждого потока вычисляется динамически исходя из множества факторов, некоторые из которых могут непосредственно быть установлены программистом, а некоторые - нет. К этим факторам относятся класс приоритета (Priority Class) процесса, уровень приоритета (Priority Level) потока, используемые вместе для определения базового приоритета (Base Priority) и уровня повышения приоритета (Priority Boost), действующих для этого потока. Класс приоритета устанавливается для каждого запущенного процесса. Почти для всех приложений Дельфи он будет Normal, за исключением скринсейверов, которым можно установить класс приоритета Idle. Обычно программисту в Дельфи не нужно изменять класс приоритета запущенного процесса. Уровень приоритета каждого потока можно затем установить в рамках класса , назначенного для процесса, что более полезно, и программист может использовать вызов API SetThreadPriority для изменения уровня приоритета потока. Допустимые значения параметра: THREAD_PRIORITY_HIGHEST, THREAD_PRIORITY_ABOVE_NORMAL, THREAD_PRIORITY_NORMAL, THREAD_PRIORITY_BELOW_NORMAL, THREAD_PRIORITY_LOWEST и THREAD_PRIORITY_IDLE. Поскольку реальный базовый приоритет потока вычисляется на основе как уровня приоритета, так и класса приоритета процесса, потоки с уровнем приоритета Above Normal в процессе с классом приоритета Normal будут обладать большим базовый приоритетом, чем потоки с уровнем приоритета Above Normal в процессе с классом приоритета Below Normal. Как только базовый приоритет потока вычислен, он остается фиксированным на все время жизни потока или пока уровень приоритета (или класс процесса) не изменится. Тем не менее, фактический приоритет, используемый в каждый момент, планировщик задач слегка изменяет в результате повышения приоритета.
Повышение приоритета является механизмом, используемым планировщиком, чтобы постараться учесть поведение потоков во время выполнения. Некоторые потоки будут полностью занимать CPU или ввод/вывод во время своего исполнения, и планировщик может повысить приоритет этих потоков, но не выделит им полный квант времени. Кроме того, потокам, которые владеют дескрипторами окон, находящихся в данный момент на переднем плане (foreground) также дается небольшое повышение приоритета для улучшения реакции на действия пользователя
Какой приоритет дать моему потоку?
Поняв основы обращения с приоритетам, мы можем теперь попытаться назначить нужные уровни приоритетов уровни потокам нашего приложения. Учтите, что по умолчанию поток VCL выполняется с уровнем приоритета normal. В обшем большинство Дельфи-приложений пишутся так, чтобы по возможности обеспечивать пользователю наиболее быструю ответную реакцию, так что редко нужно увеличивать приоритет потока выше normal - при этом всякий раз при выполнении потока будет происходить задержка многих действий, например, перерисовки окна. Большинство потоков, имеющих дело со вводом/выводом или передачей данных в приложениях Дельфи можно оставить приоритетом normal, так как, если нужно, планировщик задач даст потоку больше времени, а если поток занимает слишком много процессорного времени, то увеличения доли времени не произойдет, что приводит к разумной скорости операций в основном потоке VCL. И наоборот, понижение приоритетов может быть очень полезно. Если вы уменьшите приоритет потока, выполняющего фоновую интенсивную обработку, требующую вычислительных ресурсов, машина покажется пользователю лучше откликающейся на его действия, чем если бы у этого потока оставить нормальный приоритет. Обычно пользователь значительно терпимее относится к небольшим задержкам в выполнении низкоприоритетных потоков: он может переключиться на другие задачи, и при этом компьютер и приложение не теряют восприимчивости к вводу.

Изображение

dyvniy M
Автор темы, Администратор
Администратор
Аватара
dyvniy M
Автор темы, Администратор
Администратор
Возраст: 41
Репутация: 1
Лояльность: 1
Сообщения: 3579
Зарегистрирован: Ср, 10 октября 2012
С нами: 11 лет 6 месяцев
Профессия: Программист
Откуда: Россия, Москва
ICQ Сайт Skype ВКонтакте

#6 dyvniy » Вс, 7 февраля 2016, 20:06:59

Callbacks
http://stackoverflow.com/questions/14064854/delphi-callbacks

Код: Выделить всё

type  TMyProc = procedure(Param1: Integer);
Then you can use your procedure type anywhere, as long as your procedure's signature matches your type.
If you're using an object method instead of a plain procedure/function, you need to use of object

Код: Выделить всё

type  TMyMethod = procedure(Param1: Integer) of object;
To call your callback from within, you can use something like this:

Код: Выделить всё

procedure DoAndCallBack(MyProc: TMyProc)
begin
  MyProc(1);
end;


Более подробно
http://delphiworld.narod.ru/base/function_pointer.html
Спойлер
Указатель на функцию

Это то, что я нашел при создании простой машины состояний:

Ниже приведен простой пример для Borland Delphi, использующий указатели функций для управления программным потоком. Просто создайте простую форму с единственной кнопкой и скопируйте код из Unit1 во вновь созданный модуль. Добавьте к проекту Unit2 и скомпилируйте проект. Дайте мне знать, если у вас возникнут какие-либо проблемы.


interface

uses

SysUtils, WinTypes, WinProcs, Messages, Classes, Graphics, Controls,
Forms, Dialogs, StdCtrls;

type

TForm1 = class(TForm)
Button1: TButton;
procedure Button1Click(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;

var

Form1: TForm1;
CurrProc: LongInt;
MyVal: LongInt;

implementation

uses Unit2;

{$R *.DFM}

procedure TForm1.Button1Click(Sender: TObject);
var

NewProc: LongInt;
MyString: string;
begin

CurrProc := 2; { начальная точка в таблице методов }
MyVal := 0; { вспомогательная переменная }
NewProc := 0;
{ возвращаемое значение для следующего индекса в таблице методов }
while CurrProc < 6 do
begin
{ выполняем текущий индекс в таблице методов и получаем следующую процедуру }
NewProc := ProcTable[CurrProc](MyVal);

{ просто показываем значения NewProc и CurrProc }
FmtStr(MyString, 'NewProc [%d] CurrProc [%d]', [NewProc, CurrProc]);
MessageDlg(MyString, mtInformation, [mbOK], 0);

{ присваиваем текущую процедуру возвращаемой процедуре }
CurrProc := NewProc;
end;

end;

end.


{ Это простой пример, определяющий массив указателей на функции }

interface

type

{ определяем Procs как функцию }
Procs = function(var ProcNum: LongInt): LongInt;

var

{ объявляем массив указателей на функции }
ProcTable: array[1..5] of Procs;

{ определения интерфейсов функций }
function Proc1(var MyVal: LongInt): LongInt; far;
function Proc2(var MyVal: LongInt): LongInt; far;
function Proc3(var MyVal: LongInt): LongInt; far;
function Proc4(var MyVal: LongInt): LongInt; far;
function Proc5(var MyVal: LongInt): LongInt; far;

implementation

uses Dialogs;

function Proc1(var MyVal: LongInt): LongInt;
begin

MessageDlg('Процедура 1', mtInformation, [mbOK], 0);
Proc1 := 6;
end;

function Proc2(var MyVal: LongInt): LongInt;
begin

MessageDlg('Процедура 2', mtInformation, [mbOK], 0);
Proc2 := 3;
end;

function Proc3(var MyVal: LongInt): LongInt;
begin

MessageDlg('Процедура 3', mtInformation, [mbOK], 0);
Proc3 := 4;
end;

function Proc4(var MyVal: LongInt): LongInt;
begin

MessageDlg('Процедура 4', mtInformation, [mbOK], 0);
Proc4 := 5;
end;

function Proc5(var MyVal: LongInt): LongInt;
begin

MessageDlg('Процедура 5', mtInformation, [mbOK], 0);
Proc5 := 1;
end;

initialization

{ инициализируем содержание массива указателей на функции }
@ProcTable[1] := @Proc1;
@ProcTable[2] := @Proc2;
@ProcTable[3] := @Proc3;
@ProcTable[4] := @Proc4;
@ProcTable[5] := @Proc5;

end.

Я думаю это можно сделать приблизительно так: объявите в каждой форме процедуры, обрабатывающие нажатие кнопки, типа процедуры CutButtonPressed(Sender:TObject) of Object; затем просто назначьте события кнопок OnClick этим процедурам при наступлении событий форм OnActivate. Этот способ соответствует концепции ОО-программирования, но если вам не нравится это, то вы все еще можете воспользоваться указателями функций, которая предоставляет Delphi.

Объявите базовый класс формы с объявлениями абстрактных функций для каждой функции, которую вы хотите вызывать из вашего toolbar. Затем наследуйте каждую вашу форму от базового класса формы и создайте определения этих функций.

Пример: (Здесь может встретиться пара синтаксических ошибок - я не компилил это)


type
TBaseForm = class(TForm)
public
procedure Method1; virtual; abstract;
end;

type
TDerivedForm1 = class(TBaseForm)
public
procedure Method1; override;
end;

TDerivedForm2 = class(TBaseForm)
public
procedure Method1; override;
end;

procedure TDerivedForm1.Method1;
begin
....
end;

procedure TDerivedForm2.Method1;
begin
....
end;

{Для вызова функции из вашего toolbar,
получите активную в настоящий момент форму и вызовите Method1}

procedure OnButtonClick;
var
AForm: TBaseForm;
begin
AForm := ActiveForm as TBaseForm;
AForm.Method1;
end;
Изображение


Название раздела: Программирование (под Desktop и Android)
Описание: Разработка и отладка приложений. Упор на 3D-графику.

Быстрый ответ


Введите код в точности так, как вы его видите. Регистр символов не имеет значения.
Код подтверждения
:) ;) :hihi: :P :hah: :haha: :angel: :( :st: :_( :cool: 8-| :beee: :ham: :rrr: :grr: :* :secret: :stupid: :music: Ещё смайлики…
   

Вернуться в «Программирование (под Desktop и Android)»

Кто сейчас на форуме (по активности за 15 минут)

Сейчас этот раздел просматривают: 96 гостей