Пусть у нас есть определение такого класса:
Это только описание класса - чтобы компилятор знал какие поля и методы в нем есть (методов сейчас мы не задали).
Это определение не зря лежит в секции type - мы можем объявлять переменные этого типа.
Переменная - это адрес участка в оперативной памяти, в котором лежат данные указанного типа. Раз переменная некоторого типа располагается в памяти, то:
1) компилятор (а также программист) должен точно знать размер этого типа, чтобы выделить нужное количество памяти.
2) должна быть выделена память необходимого размера. Для некоторых типов память выделяется автоматически при объявлении переменной указанного типа, а для некоторых - нужно вручную вызывать некоторые функции для выделения памяти (такие типы называются динамическими, также память выделяется вручную для ссылочных типов и вообще для произвольных структур, размер которых меняется в процессе работы программы).
Переменные так называемых простых типов хранят собственно значение, которое передается в функции, копируется при присваивании и т.п.
Другие стандартные типы даных являются ссылочными типами: переменная такого типа содержит не сами данные, а только адрес (длиной 4 байта) этих данных в памяти. При передачи таких переменных в функции, передается только адрес данных, что избавляет от необходимости каждый раз делать копию данных, на которые указывает переменная. Также при присваивании значения одной переменной другой, копируется только адрес данных. Таким образом две переменные указывают на одни и те же данные в памяти. Такой адрес называется указателем. Универсальный тип для хранения указателей называется Pointer.
Класс - это динамический ссылочный тип.
Будем называть классом само определение полей, как в примере выше. То-есть, когда мы описываем класс в секции type, это описание никакой памяти не занимает и с ним ничего не можно делать.
Динамический тип значит, что память под него нужно выделять и удалять вручную.
Ссылочный - значит переменные данного типа хранят только адрес и имеют размер 4 байта.
Тогда экземпляр класса - это память, выделенная для данного класса. Для сокращения также будем называть экземпляр класса термином "объект", хотя это не совсем верно. Если мы объявим переменную типа TMyClass, то она будет содержать адрес некоторого экземпляра, адрес объекта. А может содержать 0-вой адрес (специальное ключевое слово nil) - это значит, что данная переменная не указывает ни на какой объект.
Для выделния памяти под экземпляр класса (создание объекта) мы должны вызвать специальный метод класса, который называется конструктором. Хоть мы можем определять свои конструкторы, в любого класса по-умолчанию уже есть стандартный конструктор Create. Соотвественно, для освобождения занимаемой памяти (уничтожения объекта) используется специальный метод - деструктор. Стандартный деструктор называется Destroy. Но для уничтожения всё же обычно вызывается стандартный метод объекта Free (а уже он все равно потом вызовет Destroy).
Размер памяти, которая будет выделена при создании объекта с помощью конструктора, будет зависеть от размера и количества полей, которые мы определим в описании класса.
Например, в приведенном выше классе есть 2 поля типа Integer, которые имеют размер 4 байта. Тогда под каждый экземпляр данного класса будет выделяться 8 байт (реально больше, так как каждый объект имеет ещё и служебную информацию; если данный класс наследуется от других классов, то занимаемый объем есть суммой объемов данного и всех родительских классов).
Приступим к практике:
Класс может содержать методы - функции, которые выполняют некоторую работу и оперируют полями экземпляра класса.
Когда в классе появляется метод, его нужно дополнительно описать в секции implementation.
Пример:
Исходя из этого можно объснить, что я сделал в твоем коде:
Я передал свой адрес (Self), указав его в SetProgressCallback
Когда API вызовет ProgressCallback, оно передаст туда этот же адрес в качестве параметра sender
Поскольку это может быть любой адрес, а не только на мой объект, то он объявлен в функции как Pointer.
API этот адрес не интересует, оно просто передаст его в callback-функцию.
А раз так, то мы должны явно указать компилятору, чем мы считаем этот адрес. А считаем мы его указаывающим на объект типа THiAsmClass (ведь адрес именного такого объекта мы передали). Делаем мы это с помощью приведения типа THiAsmClass(sender). В таком случае мы уже можем обращаться к полям и методам объекта.
#pas
type
TMyClass = class
Field1: Integer;
Field2: Integer;
end;
Это определение не зря лежит в секции type - мы можем объявлять переменные этого типа.
Переменная - это адрес участка в оперативной памяти, в котором лежат данные указанного типа. Раз переменная некоторого типа располагается в памяти, то:
1) компилятор (а также программист) должен точно знать размер этого типа, чтобы выделить нужное количество памяти.
2) должна быть выделена память необходимого размера. Для некоторых типов память выделяется автоматически при объявлении переменной указанного типа, а для некоторых - нужно вручную вызывать некоторые функции для выделения памяти (такие типы называются динамическими, также память выделяется вручную для ссылочных типов и вообще для произвольных структур, размер которых меняется в процессе работы программы).
Переменные так называемых простых типов хранят собственно значение, которое передается в функции, копируется при присваивании и т.п.
Другие стандартные типы даных являются ссылочными типами: переменная такого типа содержит не сами данные, а только адрес (длиной 4 байта) этих данных в памяти. При передачи таких переменных в функции, передается только адрес данных, что избавляет от необходимости каждый раз делать копию данных, на которые указывает переменная. Также при присваивании значения одной переменной другой, копируется только адрес данных. Таким образом две переменные указывают на одни и те же данные в памяти. Такой адрес называется указателем. Универсальный тип для хранения указателей называется Pointer.
Класс - это динамический ссылочный тип.
Будем называть классом само определение полей, как в примере выше. То-есть, когда мы описываем класс в секции type, это описание никакой памяти не занимает и с ним ничего не можно делать.
Динамический тип значит, что память под него нужно выделять и удалять вручную.
Ссылочный - значит переменные данного типа хранят только адрес и имеют размер 4 байта.
Тогда экземпляр класса - это память, выделенная для данного класса. Для сокращения также будем называть экземпляр класса термином "объект", хотя это не совсем верно. Если мы объявим переменную типа TMyClass, то она будет содержать адрес некоторого экземпляра, адрес объекта. А может содержать 0-вой адрес (специальное ключевое слово nil) - это значит, что данная переменная не указывает ни на какой объект.
Для выделния памяти под экземпляр класса (создание объекта) мы должны вызвать специальный метод класса, который называется конструктором. Хоть мы можем определять свои конструкторы, в любого класса по-умолчанию уже есть стандартный конструктор Create. Соотвественно, для освобождения занимаемой памяти (уничтожения объекта) используется специальный метод - деструктор. Стандартный деструктор называется Destroy. Но для уничтожения всё же обычно вызывается стандартный метод объекта Free (а уже он все равно потом вызовет Destroy).
Размер памяти, которая будет выделена при создании объекта с помощью конструктора, будет зависеть от размера и количества полей, которые мы определим в описании класса.
Например, в приведенном выше классе есть 2 поля типа Integer, которые имеют размер 4 байта. Тогда под каждый экземпляр данного класса будет выделяться 8 байт (реально больше, так как каждый объект имеет ещё и служебную информацию; если данный класс наследуется от других классов, то занимаемый объем есть суммой объемов данного и всех родительских классов).
Приступим к практике:
#pas
// Объявим переменную для хранения нашего объекта (а точнее, адреса на него)
var
MyObject: TMyClass; // Переменная изначально хранит nil, то-есть не указывает ни на какой объект
// Поработаем с нашим классом
procedure Test;
begin
// Выделим память с помощью конструктора и присвоим её адрес нашей переменной
MyObject := TMyClass.Create; // Конструктор возвращает адрес на новосозданный объект
// Теперь мы можем работать с объектом:
MyObject.Field1 := 123;
MyObject.Field2 := MyObject.Field1 + 222;
// Никогда не забываем освободить память, когда объект больше не нужен
MyObject.Free;
// Вызов метода Free освободил занимаемую память, но переменная MyObject всё ещё содержит
// адрес бывшего объекта. По этому адресу теперь будут записаны какие-то другие данные,
// а значит после вызова Free мы уже не имеем права обращаться к полям и методам объекта MyObject,
// пока не создадим новый объект и присвоим его адрес этой переменной.
// Чтобы знать, что переменная не указывает на действительный объект, присвоим ей 0-вой адрес:
MyObject := nil;
end;
Когда в классе появляется метод, его нужно дополнительно описать в секции implementation.
Пример:
#pas
type
TMyClass = class
Field1: Integer;
Field2: Integer;
function GetSum: Integer;
end;
implementation
function TMyClass.GetSum: Integer;
begin
// В методах своего класса мы можем обращаться к своим полям. А точнее, эти поля будут принадлежать
// тому объекту, для которого мы вызываем метод (текущий объект).
Result := Field1 + Field2;
//Для получения адреса текущего объекта в методе можно обратится к предопределенной переменной Self
// Строчка выше может быть записана как
// Result := Self.Field1 + Self.Field2;
// Этот свой адрес мы можем передавать в различные сторонние функции и т.п.
end;
procedure Test;
var
i: Integer;
begin
MyObject := TMyClass.Create;
// Присвоим значения полям
MyObject.Field1 := 123;
MyObject.Field2 := 222;
// Получим их сумму, вызвав метод объекта:
i := MyObject.GetSum;
// Метод GetSum будет оперировать полями объекта, заданного в MyObject
// В отличии от конструктора TMyClass.Create
// мы не должны вызывать метод как TMyClass.GetSum, потому что методу нужно оперировать с
// уже созданным объектом. Тогда как для конструктора объекта ещё не существует и он как раз
// используется для создания экземпляра объекта.
MyObject.Free;
MyObject := nil;
end;
#pas
Arch.SetProgressCallback(Self, ProgressCallback);
#pas
function ProgressCallback(sender: Pointer; total: boolean; value: int64): HRESULT; stdcall;
begin
if total then
_hi_onEvent(THiAsmClass(sender).onProgressMax, value) else
_hi_onEvent(THiAsmClass(sender).onProgress, value);
Result := S_OK;
end;
API этот адрес не интересует, оно просто передаст его в callback-функцию.
А раз так, то мы должны явно указать компилятору, чем мы считаем этот адрес. А считаем мы его указаывающим на объект типа THiAsmClass (ведь адрес именного такого объекта мы передали). Делаем мы это с помощью приведения типа THiAsmClass(sender). В таком случае мы уже можем обращаться к полям и методам объекта.