HGLOBAL, GlobalHandle, LocalHandle

Программирование на Visual Basic, главный форум. Обсуждение тем программирования на VB 1—6.
Даже если вы плохо разбираетесь в VB и программировании вообще — тут вам помогут. В разумных пределах, конечно.
Правила форума
Темы, в которых будет сначала написано «что нужно сделать», а затем просьба «помогите», будут закрыты.
Читайте требования к создаваемым темам.
arthur2
Продвинутый гуру
Продвинутый гуру
Аватара пользователя
 
Сообщения: 1688
Зарегистрирован: 23.01.2008 (Ср) 14:35

HGLOBAL, GlobalHandle, LocalHandle

Сообщение arthur2 » 23.06.2019 (Вс) 6:52

Многие АПИ просят HGLOBAL. В примерах я обычно вижу, что память для получения такого хендла выделяется, лочится и освобождается вручную с помощью функций GlobalЛяляля или реже LocalЛяляля.

Можно ли для выделения памяти положиться на бейсик и пользоваться просто массивами, а потом получать хендлы с помощью GlobalHandle или LocalHandle, и если да, то какой из них правильней?

Код: Выделить всё
Debug.Print VarPtr(Bytes(0))
Debug.Print LocalHandle(Bytes(0))
Debug.Print GlobalHandle(Bytes(0))
Debug.Print LocalHandle(Bytes(1))
Debug.Print GlobalHandle(Bytes(1))

Как я себе представляю механизм работы (предположение ни на чем, кроме интуиции, не основано, просто подтвердите или поправьте, всё ли я правильно себе напредставлял).

Бейсик, редимя массив или уничтожая, сам вызывает все необходимые функции.
В качестве хендлов система использует адреса начала выделенных данных.
Функции GlobalHandle и LocalHandle просто проверяют, а выделяла ли система память на указанные адреса, и если да, то их и возвращает, а если нет, то возвращает ноль.
Отличие глобальных и локальных хендлов только в том, где потом они хранятся и откуда они могут быть использованы, но это всё равно просто адреса начала выделенных данных.

Если последнее предположение верно, можно ли вообще обойтись без получения хендлов и пользоваться адресами начала данных массивов? Ведь если массив существует, нам нет необходимости проверять, выделена ли на эти адреса память.
Артур
 
   

Хакер
Телепат
Телепат
Аватара пользователя
 
Сообщения: 16478
Зарегистрирован: 13.11.2005 (Вс) 2:43
Откуда: Казахстан, Петропавловск

Re: HGLOBAL, GlobalHandle, LocalHandle

Сообщение Хакер » 23.06.2019 (Вс) 14:31

arthur2 писал(а):Отличие глобальных и локальных хендлов только в том, где потом они хранятся и откуда они могут быть использованы, но это всё равно просто адреса начала выделенных данных.

Всё совсем не так.

В 16-битных Windows был совершенно другой подход к организации памяти. Это сейчас тот факт, что каждый процесс имеет изолированное АП, и что АП делится на страницы, кажутся естественными и непоколебимыми. Но в 16-битном мире всё было совсем не так.

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

Вместо одного одномерного адресного пространства с плоскими одномерными адресами предлагалось иметь многомерное пространство, состоящее из одномерных подпространств, где вместо плоских адресов использовались бы пары из идентификатора объекта и смещения внутри него.

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

Главная полезная вещь, которую давали сегменты: это контроль доступа (можно ли читать, можно ли писать, можно ли исполнять) и контроль границ. Это с плоским (flat) адресным пространством мы, пробегаясь по какому-то буферу, массиву, длинной строке и делая чтение или запись, можем случайно выбежать за его пределы и начать читать какие-то совершенно другие непричастные данные. С сегментами, которые можно рассматривать как аппаратные массивы (или аппаратные буферы, аппаратные контейнеры данных) любой выход за границы сегмента будет пресекаться на корню, и что самое главное — эта защита от выходов за границы, как и защита от несанкционированного доступа, выполнена аппаратно.

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

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


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

А теперь коснёмся непосредственно селектора сегмента. Что это такое? С точки зрения пользователя этой некий идентификатор сегмента, то есть идентификатор, идентифицирующий блок памяти, к которому можно обращаться через этот селектор (в паре со смещением внутри блока). С точки зрения внутреннего устройства селектор это 16-битное число, состоящее из трёх битовых полей: индекс дескриптора сегмента в таблице дескрипторов, флаг, определяющий, какая из двух (глобальная или локальная) таблиц дескрипторов используется, и поле, определяющая требуемый уровень доступа.

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

Вся подобная информация о сегменте содержится в дескрипторе сегмента (это просто структура), а дескриптор сегмента заносится в таблицу дескрипторов. Индекс дескриптора в этой таблице и становится основной частью селектора сегмента.

Таких таблицы две: глобальная и локальная. Идея в том, что в многозадачной системе захочется иметь набор глобальных сегментов, которые будут одновременно видимы всем задачам, и наборы локальных сегментов (каждой задаче будет виден только свой набор локальных сегментов).

Поэтому в реальности, некая абстрактная операционная система (в полную силу использующая преимущества сегментов) будет иметь одну глобальную дескрипторную таблицу (GDT), и какой-то набор локальных дескрипторных таблиц (LDT), например, по числу работающих задач.

Для GDT у процессора есть специальный регистр GDTR, хранящий адрес (плоский, одномерный) начала GDT в памяти.
Для текущий LDT у процессора есть специальный регистр LDTR, который хранит... нет, не адрес, а почти что адрес LDT в памяти. Хотя LDTR мог бы, подобно GDTR, хранить адрес LDT в памяти, в реальности LDT должна храниться отдельном сегменте, и в регистре LDTR вместо адреса LDT содержится селектор сегмента, внутри которого лежит LDT.

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

Отдельно можно поговорить о недостатках, которые имела сегментная модель, и которые можно было исправить (по мере развития архитектуры Intel), но которые не стали исправлять.

Логические адреса всю жизнь состояли из пары селекторсегмента:смещение.
Селектор всю жизнь был 16-битным.
Смещение в 16-битных процессорах и 16-битных режимах 32-битных процессоров были 16-битными.
Смещения в 32-битных процессорах стали 32-битными.

Это давало 32-битные логические адреса в 16-битных режимах и 48-битные логические адреса в 32-битных режимах.

Хотя на первый взгляд кажется, что 32-битные логические адреса позволяли бы адресовать 232 (4 Гб) отдельный байтиков, а 48-битные адреса — аж 248 отдельных ячеек памяти, в реальности всё куда скромнее.

Во-первых, хотя 16-битные селекторы могут принимать 65536 возможных значений, они не являются уникальными идентификаторами сегментов и не могут идентифицировать 65536 уникальных сегментов. Не все 16 бит селектора являются индексом сегмента, а лишь 13 старших битов хранят индекс. Младшие 2 бита селектора это поле RPL, третий бит справа определяет, в какой таблцие сегментов содержится дескриптор для нужного нам сегмента.

Поэтому селекторы 1230, 1231, 1232 и 1233 ссылаются не на 4 разных, а на один и тот же сегмент (описанный в GDT). Это можно сравнить с тем, что у нас на диске есть один файл, но мы с помощью CreateFile открыли этот файл несколько раз и получили несколько разных хендлов: один хендл позволяет только читать из файла, но не писать, потому что при вызове CreateFile было запрошено только право на чтение, а другой хендл ссылается на тот же самый файл, но используя этот хендл можно и читать, и писать, потому что при вызове CreateFile были запрошены другие, более полные права.

Так как младшие 2 бита селектора, какие бы значения они ни принимали, не меняют то, какой сегмент идентифицируется селектором, пространство логических адресов сужается с 232 до 230 (в 16-битных режимах работы процессора) и с 248 до 246 (в 32-битных режимах).

Во-вторых, логические адреса в итоге транслируются либо сразу в физические, либо сначала в линейные, а затем физические. Выбор между двухступенчатой и трёхступенчатой схемой трансляции адресов осуществляется сменой значения флага PG регистра CR3. Как бы там ни было, ширина линейного адреса — 232, поэтому толку с с логических адресов шириной 246 оказывается очень мало. Хотя мы можем иметь 246 уникальных логических адресов, они все в совокупности будут ссылаться на куда более скромное множество из 232 уникальных ячеек памяти, и на все или чатсь таких ячеек памяти в логическом пространстве будут целая куча эээ... псевдонимов. Ширина же физических адресов (адресов физ. памяти) определялась железом, и лишь с появлением у процессоров режима PAE достигла 36-бит, что давало возможность всем задачам в совокупности использовать больше 4 Гб памяти в сумме, но одна отдельно взятая задача не могла использовать (точнее просто «видеть») больше 4 Гб памяти, потому что это ограничение проистекает из ширины линейных адресов. А логические адреса (пространство которых больше 4 Гб) транслируются сперва в линейные, а потом в физические. Промежуточный этап в виде линейных адресов можно было бы исключить: если отключить страничную грануляцию памяти (бит PG регистра CR0), логические адреса будут преобразовываться сразу в физические (точнее сказать, линейное пространство адресов и физическое пространство станут тождественными), однако в дескрипторе сегмента есть поле база (которое при сложение со смещением даёт плоский одномерный адрес), и это поле 32-битное, так что получить физические адреса больше 0xFFFFFFFF (4 Гб) всё равно никак не получилось бы, а одновременно с этим отключение бита PG отрезает для нас страничное преобразование (хитрое преобразование, преобразующее линейные адреса в физические адреса, дробя логический 32-битный адрес на пару индексов и используя сначала первый, а затем второй индекс, чтобы через пару таблиц вычислить, какая страница физпамяти соответствует адресуемой страницы линейного пространства) — а отключение страничного преобразования отрезает PAE с его 36-битным расширением физических адресов. То есть мы не только не добьёмся того, чтобы одна задача смогла видеть сразу больше 4 Гб (232) памяти, используя 46-битные логические адреса, но и потеряем возможность для всех задач в сумме использовать больше 4 Гб.

И всё таки существует трюк, который позволит (позволил бы) одной задаче, имя пространство логический адресов размером 246 (что гораздо больше, чем 4 Гб) видеть и работать с памятью, больше чем 4 Гб. Но к этому мы буквально скоро вернёмся.

Третья проблема сегментов в том, что селектор имеет размер 16 бит, из них только 13 бит отведено под индекс сегмента в соотв. таблице, что даёт нам 213 = 8192 возможных индексов. Это означает, что на всю систему число глобальных сегментов (общих, между всеми задачами) не может превышать этот теоретический лимит, и, если говорить об отдельно взятой задаче, то в её LDT не может быть больше 8192 дескрипторов.

      То есть теоретический максимум сегментов, которыми может распоряжаться отдельно взятая задача: 8192 сегментов в GDT (общие для всех задач) и 8192 сегментов в LDT. Если наша программа будет под каждый маленький объект, под каждый массив, под каждую структуру, под каждую строчку выделять память в лице отдельного сегмента (каждый объект будет жить в собственном сегменте, каждый массив будет жить в собственном сегменте), то наша программа не сможет выделить больше 8 тысяч блочков памяти под объекты/массивы/структуры. Тот факт, что сумма объектов, массивов и тому подобных сущностей, которые может породить и держать одновременно существующими одна программа, ограничена числом около 8 тысяч кажется дикой и возмутительной, и кажется, что это проблема сегментов. Но, на самом деле, ответ не такой однозначный.

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

      Мы привыкли, что запущенная программа — это процесс, и что процесс — это один или несколько потоков. За потоками (как понятием операционной системы стоят задачи (как понятие процессорной архитектуры), каждый поток это отдельная задача, и многопоточность обеспечивается тем, что процессор умеет обеспечивать многозадачность, переключаясь между задачами и изменяя контекст (сохраняя и подменяя значения всех регистров при переключении с одной задачи на другую).

      Но кто сказал, что это — единственный возможный подход, и что этот подход надо принимать за догму? Существует и сильно другой подход: процессорную сущность «задача» можно использовать не для одного потока, а к более мелкой части программы. Саму программу можно рассматривать не как набор функций, код которых последовательно выполняются, и которые передают управление из одной в другую, а как набор функциональных блоков, обменивающихся друг с другом сообщениями. Существуют целые языки программирования, где нет вызовов функций, но есть обмен сообщений между некими объектами. Модель оконных сообщений в Windows тоже частично следует этой парадигме, что кроме процедурного подхода, может быть и другой, основанный на обмене сообщениями между асинхронно функционирующими (потенциально — одновременно исполняющимися) сущностями.

      И тогда да, кажется, что иметь не более 8 тысяч сегментов на одну программу или один поток программы — это очень мало. Но кто сказал, что нужно иметь одну задачу на один поток? Ведь задача, если вдуматься в смысл самого слова, это не обязательно поток. Почему бы не поместить, грубо говоря, каждую отдельную функцию в свою отдельную задачу? А если точнее, то почему бы не поместить отдельный функциональный блок в свою отдельную задачу?

      Скажем, у нас есть браузер. Ему нужно периодически сжимать и разжимать данные, сжатые с помощью gzip. В текущем подходе для этого в коде браузера будет функция для компрессии/декомпрессии gzip-сжатых данных. И в каждом потоке, когда надо что-то сжать или разжать, эта функция будет вызвана, ей будет передано управление, а потом возвращено в вызывающий код.

      А есть совершенно другой подход: механизм gzip-сжатия можно выделить в отдельную задачу. У этой задачи может быть очередь входящих заданий: вход и выход данных. Когда нашему приложению нечего сжимать, эта задача спит, процессор просто не переключается на неё. Но когда кому-то надо что-то декодировать, он кидает задание в очередь заданий gzip-механизму, и в рамках отдельной процессорной задачи gzip-код выполняет свою непосредственную работу. И если думать о задачах как о маленьких функциональных единицах программы (отдельно рендерер текста, отдельно gzip-механизм, отдельно спелл-чекер), то лимит в 8 тыс. локальных и 8 тыс. глобальных сегментов уже не кажется таким маленьким.

      Если выделить код преобразования ANSI-текста в юникод, и юникода в ANSI в отделную задачу, то этой задаче (с помощью аппаратных возможностей процессора) можно настроить такое окружение, что кроме собственного кода, сегмента с входными данными, сегмента под выходные данные и сегмента с таблицей правил преобразования символов, ей не будет доступно ничего. Какая бы страшная ошибка ни была в коде преобразования юникода, какие бы страшные данные на вход не подали этому преобразователю, её код был в жесткой изоляции от всего остального, в песочнице (sandbox), и дальше если всё выйдет из под контроля, вышедший из под контроля код не сможет ничего испортить. Он сможет записать какую-нибудь гадость в буфер под выходные данные — и не более того. Буфер под входные данные будет только для чтения, код самой задачи будет только для чтения, задача сможет испортить свой стек. Таблица преобразований символов будет тоже только для чтения.

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

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

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

      Обычный процедурный вызов оборачивается тем, что один поток засыпает и ждёт, пока другой поток не сделает часть своей работы. Ну и чем это отличается от использования задач, когда задача — не олицетворение потока, а олицетворение отдельно взятой процедуры, и вызов процедуры приводит к засыпанию вызывающей задачи и активации вызываемой (которая могла быть активной и до этого и работать в духе обработчика очереди — разгребать очередь входящих заданий и выдавать результаты, выдача которых размораживала бы те задачи, которые заснули до момента завершения своих заявок)?

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

Ладно, возвращаемся к этим словам:
И всё таки существует трюк, который позволит (позволил бы) одной задаче, имя пространство логический адресов размером 246 (что гораздо больше, чем 4 Гб) видеть и работать с памятью, больше чем 4 Гб. Но к этому мы буквально скоро вернёмся.


И в 16-битном, и в 32-битном защищённом режиме процессора в дескрипторе сегмента есть флаговый бит «P» — Present. Он обозначает, присутствуют ли данные, доступ к которым обеспечивается этим сегментом, в памяти или нет. Если поле сброшено в ноль, при обращении к данным сегмента процессор генерирует исключение #NP, для которого ОС (или софт в общем случае) может назначить свой обработчик и что-то с сегментом сделать.

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

Такой механизм подкачки появился лишь в процессоре 80386 с добавлением режима страничной организации и страничного преобразования, но ещё в 80286 своппинг (подкачка) был и была его поддержка в Windows начинается, кажется, с версии 2.0. Только это была не подгрузка и выгрузка отдельных страниц, а подгрузка и выгрузка целых сегментов целиком.

Выгруженному сегменту в дескрипторе ставилось P=0, а при повторном обращении к нему срабатывал предоставленный системой обработчик исключения #NP (not present), который загружал нужный сегмент (выгружая взамен какой-нибудь другой) и устанавливал P=1 — а программа, которая делала попытку обращения к сегменту, даже не замечала подвоха и не знала, что её обращение к сегменту вызвало цепную реакцию, итогом которой стала подгрузка этого сегмента с диска и выгрузка какого-то другого на диск.

Такой подход позволял одной программе, или точнее определённой задаче работать с неким числом сегментов, суммарный размер которых превышал возможности физической памяти компьютера, которые во времена 80286 с его 16-битным защищённым режимом были очень скромными.

Если вернуться к вопросу о том, какой трюк позволяет во времена 32-битного защищённого режима из одной задачи (с логическим адресным пространством размером 248 что равно 64 Тб) установить сегменты так, чтобы они охватывали более 4 Гб не пересекающейся памяти. А он довольно интересный: хотя поле база в дескрипторе сегмента 32-битное, и 46-битные логические адреса конвертируются в 32-битные линейные (и затем в 32-битные или 36-битные физические), мы можем все сегменты разбить на группы и каждой группе присвоить своё отдельное 4 гигабайтное линейное адресное пространство.

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

Почему при переключении между потоками, принадлежащими разным процессам, код вдруг начинает видеть совершенно другое АП? Потому что при установке бита PG в CR0 активируется страничная организация памяти и страничное преобразование. Суть страничного преобразования состоит в том, что после того, как логический адрес (селектор:смещение) преобразован в линейный адрес (32-битный плоский одномерный адрес), линейный адрес преобразуется в физический адрес при помощи двухуровневого дерева таблиц.

В регистре CR3 есть битовое поле, которое называется PDBR (Page-Directory Base Register) и хранит указатель на начало Page Directory. Это таблица, которая состоит их элементов (Page Directory Entry, PDE), и каждый элемент или невалидный, или хранит указатель на начало Page Table. Это другая таблица, которая состоит из других элементов (Page Table Entry, PTE), каждый из которых описывает отдельно взятую страницу линейного адресного пространство (которое размером 4 Гб) и для каждой страницы хранит во-первых флаги (атрибуты доступы, признак того, что для этой странице линейного пространства есть своя страница в физ. памяти) и адрес соответствующей страницы в физ. памяти.

Любое обращение к памяти из любого потока любого процесса (под Windows и аналогичными ОС) приводит к тому, что сначала логический адрес (пара селектор:смещение) преобразуется в линейный адрес по формуле:
ЛинейныйАдрес = ТаблицаДескрипторов(ВзятьИндексИзСелектора(Селектор)).База + Смещение
А затем линейный адрес преобразуется в физический по формуле:
Код: Выделить всё
СмещениеВнутриСтраницы = ВзятьМладшие12Бит(ЛинейныйАдрес)
ИндексPTE = ВзятьСредние10Бит(ЛинейныйАдрес)
ИндексPDE = ВзятьСтаршие10Бит(ЛинейныйАдрес)
МассивPDEшек = CR3.PDBR
МассивPTEшек = МассивPDEшек(ИндексPDE).УказательНаСоотвТаблциуPTEшек
ФизическийАдрес = МассивPTEшек(ИндексPTE).ФизическийАдреСтраницы + СмещениеВнутриСтраницы


По мере этого преобразования в PDE и PTE проверяется флаговый бит P, и если P=0, вызывается предоставленный операционной системой обработчик исключения #PF (Page Fault), который должен загрузить отсутствующую страницу в физическую память с диска (возможно ценой выгрузки какой-то другой)

Вся суть «переключения АП» при переключении между потоками разных процессов состоит в том, что при этом изменяется значение регистра PDBR (это часть регистра CR3), а значит автоматически сменяется дерево таблиц, используемых для преобразования линейных адресов в физический (и проверки линейных адресов на предмет того, присутствует ли адресуемая страница в физпамяти). Помимо этого ещё и сбрасывается кеш TLB, но это малозначимо в рамках данной дискуссии.

Разбив все сегменты текущей задачи на группы, можно для каждой группы иметь своё 4 ГБайтное линейное пространство, определяемой своим каталогом страниц. При обращении к сегменту, если он принадлежит к другой группе — не к той, линейное пространство которой выбрано в данный момент как текущее (а текущее пространство это то — на каталог страниц которого указывает PDBR), то мы переключаем пространство сменой значения PDBR, а всем сегментам, которые стали невалидными в результате переключения на другое линейное пространство, мы в дескрипторах установим P=0, чтобы, когда код обратится к одному из таких невалидных сегментов, сработал обработчик исключения #NP, и переключил PDBR на другое линейное пространство, актуальной для той группы сегментов, к которой будет обращение, а текущую группу отметил невалидной, проставив в её дескрипторах сегментов этой группы P=0. Это переключение будет точно таким же, как переключение между АП при смене текущего потока с потока одного процесса на поток другого процесса в современных ОС.

Недостаток такого подхода: временнЫе затраты на переключение между линейными АП, когда идут обращения к разным сегментам, принадлежащим разным группам сегментов. Если такие обращения чередуются, у нас каждое обращение будет приводить к переключению. Но несколько групп и несколько линейных пространств нам понадобится только в том случае, если нам нужны несколько сегментов, которые в совокупности не могут быть спроецированы на одно линейное 4 Гбайтное пространство без пересечений.

Я не знаю ни одной актуальной ОС, которая вообще использовала бы сегменты по первоначально задуманному назначению, и уж тем более не знаю, чтобы существовала хоть одна ОС, которая бы использовала подобные тактики для того, чтобы одна задача могла распоряжаться несколькими непересекающимися сегментами, размер которых в сумме больше чем 4 Гб.

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

Все актуальные ОС, поскольку механизм сегментов нельзя отключить, делают трюк с созданием лишь минимального набора сегментов и установкой всем им минимальной базы (нулевой) и максимального лимита (4 Гб). Это даёт иллюзию так называемой плоской (flat) модели памяти при том, что механизм сегментов никуда не девается.

Современные версии Windows, например, создают ещё отдельный сегмент с небольшим лимитом и его селектор помещает в регистр FS. Этот сегмент ссылается на регион памяти, в котором находится TIB — блок информации о текущем потоке. В результате первое поле TIB-а доступно как FS:[0], второе — как FS:[4]. Но сам блок памяти, доступный через FS, доступен также и через DS/CS/ES, то есть через гигантские вырожденные сегменты. То есть Windows не использует всю мощь сегментов по обеспечению изоляции, контроля доступа и безопасности, а использует этот механизм только для лёгкого поиска потоко-специфичного блока данных.

Но вернёмся к 16-битным версиям Windows.

Не существовало иллюзии плоского адресного пространства. У каждого процесса не было изолированного АП. Широко использовались сегменты с ненулевыми базами. Не существовало разделения памяти на страниц. Не существовало постраничной подкачки: была только подгрузка и выгрузка сегментов целиком. Была GDT с дескрипторами глобальных сегментов и LDT у каждой задачи с сегментами, специфичными для задачи. Был лимит на размер сегмента в 64 кб и размер на количество сегментов в одной таблице в 8 тысяч.

Как должны были вести себя функции выделения памяти в тогдашних версиях Windows? Если программист запрашивает блоки по 16 байт, должна ли система для каждого запрошенного блока создавать новый сегмент? Тогда быстро исчерпается лимит на количество сегментов. Или же все запрашиваемые блоки должны выделяться из одного сегмента? Тогда исчерпается сам сегмент, из которого идут выделения.
В первом случае функция выделения памяти могла бы возвращать 16-битные селекторы сегментов, что соответствовало бы разрядности процессоров (а процессор хорошо работает с числами, размер которых соответствует его разрядности). Во втором случае функция выделения памяти должна была бы возвращать полные указатели — пару сегмент:смещение, которые занимают 32-бита, что в два раза больше разрядности процессора, и что не влазит целиком в один регистр процессора и требует использовать пару.

Создатели Windows решили сделать так, чтобы при выделении памяти вместо указателя на блок возвращался бы 16-битный хендл блока. На время, когда к блоку нужен доступ, программист вызывает GlobalLock или LocalLock, и, передав хендл, получает взамен указатель. Только в этот момент для блока памяти выделяется конкретный сегмент и становится ясным конкретное смещение. Поработав с блоком, используя указатель, когда работа с блоком оконечена, программист вызывает GlobalUnlock или LoclUnlock, и «открепляет» блок памяти. У программиста остаётся на руках хендл, а вот рабочий указатель становится невалидным, и тот сегмент, в котором был блок, может быть и выгружен, и перемещён в другое место (в физ. памяти), и использован для других нужд.

Поскольку сегменты бывают глобальными и локальными (и живут в GDT и LDT), построенный поверх мезанизм выделения памяти и управления ею через хендлы тоже состоит из двух семейств функций: Global-функций и Local-функций, и для хендлов существует два типа: HGLOBAL и HLOCAL.

Итак, на 16-битных версиях Windows HGLOBAL и HLOCAL — это именно хендлы блоков памяти, но никак не указатели на них. Используя хендлы, нельзя напрямую читать или писать данные. Блок памяти нужно закрепить в памяти функцией GlobalLock или LocalLock, что даст нам указатель, используя который уже можно писать, читать и копировать данные. Разумный подход состоит в том, чтобы фиксировать память только на те моменты, пока это нужно, а в остальное время оставлять на руках только хендл.

GlobalHandle и LocalHandle — это обратные функции, способные по указателю предварительно зафиксированного блока, вернуть хендл этого блока.
И ещё раз: хендл — это не указатель. Хотя бы потому, что хендл 16-битный, а указатель состоит из 16-битного селектора и 16-битного смещения.

С приходом 32-битных версий поменялось всё. Процессы получили отдельные изолированные АП. Сегменты ушли в тень. Модель памяти стала «плоской». Ушли в прошлое near-, far- и huge-указатели — остались только просто указатели (32-битные). Появилась страничная организация памяти. Появились атрибуты доступа у каждой страницы, а не у всего сегмента целиком.

Появились новые функции для выделения памяти (VirtualAlloc/HeapAlloc). Но старые функции выделения памяти не могли исчезнуть, потому что тонны программ, в коде которых они использовались, должны были компилироваться под новую 32-битную систему без переписывания всего кода.

Функции LocalAlloc и GlobalAlloc остались, и формально они по прежнему возвращали хендл блока, который надо было фиксировать, прежде чем нам дадут указатель, по которому можно писать/читать. В реальности же функции превратились в заглушки-обётки над HeapAlloc, а возвращаемые значения (якобы хендлы) были самыми настоящими адресами.

Так можно ли в итоге функциям, ожидающим на вход HGLOBAL или HLOCAL, передавать указатель, полученный не от LocalAlloc или GlobalAlloc? По идее — нет, потому что типы не соответствует (указатель и хендл).

Можно ли, если функция ожидает на вход HGLOBAL или HLOCAL, передавать указатель, прогнав его через GlobalHandle или LocalHandle? По идее, тоже нет, если мы не уверены точно, что этот указатель мы через цепочку LocalAlloc->LocalLock.

Вопрос в совместимости. Все эти функции остались существовать только для обеспечения совместимости старого кода с новыми ОС. Если нас волнует совместимость нашего кода со старыми ОС, мы должны использовать старые функции строго в рамках той идеологии, в какой они работали. Если нас волнует совместимость нашего кода с новыми ОС, мы вообще не должны использовать эти старые функции, кроме случаев-исключений, указанных в MSDN.
—We separate their smiling faces from the rest of their body, Captain.
—That's right! We decapitate them.

The trick
Постоялец
Постоялец
 
Сообщения: 781
Зарегистрирован: 26.06.2010 (Сб) 23:08

Re: HGLOBAL, GlobalHandle, LocalHandle

Сообщение The trick » 23.06.2019 (Вс) 22:05

arthur2 писал(а):Можно ли для выделения памяти положиться на бейсик и пользоваться просто массивами, а потом получать хендлы с помощью GlobalHandle или LocalHandle, и если да, то какой из них правильней?

Нельзя. Нужно выделять HGLOBAL, если память перемещаема то лочить, писать данные в этот блок, анлочить.
UA6527P

arthur2
Продвинутый гуру
Продвинутый гуру
Аватара пользователя
 
Сообщения: 1688
Зарегистрирован: 23.01.2008 (Ср) 14:35

Re: HGLOBAL, GlobalHandle, LocalHandle

Сообщение arthur2 » 24.06.2019 (Пн) 6:53

Хакер
Спасибо! Было очень интересно. И временам даже казалось, что почти всё понимаю :)

И всё-таки - разве бейсик при создании массива пользуется какими-то другими механизмами? Почему нельзя положиться на GlobalHandle или LocalHandle, если передать им адреса массивов бейсика?

Хакер писал(а):Можно ли, если функция ожидает на вход HGLOBAL или HLOCAL, передавать указатель, прогнав его через GlobalHandle или LocalHandle? По идее, тоже нет, если мы не уверены точно, что этот указатель мы через цепочку LocalAlloc->LocalLock.
Если речь о массиве бейсика - разве мы "не уверены точно, что этот указатель мы через цепочку LocalAlloc->LocalLock"?

Или даже так: разве то, что GlobalHandle вернула не ноль, не означает, что с данным блоком памяти все формальности соблюдены?
Последний раз редактировалось arthur2 24.06.2019 (Пн) 7:02, всего редактировалось 1 раз.
Артур
 
   

arthur2
Продвинутый гуру
Продвинутый гуру
Аватара пользователя
 
Сообщения: 1688
Зарегистрирован: 23.01.2008 (Ср) 14:35

Re: HGLOBAL, GlobalHandle, LocalHandle

Сообщение arthur2 » 24.06.2019 (Пн) 6:56

The trick писал(а):Нельзя.
Почему?
Артур
 
   

Хакер
Телепат
Телепат
Аватара пользователя
 
Сообщения: 16478
Зарегистрирован: 13.11.2005 (Вс) 2:43
Откуда: Казахстан, Петропавловск

Re: HGLOBAL, GlobalHandle, LocalHandle

Сообщение Хакер » 24.06.2019 (Пн) 7:16

arthur2 писал(а):И всё-таки - разве бейсик при создании массива пользуется какими-то другими механизмами? Почему нельзя положиться на GlobalHandle или LocalHandle, если передать им адреса массивов бейсика?

VB не использует ни LocalAlloc, ни GlobalAlloc при выделении массивов. Поэтому и нельзя.

Если речь о массиве бейсика - разве мы "не уверены точно, что этот указатель мы через цепочку LocalAlloc->LocalLock"?

Мы как раз точно уверены, что выделяет он их НЕ через эту цепочку.
Память под массивы выделяется через SafeArrayAllocData, та в свою очередь выделяет через IMalloc::Alloc, а OLE-шная реализация IMalloc::Alloc вызывает HeapAlloc.
—We separate their smiling faces from the rest of their body, Captain.
—That's right! We decapitate them.

Teranas
Бывалый
Бывалый
Аватара пользователя
 
Сообщения: 224
Зарегистрирован: 13.12.2008 (Сб) 4:26
Откуда: Новосибирск

Re: HGLOBAL, GlobalHandle, LocalHandle

Сообщение Teranas » 24.06.2019 (Пн) 11:21

Я очень часто использую динамические массивы вместе с API и VarPtr(Bytes(0)) и всё отлично работает. Или я тоже делаю не правильно?
С уважением, Андрей.

Хакер
Телепат
Телепат
Аватара пользователя
 
Сообщения: 16478
Зарегистрирован: 13.11.2005 (Вс) 2:43
Откуда: Казахстан, Петропавловск

Re: HGLOBAL, GlobalHandle, LocalHandle

Сообщение Хакер » 24.06.2019 (Пн) 13:26

Teranas писал(а):Я очень часто использую динамические массивы вместе с API и VarPtr(Bytes(0)) и всё отлично работает. Или я тоже делаю не правильно?

А здесь что, кто-то говорил что указатель на начало массива нельзя передавать в любые API?
—We separate their smiling faces from the rest of their body, Captain.
—That's right! We decapitate them.

arthur2
Продвинутый гуру
Продвинутый гуру
Аватара пользователя
 
Сообщения: 1688
Зарегистрирован: 23.01.2008 (Ср) 14:35

Re: HGLOBAL, GlobalHandle, LocalHandle

Сообщение arthur2 » 24.06.2019 (Пн) 19:34

Хакер писал(а): а OLE-шная реализация IMalloc::Alloc вызывает HeapAlloc.
Хакер писал(а):В реальности же функции превратились в заглушки-обётки над HeapAlloc
arthur2 писал(а):разве то, что GlobalHandle вернула не ноль, не означает, что с данным блоком памяти все формальности соблюдены?


Раз GlobalHandle/LocalLock возвращают не ноль, а затем функции принимают то, что вернули GlobalHandle/LocalLock и при этом корректно работают, это разве не значит, что в системе эти блоки памяти правильно выделены и правильно залочены?

Зайдём с другого конца :)
Хакер писал(а):По идее — нет...
Это ведь не категорическое нет. На сколько я могу судить, такая речевая конструкция означает: Использовать то-то и то-то в большинстве случаев нельзя. Но в некоторых ситуациях и с некоторыми оговорками - всё же допустимо. Итак, какие ситуации ты имел ввиду?
Артур
 
   

Хакер
Телепат
Телепат
Аватара пользователя
 
Сообщения: 16478
Зарегистрирован: 13.11.2005 (Вс) 2:43
Откуда: Казахстан, Петропавловск

Re: HGLOBAL, GlobalHandle, LocalHandle

Сообщение Хакер » 24.06.2019 (Пн) 20:19

arthur2 писал(а):а затем функции принимают то, что вернули GlobalHandle/LocalLock и при этом корректно работают, это разве не значит, что в системе эти блоки памяти правильно выделены и правильно залочены?


Функция сложения вместо функции умножения возвращает правильный результат для пар чисел (0;0) и (2;2), а остановившиеся часы дважды в сутки показывают правильное время.

arthur2 писал(а):Это ведь не категорическое нет.

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

Можно ли в программе не получать пути %windir%, %programfiles% и %userprofile%? Можно ли жестко забить в программу пути C:\WINDOWS, C:\Program Files и C:\Documents and Settings\firehacker?

По факту, если ты попробуешь именно так и сделать, то эксперимент тебе скорее всего покажет, что можно, ибо всё будет работать.
А по идее нельзя, потому что работать это будет не везде и не всегда.
У меня на компьютере это точно не будет работать, потому что у меня %windir%=c:\os\xp, %programfiles%=c:\soft, и %userprofile%=c:\users\firehacker.

Даже если то, что ты пытаешься сделать, работает, проблема не в том, что оно не работает вообще, проблема в том, что нетникаких гарантий, что оно будет работать у всех и будет работать всегда (что с выходом новой версии всё не сломается).

На всякий случай, если ты считаешь, что внутренности GlobalAlloc в 32-битных Windows выглядат так:
Код: Выделить всё
HGLOBALGlobalAlloc(..., DWORD dwBytes)
{
    return (HGLOBAL)HeapAlloc(GetProcessHeap(), ..., dwBytes);
}

и что HGLOBAL ну совсем ничем не отличается идеологически в плане типа по просто-адреса, то я тебе огорчу, внутренности выглядит так:
Код: Выделить всё
HGLOBAL
WINAPI
GlobalAlloc(
    UINT uFlags,
    SIZE_T dwBytes
    )
{
    PBASE_HANDLE_TABLE_ENTRY HandleEntry;
    HANDLE hMem;
    LPSTR p;
    ULONG Flags;

    if (uFlags & ~GMEM_VALID_FLAGS) {
        SetLastError( ERROR_INVALID_PARAMETER );
        return( NULL );
        }

    Flags = 0;
    if (uFlags & GMEM_ZEROINIT) {
        Flags |= HEAP_ZERO_MEMORY;
        }

    if (!(uFlags & GMEM_MOVEABLE)) {
        if (uFlags & GMEM_DDESHARE) {
            Flags |= BASE_HEAP_FLAG_DDESHARE;
            }

        p = RtlAllocateHeap( BaseHeap,
                             MAKE_TAG( GMEM_TAG ) | Flags,
                             dwBytes ? dwBytes : 1
                           );

        if (p == NULL) {
            SetLastError( ERROR_NOT_ENOUGH_MEMORY );
            }

        return p;
        }

    p = NULL;
    RtlLockHeap( BaseHeap );
    Flags |= HEAP_NO_SERIALIZE | HEAP_SETTABLE_USER_VALUE | BASE_HEAP_FLAG_MOVEABLE;
    try {
        HandleEntry = (PBASE_HANDLE_TABLE_ENTRY)RtlAllocateHandle( &BaseHeapHandleTable, NULL );
        if (HandleEntry == NULL) {
            SetLastError( ERROR_NOT_ENOUGH_MEMORY );
            goto Fail;
            }

        hMem = (HANDLE)&HandleEntry->Object;
        if (dwBytes != 0) {
            p = (LPSTR)RtlAllocateHeap( BaseHeap, MAKE_TAG( GMEM_TAG ) | Flags, dwBytes );
            if (p == NULL) {
                HandleEntry->Flags = RTL_HANDLE_ALLOCATED;
                RtlFreeHandle( &BaseHeapHandleTable, (PRTL_HANDLE_TABLE_ENTRY)HandleEntry );
                HandleEntry = NULL;
                SetLastError( ERROR_NOT_ENOUGH_MEMORY );
                }
            else {
                RtlSetUserValueHeap( BaseHeap, HEAP_NO_SERIALIZE, p, hMem );
                }
            }
Fail:   ;
        }
    except (EXCEPTION_EXECUTE_HANDLER) {
        BaseSetLastNTError( GetExceptionCode() );
        }

    RtlUnlockHeap( BaseHeap );

    if (HandleEntry != NULL) {
        HandleEntry->Object = p;
        if (p != NULL) {
            HandleEntry->Flags = RTL_HANDLE_ALLOCATED;
            }
        else {
            HandleEntry->Flags = RTL_HANDLE_ALLOCATED | BASE_HANDLE_DISCARDED;
            }

        if (uFlags & GMEM_DISCARDABLE) {
            HandleEntry->Flags |= BASE_HANDLE_DISCARDABLE;
            }

        if (uFlags & GMEM_MOVEABLE) {
            HandleEntry->Flags |= BASE_HANDLE_MOVEABLE;
            }

        if (uFlags & GMEM_DDESHARE) {
            HandleEntry->Flags |= BASE_HANDLE_SHARED;
            }

        p = (LPSTR)hMem;
        }

    return( (HANDLE)p );
}


То есть да, блок выделяется с помощью всё той же HeapAlloc. Но при этом некий код, получивший значение типа HGLOBAL, предполагает, что после выделения блока над ним был выполнен ряд манипуляций, а ты им суёшь адрес блока, над которым этих манипуляций не было сделано.
—We separate their smiling faces from the rest of their body, Captain.
—That's right! We decapitate them.

arthur2
Продвинутый гуру
Продвинутый гуру
Аватара пользователя
 
Сообщения: 1688
Зарегистрирован: 23.01.2008 (Ср) 14:35

Re: HGLOBAL, GlobalHandle, LocalHandle

Сообщение arthur2 » 24.06.2019 (Пн) 20:50

Хакер
Тебе кажется, что я спорю с тобой и пытаюсь отстоять свою правоту. Тебе это кажется. Я просто хочу разобраться. Давая исходить из того, что я согласен, что так делать нельзя.

Идём дальше. В каких конкретных случаях такой подход может выйти боком? Скажем, такой код:
Код: Выделить всё
    hMem = GlobalHandle(Bytes(0))
       CreateStreamOnHGlobal ByVal hMem, False, Stream
В какой ситуации он будет работать неправильно?
Хакер писал(а):и что HGLOBAL ну совсем ничем не отличается идеологически в плане типа по просто-адреса
Нет, я так не считаю. И даже когда задавал вопрос, можно ли адрес использовать вместо хендла, предполагал, что, наверное, нельзя :) Но вот почему нельзя использовать как хендл то, что возвращает GlobalHandle - мне непонятно. Это не означает, будто я думаю, что можно. Это означает, что я не понимаю, почему нельзя.
Артур
 
   

Хакер
Телепат
Телепат
Аватара пользователя
 
Сообщения: 16478
Зарегистрирован: 13.11.2005 (Вс) 2:43
Откуда: Казахстан, Петропавловск

Re: HGLOBAL, GlobalHandle, LocalHandle

Сообщение Хакер » 24.06.2019 (Пн) 21:00

arthur2 писал(а):о вот почему нельзя использовать как хендл то, что возвращает GlobalHandle - мне непонятно.

Просто потому, что это нарушает документацию на функцию GlobalHandle. Там относительно аргумента сказано, что значение должно быть получено от GlobalLock,а не абы откуда.
—We separate their smiling faces from the rest of their body, Captain.
—That's right! We decapitate them.

arthur2
Продвинутый гуру
Продвинутый гуру
Аватара пользователя
 
Сообщения: 1688
Зарегистрирован: 23.01.2008 (Ср) 14:35

Re: HGLOBAL, GlobalHandle, LocalHandle

Сообщение arthur2 » 24.06.2019 (Пн) 21:16

Понятно :) И всё таки:

Хакер писал(а):Но при этом некий код, получивший значение типа HGLOBAL, предполагает, что после выделения блока над ним был выполнен ряд манипуляций, а ты им суёшь адрес блока, над которым этих манипуляций не было сделано
Смысл понятен. Теперь конкретно: какие манипуляции над блоком предполагает, например, CreateStreamOnHGlobal, но бейсик не сделал? На сколько я понимаю, блок, выделенный бейсику под массив, не только выделен, но и залочен. Что ещё может понадобиться?
Артур
 
   

Хакер
Телепат
Телепат
Аватара пользователя
 
Сообщения: 16478
Зарегистрирован: 13.11.2005 (Вс) 2:43
Откуда: Казахстан, Петропавловск

Re: HGLOBAL, GlobalHandle, LocalHandle

Сообщение Хакер » 24.06.2019 (Пн) 21:40

arthur2 писал(а):Теперь конкретно: какие манипуляции над блоком предполагает, например, CreateStreamOnHGlobal, но бейсик не сделал?

В общем случае: неизвестно. Нам надо полагаться на общий случай, а не на какой-то частный.

arthur2 писал(а):На сколько я понимаю, блок, выделенный бейсику под массив, не только выделен, но и залочен.

И на сколько ты понимаешь термин «залочен»? Что значит «залочен» в рамках 32-битной ОС с плоской моделью памяти у процессов?
—We separate their smiling faces from the rest of their body, Captain.
—That's right! We decapitate them.

arthur2
Продвинутый гуру
Продвинутый гуру
Аватара пользователя
 
Сообщения: 1688
Зарегистрирован: 23.01.2008 (Ср) 14:35

Re: HGLOBAL, GlobalHandle, LocalHandle

Сообщение arthur2 » 24.06.2019 (Пн) 22:33

понимаю так, что в системе хранится адрес начала данных и длина блока, и это всё помечено, как уже используемое - чтобы не выделить данные отсюда куда-то ещё. Собственно, если вернуться к твоему описанию, получается, что всё это делается в момент выделения блока, а лоченье на 32-битных системах вообще не нужно и потому не делается вовсе? Типа, раз блок выделен, то он уже как бы и залочен? Или я чего-то не того понял?
Артур
 
   

arthur2
Продвинутый гуру
Продвинутый гуру
Аватара пользователя
 
Сообщения: 1688
Зарегистрирован: 23.01.2008 (Ср) 14:35

Re: HGLOBAL, GlobalHandle, LocalHandle

Сообщение arthur2 » 25.06.2019 (Вт) 6:17

Нарыл вот такую ссылку:
https://www.transl-gunsmoker.ru/2009/10 ... ock-4.html
По ней выходит, что и в вин32 хендлы не всегда равны указателям.

Кстати, а массивы бейсика - это перемещаемые или неперемещаемые блоки?
Артур
 
   

iGrok
Артефакт VBStreets
Артефакт VBStreets
 
Сообщения: 4272
Зарегистрирован: 10.05.2007 (Чт) 16:11
Откуда: Сетевое сознание

Re: HGLOBAL, GlobalHandle, LocalHandle

Сообщение iGrok » 27.06.2019 (Чт) 1:54

Я не уверен, что я понимаю всё на 100%, но всё же.

Если я правильно понял, в 32-бит ОС нет больше никаких "залочен" или "незалочен", это фиктивный механизм, оставленный для совместимости со старым ПО и 16-битной системой. То есть они что-то делают, но совсем не то, что раньше.

Т.е. изначально *Alloc возвращали хэндл, *Lock "фиксировали" под него память и возвращали реальный адрес, *Unlock "возвращали" этот реальный адрес "в оборот". Сейчас *Alloc сразу возвращает адрес попутно выполняя дополнительные манипуляции "для совместимости", а *Lock - полная фикция (опять же, с какими-то дополнительными манипуляциями), адрес она не меняет.

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

Опять же, теоретически возможен вариант, что и сейчас это не везде и не всегда будет работать. И вряд ли есть человек, способный ответить на вопрос "а где и когда не будет?". Тут обратная ситуация - точно нет человека, который может гарантировать, что это будет работать всегда и везде.
label:
cli
jmp label

arthur2
Продвинутый гуру
Продвинутый гуру
Аватара пользователя
 
Сообщения: 1688
Зарегистрирован: 23.01.2008 (Ср) 14:35

Re: HGLOBAL, GlobalHandle, LocalHandle

Сообщение arthur2 » 27.06.2019 (Чт) 5:37

iGrok писал(а):Опять же, теоретически возможен вариант, что и сейчас это не везде и не всегда будет работать. И вряд ли есть человек, способный ответить на вопрос "а где и когда не будет?". Тут обратная ситуация - точно нет человека, который может гарантировать, что это будет работать всегда и везде.

Ага :) Старый механизм снова заработает, если перераспределить память на тот же хендл. По ссылке выше:
Что же за история скрывается с флагом GMEM_MOVEABLE в Win32?

Память GMEM_MOVEABLE выделяет "описатель" (handle). Этот описатель может быть сконвертирован в указатель через GlobalLock. Вы можете вызывать GlobalReAlloc на незаблокированном блоке GMEM_MOVEABLE (или заблокированном блоке GMEM_MOVEABLE, когда вы передаёте в GlobalReAlloc флаг GMEM_MOVEABLE, говоря: "перемести его, даже если он заблокирован"), и память будет перемещена, то описатель продолжит ссылаться на неё. Вы должны будете повторно заблокировать её, чтобы получить новый указатель, куда он был перемещён.


В общем и целом - я препридумал, как подружить хендлы с массивами. Я хотел получить хендл на данные массива - теперь делаю наоборот. Сначала выделяю память, а потом перенаправляю на неё SA неинициированного массива.
Артур
 
   

The trick
Постоялец
Постоялец
 
Сообщения: 781
Зарегистрирован: 26.06.2010 (Сб) 23:08

Re: HGLOBAL, GlobalHandle, LocalHandle

Сообщение The trick » 28.06.2019 (Пт) 8:42

Этот механизм удобен тем что приложение выделяет хендл и сохраняет его. Оно может передавать его другим сторонним модулям которые могут изменять эту память и ее размер. Т.к. в этом случае адрес данных может поменяться - хендл остается неизменным.
UA6527P

Хакер
Телепат
Телепат
Аватара пользователя
 
Сообщения: 16478
Зарегистрирован: 13.11.2005 (Вс) 2:43
Откуда: Казахстан, Петропавловск

Re: HGLOBAL, GlobalHandle, LocalHandle

Сообщение Хакер » 28.06.2019 (Пт) 11:33

arthur2 писал(а):В общем и целом - я препридумал, как подружить хендлы с массивами. Я хотел получить хендл на данные массива - теперь делаю наоборот. Сначала выделяю память, а потом перенаправляю на неё SA неинициированного массива.

Зачем вообще кому-то может понадобится для SA-массива получить хендл, олицетворяющий SA-массив как будто память под него выделена не современными, а допотопными морально устаревшими функциями?
—We separate their smiling faces from the rest of their body, Captain.
—That's right! We decapitate them.

arthur2
Продвинутый гуру
Продвинутый гуру
Аватара пользователя
 
Сообщения: 1688
Зарегистрирован: 23.01.2008 (Ср) 14:35

Re: HGLOBAL, GlobalHandle, LocalHandle

Сообщение arthur2 » 01.07.2019 (Пн) 6:28

В этой теме остались для меня невыясненные вопросы.

Как пользоваться "современными, а не допотопными" функциями? Со старым механизмом всё понятно - получаем хендл, по нему в любой момент получаем адрес. А с новыми как? Может всё же отнестись к Globalляляля не как к заглушкам, а как к обёрткам, которыми понятно пользоваться?

Есть ли в новом механизме разделение на перемещаемую и неперемещаемую память (кроме как для совместимости) и в каких случаях какой (кроме, опять же, совместимости) пользоваться?

Глобальные хендлы, на сколько я понял, можно передавать в другие процессы? При этом ведь нужна синхронизация. Она есть встроенная в этом механизме (типа заперта записи на время, пока сам пользуюсь)? Или нужно прикручивать мутексы и семафоры?
Артур
 
   

The trick
Постоялец
Постоялец
 
Сообщения: 781
Зарегистрирован: 26.06.2010 (Сб) 23:08

Re: HGLOBAL, GlobalHandle, LocalHandle

Сообщение The trick » 01.07.2019 (Пн) 9:43

arthur2 писал(а):Есть ли в новом механизме разделение на перемещаемую и неперемещаемую память (кроме как для совместимости) и в каких случаях какой (кроме, опять же, совместимости) пользоваться?

Нет. Сейчас эти функции просто выделяют память в куче. Поэтому правильнее и быстрее работать напрямую с кучей.

arthur2 писал(а):Глобальные хендлы, на сколько я понял, можно передавать в другие процессы? При этом ведь нужна синхронизация. Она есть встроенная в этом механизме (типа заперта записи на время, пока сам пользуюсь)? Или нужно прикручивать мутексы и семафоры?

Нельзя передавать.
UA6527P

arthur2
Продвинутый гуру
Продвинутый гуру
Аватара пользователя
 
Сообщения: 1688
Зарегистрирован: 23.01.2008 (Ср) 14:35

Re: HGLOBAL, GlobalHandle, LocalHandle

Сообщение arthur2 » 01.07.2019 (Пн) 9:59

The trick писал(а):Нельзя передавать.
Тогда что в данном случае обозначает глобальные и чем они отличаются от локальных?
Артур
 
   

The trick
Постоялец
Постоялец
 
Сообщения: 781
Зарегистрирован: 26.06.2010 (Сб) 23:08

Re: HGLOBAL, GlobalHandle, LocalHandle

Сообщение The trick » 01.07.2019 (Пн) 10:55

arthur2 писал(а):
The trick писал(а):Нельзя передавать.
Тогда что в данном случае обозначает глобальные и чем они отличаются от локальных?

Для 32-битных платформ это одно и тоже.
https://www.transl-gunsmoker.ru/2009/10 ... alloc.html
UA6527P


Вернуться в Visual Basic 1–6

Кто сейчас на конференции

Сейчас этот форум просматривают: SemrushBot и гости: 13

    TopList