Что такое код localfree


Содержание

Как я могу назвать LocalFree()?

У меня есть эта часть кода

Должен ли я называть LocalFree(localHandle); или LocalFree(lpText); ?

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

Если вы вызываете LocalLock, вы должны позвонить LocalUnlock (localHandle), прежде чем вы вызываете LocalFree (localHandle).

Почему бы просто не использовать malloc? Есть ли какая-то техническая причина, по которой вам нужно только вызвать LocalAlloc? Пусть среда выполнения сделает для вас работу.

Параметр hMem для LocalFree задокументирован как:

Поэтому в вашем примере вам нужно позвонить:

Не забудьте вызвать LocalUnlock перед вызовом LocalFree, чтобы уменьшить счетчик ссылок. Это необходимо при использовании LMEM_MOVEABLE .

Почему этот вызов LocalFree () вызывает ошибку?

Однако, когда я запускаю приложение, оно вылетает. Отладчик сообщает: Critical error detected c0000374 и указывает на LocalFree() строка в конце фрагмента выше.

Почему эта линия вызывает проблемы? Согласно документации для ppSecurityDescriptor параметр:

«Указатель на переменную, которая получает указатель на дескриптор безопасности объекта. Когда вы закончите использовать указатель, освободите возвращенный буфер, вызвав функцию LocalFree».

…это именно то, что я сделал.

Решение

SID, на который ссылается ваш pSID local var ссылается на данные, уже содержащиеся в дескрипторе безопасности, на который ссылается pSD , Он «владеет» этим SID, а у вас есть ссылка на дескриптор. Вам нужно только освободить последний.

Короткая версия: Удалить FreeSid(pSID) вызов.

Как вызвать LocalFree ()?

У меня есть этот кусок кода

Должен ли я позвонить LocalFree(localHandle); или LocalFree(lpText); ?

hMem Параметр LocalFree документирован как:

Дескриптор локального объекта памяти. Эта ручка возвращается либо LocalAlloc или LocalReAlloc функции. Это не безопасно для освобождения памяти , выделенной с GlobalAlloc .

Так что в вашем образце вам необходимо позвонить:

Не забудьте позвонить LocalUnlock перед вызовом LocalFree , чтобы уменьшить количество ссылок. Это является обязательным при использовании LMEM_MOVEABLE .

Эти функции, как представляется, частью некоторой библиотеки. Я не знаю библиотеку, но, как правило (см таНос () и свободный ()), вы должны позвонить бесплатно () — функция на указатель / объекта, возвращенного таНос () — функция.

Так что в вашем случае это будет LocalFree(localHandle) .

Ни один из тех, но никогда не LocalFree (lpText) Судя по вашему коду, память, на которую указывает lpText будет мусор после вызова LocalFree.

Если вы звоните LocalLock, то вам следует обратиться к LocalUnlock (localHandle) перед вызовом LocalFree (localHandle).

Почему бы просто не использовать таНос? Есть некоторые технические причины вам нужно только позвонить LocalAlloc? Пусть среда сделать работу для вас.

Прочитайте онлайн СПРАВОЧНИК ПО WinAPI | LocalFree

Описание: function LocalFree(Mem: THandle): THandle;

Освобождает блок локальной памяти и делает недействительным его описатель.

Паpаметpы:

Mem: Идентификатоp блока локальной памяти.

Возвpащаемое значение:

В случае успешного завеpшения — нуль; если нет, то Mem. функция находится в файле kernel32.dll

Внимание!

Текст предназначен только для предварительного ознакомительного чтения.

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

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

Все права на исходные материалы принадлежат соответствующим
организациям и частным лицам.

Что такое low-code/no-code платформа и CRM, CRM+, ERP

Суть low-code/no-code (далее просто low-code) в том, чтобы снизить порог создания/изменения информационной системы до уровня бизнес аналитика или даже продвинутого пользователя. Это когда вендор не просто создаёт платформу со встроенным языком и его сотрудники заявляют о том, что сделают для клиента «всё или почти всё» — low-code платформа, это когда бизнес-аналитики или выделенные ответственные на стороне клиента (его сотрудники) могут это «почти всё» сделать сами.

Что входит в понятие на платформе можно «почти всё»?

  1. Формат данных, пользовательские данные
  2. Вычисления
  3. Интерфейсы десктоп/web
  4. Отчеты, дашборды, аналитика
  5. Шаблоны документов, рассылок, нотификаций
  6. Управление процессами
  7. Управление доступом и логированием
  8. Управление личным кабинетом клиентов и данными на сайте

Возможности low-code существенно сокращают путь к результату с цепочки «Задача пользователя – бюджет разработки – бизнес-аналитик – ТЗ – исполнитель – согласование результата – внесение изменений – приёмка» до «Задача пользователя –Бизнес-аналитик – приёмка».

Ключевые сотрудники – это «носители/владельцы знаний о процессах компании». Именно предоставление в их руки инструмента, позволяющего! полностью! создавать/изменять информационную систему предприятия, приводит к:

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

и более «приземлённо»:

  • возможность руководителям разных уровней оперативно пробовать новые модели управления
  • самостоятельная адаптация интерфейса и визуализации к потребностям пользователей
  • быстрое создание новых отчетов, форм, бланков
  • оперативное внесение изменений в управление процессами
  • + много другое

Ниже взгляд на то, как может быть построена система low-code. Один из вариантов. С объяснением ключевых моментов.

1. Формат данных, пользовательские данные

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

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

В текущий момент развития рынка ИТ в РФ много компаний – поставщиков CRM научились добавлять свои справочники. Просто добавления с компромиссом недостаточно, чтобы называться полноценной платформой.

Основные моменты

a) Визуализация данных перед конечным пользователем.

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

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

2. Вычисления

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

  • Динамические вычисления (выполняются каждый запрос к таблице)
  • Вычисления по событиям (выполняются только, когда создается запись в контрольном реестре или происходит изменение контрольного атрибута)
  • Вычисления по расписаниям (происходят, к примеру, ночью или вообще раз в неделю/месяц)

a) Составление алгоритмов вычислений

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

b) При этом, здесь же допускается код на T-SQL.

Код на T-SQL снимает ограничения по сложности вычислений, делая платформу более широкой, чем «для бизнес-аналитика». По сути это снова «отсутствие ограничений». Low-code платформа не должна быть средством только для бизнес-аналитиков – она должна закрывать потребности разработки на платформе готового решения, включая код на встренном языке и, к примеру, T-SQL. Но бизнес-аналитик на low-code платформе должен иметь возможность закрыть бОльшую часть типовых задач.

c) «Учет – это итоги»

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

По сути «представления» – это некий «табличный конструктор». Его доступность бизнес-аналитикам или продвинутым пользователям позволяет собирать таблицы из нескольких таблиц, т.е. создавать представления, которые не хранятся в БД. Представления и их разработка очень важны в анализе и сопоставлении данных, в т.ч. маркетологами. В концепции low-code это означает, что сложные конструкции, которые обычно длительный срок собираются программистами, теперь бизнес-аналитиками могут создаваться «мышкой» в короткие сроки, к тому же и быстро меняться.

e) Агрегаты (регистры)

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

Промежуточный вывод: low-code проектирование готовой конфигурации с точки зрения данных – это закрытие без программирования силами бизнес-аналитика всех вопросов формата БД для бизнеса любого размера и сложности + обязательная при этом скорость разработки, которая получается очень высокой.

3. Интерфейсы десктоп/web

a) Доступность для дизайна

Одним из главных в дизайне интерфейса является принципиальная доступность этой функции бизнес-аналитику, причем, конечно, без программирования. Это значит, что есть компонентный состав (о нём ниже) и есть «мышка», которой можно расставить на форме всё, как требуется, а свойства, функции и пр. задать, к примеру, в инспекторе объектов или в карточках объектов. Сложность форм в low-code платформе не должна быть ничем ограничена.

Применительно к современным CRM и ERP системам дизайнер интерфейсов должен быть, как для десктопа (если система поставляется в десктопном варианте), так и для web.

b) Нарисовал и оно работает

Работа того, что только что было отрисовано – очень важный аспект. Зачастую, в платформах для того, чтобы отрисованный интерфейс работал, код необходим. Пусть и не большой. Это не low-code платформы, даже, если вендор так пытается её представить.

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

c) Компонентный состав

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

  • Пивот
  • Органайзер
  • Индикаторы
  • Итоги
  • Геовизуализация
  • другое

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

d) Карточки записей

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

В low-code платформах для реализации этой возможности должны быть настройки с копированием карточки из одной группы пользователей в другую, при этом, с созданием в каждой их них уникального внешнего вида. Это должно производиться БЕЗ применения встроенного языка.

e) Выход на встроенный язык

При всём сказанном, встроенный язык лишним не будет. Но это дополнение к возможностям low-code:

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

4. Отчеты, дашборды, аналитика

5. Шаблоны документов, рассылок, нотификаций

Собственно, как и в дизайнере отчетов, так и в подготовке шаблонов документов на основе MS Word и MS Excel необходима доступная всем и пользователям в т.ч. визуализация данных, описанная выше. Пользователь в платформе low-code не должен знать названия таблиц в БД, полей и пр. Ему должен быть доступен исчерпывающий визуальный инструментарий доступа ко всем данным, без знания SQL.

Здесь же следует отметить, что правильным является предоставление бизнес-аналитику возможности оперировать, как прямыми ссылками на таблицы, так и обратными. Это позволяет вставлять в шаблоны MS Word – к примеру, в договора таблицы спецификации.

6. Управление процессами

На рынке много систем, заявляющих о наличии инструментов управления процессами. Часто под этим понимают, к примеру, последовательную раздачу задач, или ветвление только одного типа (да/нет, что по сути условный переход).

Платформы low-code должны обладать мощными, доступными без программирования графическими редакторами карт процессов, где бизнес-аналитик должен иметь возможности моделирования:

1. Событий в БД и от этого:

  • настройку межгруппового взаимодействия пользователей
  • создание и изменение записей и их атрибутов
  • нотификацию в чат и ленту новостей
  • запуск обработчиков
  • рассылку почтовых сообщений в т.ч. с динамическими вложениями

2. Планировщик

  • o обработка времени «до» и «после» контрольных и/или ключевых значений атрибутов записей
  • o создание действий, описанных выше на регулярной (расписание) основе

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

7. Управление доступом и логированием

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

Аналогично доступ и его ограничения.

  • До любых, в т.ч. пользовательских данных и справочников
  • До атрибутов данных
  • Доступ на основе вычислений и логики

Отдельно для каждой группы пользователей

8. Управление личным кабинетом клиентов и данными на сайте

Аналогично и управление журналом аудита (логирование)

Ввиду роста грамотности пользователей. Ввиду того, что тем, кто программировал на Фортране, скоро на пенсию. Уверен, что именно за системами управления корпоративными сложными системами типа «платформа low-code» будущее.

Речь НЕ идёт о том, что произойдёт отказ от программирования. Как показано выше – везде может и должен быть шлюз/доступ/другой уровень для того, чтобы определенные вопросы реализовывались на встроенных языках и SQL.

Речь о том, что компаниям платформы low-code выгодны по объективным причинам и тренд на, собственно, говоря более простым языком: автоматизацию работы внедренцев/бизнес-аналитиков – на упрощение и ускорение их работы, очевиден.

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

Блог GunSmoker-а

. when altering one’s mind becomes as easy as programming a computer, what does it mean to be human.

7 июня 2020 г.

Разработка API (контракта) для своей DLL

Или: не создавайте своих DLL, не прочитав эту статью!

Это статья по мотивам вопросов на форумах: «Как мне вернуть строку из DLL?», «Как передать и вернуть массив записей?», «Как передать в DLL форму?».

Чтобы вам не тратить половину жизни на разобраться — в этой статье я принесу всё на блюдечке.

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

Важное примечание : статью нужно читать последовательно. Примеры кода приводятся только как примеры, на каждом шаге (пункте) статьи код примеров добавляется новыми подробностями. Например, в самом начале статьи нет обработки ошибок, указываются «классические» способы (типа, использования GetLastError , соглашения sdtcall и т.д.), которые по ходу статьи заменяются на более адекватные. Сделано так по той причине, чтобы «новые» («необычные») конструкции не вызывали вопросов. Иначе при пришлось бы к каждому примеру вставлять примечание вида: «вот это обсуждается в том пункте ниже, а вот то — в этом вот». В любом случае в конце статьи есть ссылка на уже готовый код, написанный с учётом всего сказанного в статье. Можете просто его брать и использовать. А статья объясняет зачем и почему. Если вам не интересно «зачем и почему» — листайте в конец к заключению и ссылке на скачивание примера.

Содержание

Общие понятия


Когда вы разрабатываете свою DLL, вы должны придумать прототипы экспортируемых из неё функций («заголовки»), а также основанный на них контракт (правила вызова). Всё вместе это образует API вашей DLL. API или Application Programming Interface (программный интерфейс приложения) — это описание способов, которыми один код может взаимодействовать с другим, это средство интеграции приложений.

Когда вы разрабатываете свою DLL, вы должны определить в каких условиях она будет использоваться:

  1. Могут ли её использовать приложения, написанные на другом языке программирования (например, Microsoft Visual C++) — «универсальная DLL»;
  2. Или же библиотеку DLL смогут использовать только приложения, написанные на том же языке — «Delphi DLL».

Это принципиальный момент, решить который вы должны в первую очередь: ещё до того как начнёте писать код и даже проектировать API вашей DLL. Дело в том, что во втором случае («Delphi DLL») вы можете использовать все возможности вашего языка программирования при создании API. К примеру, для Delphi это означает возможность использовать строки, объекты (в частности — формы, компоненты), динамические массивы, специализированные простые типы ( Extended , множества и т.п.) — в общем, всё то, что не существует в других языках. Также это означает возможность обмениваться памятью, делать прозрачную обработку ошибок (межмодульные исключения).

Если вы пойдёте этим путём, то вам следует рассмотреть использования run-time пакетов (BPL) вместо DLL. BPL-пакеты — это специализированные DLL, которые специально «заточены» под использование только в Delphi, что предоставляет вам множество «плюшек». Но об этом чуть позже.

Если же вы разрабатываете «универсальную DLL», то вы не можете использовать возможности вашего языка, которые не существуют в других языках программирования. И в этом случае вы можете использовать только «общеизвестные» типы данных. Но об этом также ниже.

Эта статья — в основном про «универсальные DLL» в Windows.

Что вам необходимо будет создать при разработке API вашей DLL:

  1. Заголовочники, заголовочные файлы (headers) — набор исходных файлов, которые содержат объявления структур и функций, использующихся в API. Как правило, не содержат реализации. Заголовочные файлы предоставляются на нескольких языках — как правило, это язык, на котором написана программа (в нашем случае — Delphi), C++ (как стандарт) и некоторыми дополнительными (Basic и т.п.). Все эти файлы эквивалентны и просто представляют собой перевод из одного языка программирования на другой. Чем больше языков будет в комплекте — тем лучше. Если вы не предоставите заголовочные файлы для какого-то языка, то программисты на этом языке не смогут использовать вашу DLL, пока они сами не переведут файлы с предоставляемого языка (Delphi или C++) на их язык. Т.е. отсутствие заголовочников на каком-то языке — это не красный «стоп», но достаточное препятствие.
  2. Документация — представляет собой словесное описание API и должна указывать дополнительные правила, не заложенные в синтаксисе заголовочников. К примеру, то, что такую-то функцию можно вызвать, передав ей число — это информация из заголовочников. А то, что перед вызовом этой функции нужно вызвать другую функцию — это информация из документации. В такой документации как минимум должно быть формальное описание API — перечисление всех функций, методов, интерфейсов и типов данных с объяснениями «как» и «зачем» (т.н. Reference). Дополнительно, документация может содержать неформальное описание процесса использования DLL (guide, how-to и т.п.). В простейших случаях документация пишется прямо в заголовочниках (комментариях), но чаще всего это файл (или файлы) в формате chm, html или pdf.
Илон Маск рекомендует:  Кросбраузерная Вставка Flash-анимации

SDK (Software Development Kit) — набор из заголовочников + документации. SDK — это то, что необходимо стороннему разработчику для использования вашей DLL. SDK — это то, что вы должны создать и публично распространять для всех желающих использовать вашу DLL.

Типы данных

Если вы хотите получить «универсальную DLL», то вы не можете использовать в вашем API специфичные для Delphi типы данных, потому что они не имеют аналога в других языках. Например, string , array of , TObject , TForm (и вообще — любые объекты и уж тем более компоненты) и т.п.

Что можно использовать? Целочисленные типы ( Integer , Cardinal , Int64 , UInt64 , NativeInt , NativeUInt , Byte , Word и т.п.; я бы не рекомендовал использовать Currency , если только он вам действительно нужен), вещественные ( Single и Double ; я бы рекомендовал избегать типов Extended и Comp , если только они действительно вам нужны и иначе никак), TDateTime (алиас для системного OLEDate ), перечислимые и subrange-типы (с некоторыми оговорками), символьные типы ( AnsiChar и WideChar — но не Char ), строки (только в виде WideString / BSTR ), логический тип ( BOOL , но не Boolean ), интерфейсы ( interface ), в методах которых используются допустимые типы, записи ( record ) из вышеуказанных типов, а также указатели на них (в том числе указатели на массивы из вышеуказанных типов, но не динамические массивы).

Как узнать, какой тип можно использовать, а какой — нет? Относительно простое правило — если вы не видите тип в этом списке, и типа нет в модуле Windows (модуле Winapi.Windows , начиная с Delphi XE2), то этот тип использовать нельзя. Если же тип перечислен мною выше или находится в модуле Windows / Winapi.Windows — используйте его. Это достаточно грубое правило, но для начала — сойдёт.

В случае использования записей ( record ) — вам нужно указать выравнивание данных. Используйте или ключевое слово packed (без выравнивания) или указывайте директиву <$A8>(выравнивание на 8 байт) в начале файла заголовочников.

В случае использования перечислимых типов ( Color = (clRed, clBlue, clBlack); ) — добавьте в начало заголовочников директиву <$MINENUMSIZE 4>(размер перечислимого типа не меньше 4 байт).

Строковые данные и кодировки

Если вам нужно передавать в DLL строки или возвращать из DLL строки — используйте только тип BSTR . Почему?

  1. Тип BSTR есть во всех языках программирования.
    Примечание: по историческим причинам в Delphi тип BSTR называется WideString . Поэтому, чтобы содержимое ваших Delphi-заголовочников было бы более понятным разработчикам на других языках — добавьте в их начало такой код:
    и в дальнейшем используйте тип BSTR .
  2. Тип BSTR ( WideString ) относится к автоматическим типам Delphi, т.е. вам не нужно выделять и освобождать память вручную. За вас всё автоматически сделает компилятор;
  3. Тип BSTR имеет фиксированную кодировку: Unicode. Т.е. у вас не будет проблем с неверной кодовой страницей, приводящей к «кракозябрам»;
  4. Магия компилятора Delphi позволяет просто присваивать BSTR (через оператор присваивания := ) любым строкам Delphi и наоборот. Все необходимые преобразование будут сделаны автоматически под капотом языка, не нужно вызывать никаких функций преобразования;
  5. Память для строк BSTR всегда выделяется через один и тот же менеджер памяти, поэтому у вас никогда не будет проблем с передачей памяти между исполняемыми модулями (см. ниже);

Если по каким-то причинам вы не можете использовать BSTR , то застрелитесь, пожалуйста используйте PWideChar :

  1. Не используйте PAnsiChar , потому что на дворе 2020 год, а не 1995. При использовании PAnsiChar вы получаете кучу головной боли с кодировками;
  2. Не используйте PChar , потому что он определён не однозначно: это может быть или PAnsiChar или PWideChar (в зависимости от версии компилятора).

Аналогично использованию системного имени BSTR вместо Delphi-имени WideString , и для PWideChar тоже можно сделать так:
и далее использовать LPWSTR . LPWSTR — это имя системного типа данных, который в Delphi называется PWideChar .

Конечно же, при использовании LPWSTR / PWideChar вы получаете кучу минусов:

  1. Вам нужно вручную выделять и освобождать память для PWideChar , что увеличивает шансы на проблемы с утечками памяти;
  2. Хотя в некоторых случаях вы можете делать прямые присваивания (например, PWideChar в строку), но чаще — нет. Вам придётся вызывать функции преобразования и/или функции выделения/копирования памяти;
  3. Память для строк PWideChar выделяется как обычно (без специально выделенного менеджера памяти), т.е. у вас есть проблема с передачей памяти через границу модуля (см. ниже);
  4. У PWideChar нет поля для длины. Поэтому если вы хотите передавать строки с #0 внутри и/или вы хотите передавать большие строки, то вам придётся явно передавать длину строки вместе со строкой (два параметра вместо одного).

Читать далее: String и PChar.

ANSI и Unicode

Из вышесказанного напрямую следует, что все ваши экспортируемые функции должны быть в Unicode. Не надо, глядя на Windows API, делать два варианта функций (с суффиксами -A и -W) — делайте один вариант (без суффикса, просто Unicode). Даже если вы разрабатываете на ANSI версии Delphi (Delphi 7) — не надо делать ANSI-варианты экспортируемых функций. Сейчас не 1995 год.

Общий менеджер памяти

(и почему его не нужно использовать)

В языках программирования динамическая память выделяется и освобождается специальным кодом в программе — т.н. менеджером памяти. К примеру, в Delphi менеджер памяти реализует функции вида GetMem и FreeMem . Все прочие способы управления памятью ( New , SetLength , TForm.Create и т.д.) являются переходниками (т.е. где-то внутри они вызывают GetMem и FreeMem ).

Проблема заключается в том, что каждый исполняемый модуль (будь это DLL или exe) имеют свой собственный код менеджера памяти, и, к примеру, менеджер памяти Delphi не знает ничего про менеджер памяти Microsoft C++ (и наоборот). Поэтому, если вы выделите память в Delphi и, к примеру, попытаетесь передать её в код Visual C++, то ничего хорошего не произойдёт. Более того, даже если вы выделите память в Delphi DLL и вернёте её в Delphi exe, то всё будет ещё хуже: в обоих исполняемых модулях используется два разных, но однотипных менеджера памяти. Менеджер памяти exe посмотрит на память и ему покажется, что это его память (ведь она выделена аналогичным менеджером памяти), он попытается её освободить, но только испортит этим данные учёта.

Решение этой проблемы просто — нужно использовать правило: кто выделяет память, тот её и освобождает.

Добиться выполнения этого правила можно разными способами. Часто упоминаемый способ: использование т.н. общего менеджера памяти или менеджера общей памяти (shared memory manager). Суть способа заключается в том, что несколько модулей «договариваются» использовать один и тот же менеджер памяти.

Когда вы создаёте DLL — вам об этой особенности сообщает комментарий в начале .dpr файла DLL: Что переводится как:

Важное примечание об управлении памятью DLL: модуль ShareMem должен быть указан первым элементом в конструкции USES вашей библиотеки И конструкции USES вашего проекта (используйте Project-View Source), если ваша DLL экспортирует какие-либо процедуры или функции, которые передают строки в качестве параметров или результатов функции. Это относится ко всем строкам, передаваемым в и из вашей DLL — даже к тем, которые вложены в записи и классы. ShareMem — это интерфейсный модуль для общего менеджера памяти BORLNDMM.DLL , который должен распространяться вместе с вашей DLL. Чтобы избежать использования BORLNDMM.DLL , передавайте строковую информацию с помощью параметров типа PChar или ShortString .

Это — чудовищно неправильный комментарий:

  1. Комментарий говорит о необходимости использования общего менеджера памяти, как если бы это был единственный способ решения проблемы обмена памяти — что в корне неверно (см. ниже);
  2. Комментарий говорит только о строках, хотя описываемая проблема применима к любым данным с динамическим выделением памяти: объектам, динамическим массивам, указателям;
  3. Комментарий никак не упоминает, что делать с не строковыми данными;
  4. Использование общего менеджера памяти никак не коррелирует с использованием отдельной выделенной DLL. Это — всего лишь одна из возможных реализаций;
  5. Комментарий требует использовать PChar для избежания описываемой проблемы — что также неправильно (см. выше про кодировки);
  6. Комментарий требует использовать ShortString — что, опять же, неверно с точки зрения «универсальной DLL» ( ShortString — тип, специфичный для Delphi). Хотя, это уже придирка, поскольку использование Delphi-строк и Delphi DLL в качестве общего менеджера памяти и так уже ставит крест на «универсальной DLL».

К сожалению, этот комментарий «от самих создателей Delphi» породил огромное количество мифов и плохих практик.

Что же не так с использованием общего менеджера памяти?

  1. Другие языки программирования ничего не знают про менеджер памяти Delphi;
  2. А раз вы ориентируетесь только на Delphi, то зачем вам DLL? Собирайте программу с пакетами выполнения (BPL) — этим вы автоматически получите:
    • Общий менеджер памяти в rtl.bpl ;
    • Гарантию совместимости структуры объектов, поскольку все модули будут собираться одним компилятором;
    • Отсутствие дублирования RTL и VCL (ошибки типа » TForm не совместим с TForm «, два объекта Application и т.д.);
    • Беспроблемную обработку ошибок с исключениями.
  3. Общий менеджер памяти сильно затрудняет поиск утечек памяти, поскольку модуль может загрузиться, выделить память, выгрузиться, а созданная утечка будет найдена только во время финализации менеджера памяти при выходе из программы.

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

Управление памятью в API DLL

Итак, как же вам передать память из DLL в вызывающего и наоборот? Есть несколько способов.

Как неправильно?

Для начала — как делать не следует.

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

Во-вторых, не надо «делать как в Windows». Многие смотрят на Windows API и делают так же. Но при этом они упускают, что этот API создавался хорошо если в 1995 году, а ведь многие функции идут ещё от 16-битных Windows. Те окружение и условия, для которых создавались эти функции, сегодня уже не существуют. Сегодня есть гораздо более простые и удобные способы.

Например, вот типичная Windows функция:

Параметры

lpBuffer
Указатель на буфер для получения имени пользователя. Если этот буфер недостаточно велик, чтобы вместить полное имя пользователя, функция завершается ошибкой.

pcbBuffer
На входе эта переменная указывает размер буфера lpBuffer в символах. На выходе переменная получает количество символов, скопированных в буфер, включая завершающий нулевой символ.

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

Чтобы получить результат от такой функции Windows, её надо вызвать дважды. Сначала вы вызываете функцию, чтобы определить размер буфера, затем вы выделяете буфер, и только потом вызываете функцию снова. А если данные поменяются в это время? Функции снова не хватит места. Таким образом, чтобы надёжно получить полные данные, вам придётся писать цикл. Это же ужас. Не надо так делать.

Строки

Со строками всё просто — используйте BSTR (который WideString ). Это мы подробно разобрали выше.

Обратите внимание, что в некоторых случаях сложные структурированные данные (объекты) вы можете вернуть в виде JSON или аналогичного способа упаковки данных в строку. И если это ваш случай — вы также можете воспользоваться BSTR .

Во всех прочих случаях вам нужно использовать один из трёх способов ниже.

Системный менеджер памяти

Выполнить правило «кто выделяет память, тот её и освобождает» можно следующим образом: попросить выделять и освобождать память третью сторону, про которую знают и вызываемый и вызывающий. К примеру, такой третьей стороной может быть любой системный менеджер памяти. Именно так и работает BSTR / WideString . Вот несколько вариантов, которые вы можете использовать:

  1. Системная куча процесса:
    • HeapAlloc и HeapFree , вызываемые для GetProcessHeap ;
    • GlobalAlloc и GlobalFree ;
    • LocalAlloc и LocalFree .

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

  2. COM-подобные менеджеры памяти:
    • CoTaskMemAlloc и CoTaskMemFree ;
    • SHAlloc и SHFree ;

    И снова: все эти функции сегодня эквивалентны. Несколько вариантов функций появились по историческим причинам.

  3. VirtualAlloc и VirtualFree .

Примечание: функции менеджера памяти COM и оболочки (Shell) можно вызывать сразу, без инициализации COM/OLE.

Довольно большой список. Что же из этого лучше использовать?

  • VirtualAlloc / VirtualFree выделяют память с гранулярностью в 64 Кб, поэтому их использовать нужно только если вам нужно обмениваться данными огромных размеров;
  • GlobalAlloc / GlobalFree и LocalAlloc / LocalFree совсем уж устарели и имеют бо́льшие накладные расходы, нежели HeapAlloc / HeapFree , поэтому их использовать не нужно;

Остаются HeapAlloc / HeapFree и COM. Вариант с Heap вполне может быть вариантом по умолчанию. Менеджеры памяти COM могут быть более привычны некоторым языкам программирования. Кроме того, там есть уже готовый интерфейс менеджера памяти (см. ниже). В общем, тут выбор скорее вкуса, особой разницы нет.

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

Разумеется, при этом в вашем SDK должна быть документация по функции GetDynData , где будет явно сказано, что возвращаемую память нужно освобождать вызовом CoTaskMemFree , например так:

GetDynData

Примечание: разумеется, вызовы CoTaskMemAlloc / CoTaskMemFree вы можете заменить на HeapAlloc / HeapFree или любые другие, удобные вам.

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

Выделенные функции

Другой вариант — обернуть ваш предпочитаемый менеджер памяти в экспортируемую функцию. Соответственно, в документации к функции должно быть указано, что для освобождения памяти нужно вызывать не CoTaskMemFree (или что вы там использовали), а вашу функцию-обёртку. Тогда вы сможете просто возвращать подготовленные данные сразу, без копирования. Например, в DLL (упрощённый код без обработки ошибок):
В exe: Примечание: заметьте, что мы не можем просто скопировать указатель в массив на стороне вызывающего, поскольку контракт GetDynData ничего не говорит про совместимость возвращаемых данных с динамическим массивом Delphi. Действительно, DLL может быть написана на MS Visual C++, в котором нет динамических массивов.

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

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

Если вы будете использовать одну универсальную функцию для очистки, то вы можете возвращать её в виде интерфейса IMalloc . Это будет более привычно для знакомых с основами COM. Но это также позволит вам не только возвращать память вызывающему, но и принимать от него память с передачей владения. Например:
В exe: Примечание: конечно, это немного бессмысленный пример, потому что в данном конкретном случае нет никакой необходимости передавать права на владение AOptions в функцию GetDynData : вызывающий может и сам очистить память, тогда вызываемый может не освобождать память. Но это только пример. В реальных приложениях вам может понадобится держать AOptions внутри DLL дольше вызова функции. В примере показано, как это можно реализовать, обернув менеджер памяти в интерфейс.

Если вы реализуете метод TAllocator.GetSize , то параметр ADataSize можно будет убрать.

Интерфейсы

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

  1. Интерфейс — это запись с указателями на функции, аналог класса с виртуальными функциями. Благодаря этому каждый метод автоматически становится функцией-обёрткой из предыдущего пункта, т.е. всегда работает с правильным менеджером памяти. Иными словами, нет необходимости ни использовать фиксированный сторонний менеджер памяти, ни вводить функции-обёртки;
  2. Интерфейсы понимают все языки программирования;
  3. Интерфейсами можно передавать сложные данные (объекты);
  4. Интерфейсы относятся к типам с автоматической очисткой, не надо явно вызывать функции очистки;
  5. Интерфейсы можно легко модифицировать, расширяя их в будущих версиях DLL;
  6. Способ реализации интерфейсов в Delphi с помощью магии компилятора позволяет легко реализовать правильную обработку ошибок (см. ниже следующий раздел).

Прошлый пример можно реализовать на интерфейсах примерно так, вот DLL:
В exe: В данном случае мы сделали один универсальный интерфейс IData, который можно написать один раз и использовать во всех функциях. Хотя при этом не придётся писать код для каждой функции, но при этом также получается копирование данных на стороне вызываемого, а также отсутствие типизации. Вот как мог бы выглядеть улучшенный вариант, DLL:
В этом случае внешняя обёртка (т.е. интерфейс) остаётся без изменений, меняется только код DLL. Поэтому код вызывающего (в exe) также не меняется. Но если менять контракт (интерфейс), то можно сделать и так:
Тогда на стороне exe можно будет делать так: В общем, тут довольно широкие возможности, можно делать почти как хотите. Причём даже если вы сначала сделали контракт через IData , то позже вы можете добавить ISomethingData , просто расширив интерфейс наследованием. При этом старые клиенты вашей DLL версии 1 будут использовать IData , а клиенты версии 2 могут запросить более удобный ISomethingData .

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

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

Примечание: код выше — просто пример. В реальном коде вам нужно добавить обработку ошибок и вынести определения интерфейсов IData / ISomethingData в отдельные файлы (заголовочники вашего SDK).

Обработка ошибок

(и соглашение вызова)

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

Как правило, минимальными блоками, подвергаемым контролю, являются функция или процедура (подпрограмма). Каждая подпрограмма выполняет определённую задачу. И мы можем ожидать различный уровень «успешности» выполнения этой задачи: выполнение задачи было успешно или же при её выполнении возникла ошибка. Для написания надёжного кода нам совершенно необходим способ обнаружения ошибочных ситуаций — как мы определим, что в функции возникла ошибка? И реагирования на них — так называемое, «восстановление после ошибок» (т.е.: что мы будем делать при возникновении ошибки?). Традиционно, для обработки ошибок используется два основных способа: коды ошибок и исключения.

Коды ошибок

(и почему их не надо использовать)

Коды ошибок — это, пожалуй, самый простой способ реагирования на ошибки. Суть его проста: подпрограмма должна вернуть какой-либо признак успешности выполнения поставленной задачи. Тут есть два варианта: либо она вернёт простой признак (успешно/неуспешно), либо же она вернёт статус выполнения (иначе говоря — «описание ошибки»), т.е. некий код (число) одной из нескольких заранее определённых ситуаций: неверно заданы параметры функции, файл не найден и т.п. В первом случае может существовать дополнительная функция, которая возвращает статус выполнения последней вызванной функции. При таком подходе ошибки, обнаруженные в функции, обычно передаются выше (в вызывающую функцию). Каждая функция должна проверять результаты вызовов других функций на наличие ошибок и выполнять соответствующую обработку. Чаще всего обработка заключается в простой передаче кода ошибки ещё выше, в «более верхнюю» вызывающую функцию. Например: функция A вызывает B, B вызывает C, C обнаруживает ошибку и возвращает код ошибки в B. B проверяет возвращаемый код, видит, что возникла ошибка, и возвращает код ошибки в A. A проверяет возвращаемый код и выдает сообщение об ошибке (либо решает сделать что-нибудь еще).

Например, вот типичная функция Windows API:

RegisterClassEx

Если функция завершается успешно, возвращаемое значение является атомом класса, который однозначно идентифицирует регистрируемый класс. Этот атом может использоваться только функциями CreateWindow , CreateWindowEx , GetClassInfo , GetClassInfoEx , FindWindow , FindWindowEx и UnregisterClass , а также методом IActiveIMMap.FilterClientWindows .

Если функция завершается ошибкой, возвращаемое значение равно нулю. Чтобы получить расширенную информацию об ошибке, вызовите GetLastError .

Это — типичный способ обработки ошибок в классическом API Windows. При этом используются т.н. Win32 error codes. Это — обычное число типа DWORD . Коды ошибок закреплены и объявлены в модуле Windows. За отсутствие ошибки принимается значение ERROR_SUCCESS или NO_ERROR равное 0. Для всех ошибок определены константы, начинающиеся (обычно) со слова ERROR_ , например: Описание Win32-ошибки можно получить через функцию FormatMessage . В Delphi для этой системной функции с кучей параметров имеется (конкретно для нашего случая) более удобная для использования оболочка: функция SysErrorMessage . Она, по переданному ей коду ошибки Win32, возвращает его описание. Кстати, обратите внимание, что сообщения возвращаются локализованными. Т.е. если у вас русская Windows, то сообщения будут на русском. Если английская — на английском.

Суммируя сказанное, вызывать такие функции приходится примерно так:

Как и в случае с управлением памятью — и в этом случае не надо следовать примеру Windows. Подобный стиль давно уже устарел. И вот что с ним не так (это не полный список):

  1. Чтобы вызвать функцию требуется два вызова: сама функция и GetLastError (добавьте к этому необходимость дважды вызывать саму функцию для получения от неё памяти — получается вообще страшный ужас аж в четыре вызова функций вместо одного);
  2. Вам требуется явно писать проверку вида if что-то then ошибка . И если вы забудете написать этот код, то получите баг: ваша программа будет продолжать выполнение при ошибке. Вероятно, портя данные и затрудняя локализацию бага (видимая проблема случится позже);
    • Подобные if-проверки также сильно визуально засоряют код;
  3. Если при ошибке вам нужно освободить какие-то ресурсы, да ещё если их несколько и вызовов функций тоже несколько, то правильный код для освобождения ресурсов может стать весьма нетривиальным;
  4. Вы никак не передадите дополнительную информацию. Например, никак не укажете какой именно аргумент неверен, или доступа к какому файлу у вас нет;
    • Вы никак не узнаете, какая именно функция завершилась неудачно: была ли это вызываемая вами функция, или, быть может, какая-то другая функция, которую могла вызвать вызываемая вами;
  5. Отладчик никак не уведомит вас о проблеме (хотя, гипотетически, вы можете поставить точку останова на GetLastError ).

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

Исключения

(и почему их не надо использовать)

Исключения лишены многих минусов кодов ошибок:

  1. Исключения не нужно явно проверять, ситуация по умолчанию — реакция на ошибку;
    • Программа не «засоряется» кодом проверки, он выносится отдельно;
  2. Легко освобождать ресурсы (через try — finally );
  3. Исключения легко расширять, наследовать, добавлять дополнительные поля, делать вложенные исключения;
  4. Отладчик уведомит вас о возникновении исключений;
    • Вы можете назначить свой код для диагностики исключений (т.н. трейсер исключений).

Но несмотря на все плюсы, у исключений есть один существенный минус, который перечёркивает все плюсы (применительно к API DLL).

Вспомните как возбуждаются исключения в Delphi:
Я разделил типичную строку » raise EMyExceptionClass.Create(‘Something’); » на две, чтобы проблема стала ещё более очевидной. Мы создаём объект Delphi (исключение) и «кидаем» (throw) его. И тот, кто хочет обработать это исключение, делает так:
Это означает, что объект Delphi передаётся от вызываемого (DLL), где исключение возбуждается, в вызывающего (exe), где исключение обрабатывается. Как мы узнали ранее (см. раздел «Типы данных») это — проблема. Другие языки программирования не знают что такое объект Delphi, как его прочитать, как его удалить. Даже сама Delphi не всегда это знает (например, если исключение возбуждается кодом, собранном на Delphi 7, а ловится кодом, собранном на Delphi XE, или наоборот). В других языках программирования используются похожие конструкции: исключение представлено объектом. Соответственно, Delphi код понятия не имеет как нужно работать с объектами на других языках.

Иными словами, исключения не нужно использовать по причине языковой несовместимости.

Следствие 1: исключения не должны покидать вашу DLL.

Следствие 2: вы должны ловить все исключения в своих экспортируемых функциях.

Следствие 3: все экспортируемые функции обязаны иметь глобальную конструкцию try — except .

Что использовать можно и нужно

(и какое использовать соглашение вызова)

Если мы не можем использовать коды ошибок и не можем использовать исключения, то что нам нужно использовать? А использовать нам нужно их комбинацию — и вот как она выглядит.

В Delphi есть встроенная магия компилятора, которая может обернуть любую функцию в скрытый блок try — except с автоматическим (скрытым) вызовом функции обработки. И есть магия компилятора, которая работает наоборот: по возвращаемому коду ошибки автоматически возбуждает подходящее исключение.

Прежде чем мы познакомимся с этим волшебством, нам нужно познакомится с кодами ошибок в виде типа HRESULT . HRESULT — это тоже число, но теперь уже типа Integer . HRESULT уже не просто код ошибки, он состоит из нескольких частей, подробно рассматривать которые мы не будем, но достаточно сказать, что они включают в себя собственно код ошибки (то, что раньше было Win32 кодом), признак успешности или ошибки, идентификатор возбудителя ошибки. Коды ошибок начинаются с префикса E_ (например, E_FAIL , E_UNEXPECTED , E_ABORT или E_ACCESSDENIED ), а коды успеха — с S_ (например, S_OK или S_FALSE ). Легко определить успешность кода HRESULT можно сравнив его с нулём: ошибочные коды HRESULT должны быть меньше нуля.

Выделение признака успеха/ошибки означает, что теперь нет необходимости функции возвращать только этот признак (через BOOL ), а сам код ошибки — через отдельную функцию ( GetLastError ). Теперь функция может вернуть всю информацию сразу, за один вызов:

Вместе с введением HRESULT был придуман и интерфейс IErrorInfo , который позволяет ассоциировать с возвращаемым HRESULT дополнительную информацию: произвольное описание, GUID возбуждающего (интерфейса), место возбуждения ошибки (произвольная строка), справку. Вам даже не нужно реализовывать этот интерфейс, в системе уже есть готовый объект — возвращаемый функцией CreateErrorInfo .

Наконец, в Delphi есть уже упоминаемая магия компилятора, которая может упростить написание такого кода. Для этого функция должна иметь соглашение вызова stdcall и возвращать HRESULT . Если раньше функция возвращала какой-то Result , то его надлежит сделать последним out-параметром, например: Если функция удовлетворяет этим требованиям, то вы можете объявить её так:
Этот вариант будет двоично эквивалентен (т.е. полностью совместим) такому:
Объявив функцию как safecall вы включите для неё магию компилятора, а именно:

  1. Возвращаемый результат будет автоматически преобразован в последний out-параметр;
  2. Функция будет скрыто передавать HRESULT (и, возможно, IErrorInfo );
  3. Вызов функции будет обёрнут в if-проверку возвращаемого кода. При получение ошибочного HRESULT будет возбуждено исключение:
  4. Сама функция будет обёрнута в скрытый блок try — except , преобразующий исключение в HRESULT (и, возможно, в IErrorInfo ):

Как вы видите, с такой поддержкой компилятора — можно писать код почти как если бы это была обычная функция в обычном модуле Delphi. И что самое вкусное — подобный подход могут использовать и другие языки программирования. Конечно, в них может не быть подходящей магии компилятора, но они вполне способны принять HRESULT у stdcall функции и проанализировать его (возможно, вместе с IErrorInfo ).

Как правильно работать с safecall

Теперь, когда мы описали плюсы safecall , то настала пора для ложки дёгтя. Дело в том, что «из коробки» магия safecall работает в минимальном режиме. И чтобы получить от неё максимальную выгоду, нам нужно сделать дополнительные действия. К счастью, их нужно сделать один раз и можно использовать повторно в дальнейшем.

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

Как видите, все экспортируемые функции мы поместили в единый интерфейс (объект) — это позволит нам настроить/контролировать процесс преобразования исключений в HRESULT . При этом DLL экспортирует единственную функцию, которую нам пришлось описать вручную, без safecall — что также позволило нам контролировать процесс преобразования. Не забываем, что она двоично совместима с safecall , поэтому, если вы хотите использовать эту DLL в Delphi, то вы можете делать так:
и это будет прекрасно работать.

Что касается объектов, то при возбуждении исключения в safecall методе компилятор вызывает виртуальный метод TObject.SafeCallException , который по умолчанию не делает ничего полезного, и который мы можем заменить на свой метод:

Далее, когда код вызывает safecall метод, компилятор заворачивает вызов метода в обёртку CheckAutoResult , которая (в случае ошибочного кода) возбуждает исключение через глобальную функцию-переменную SafeCallErrorProc , которую, опять же, мы можем заменить на свою:
Теперь нам осталось сделать так, чтобы наши HandleSafeCallException и RaiseSafeCallException работали бы парой и делали что-то полезное.

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

Далее нам необходим способ как-то передавать имя класса исключения. Делать это можно разными способами. Например, передавать его непосредственно в HRESULT . Для этого его нужно закодировать. Например, так:
Здесь мы проверяем на несколько специальных предопределённых классов, а также у нас есть возможность прямо передавать коды Win32 и коды аппаратных исключений. Для всех прочих (специфичных для Delphi) классов исключений мы используем хэш от имени класса вместе с FACILITY_ITF . В качестве хэша можно использовать, к примеру, SDBM — это очень простая хэш-функция с хорошей рандомизацией результата. Конечно, вы можете использовать любой другой способ — например, просто вручную выделить и зафиксировать коды для каждого класса исключения.

HRESULT с типами FACILITY_NULL и FACILITY_RPC имеют универсальное значение, поскольку они определены Microsoft. HRESULT с FACILITY_ITF определяются функцией или методом интерфейса, из которых они возвращаются. Это означает, что одно и то же 32-битное значение в FACILITY_ITF , но возвращаемое двумя разными интерфейсами, может иметь разный смысл. Таким образом, Microsoft может определять несколько универсальных кодов ошибок, в то же время позволяя другим программистам определять новые коды ошибок, не опасаясь конфликта. Соглашение распределении кодов выглядит следующим образом:

  • HRESULT с типами, отличных от FACILITY_ITF , могут быть определены только Microsoft;
  • HRESULT с типом FACILITY_ITF определяются исключительно разработчиком интерфейса или функции, которая возвращает HRESULT . Чтобы избежать конфликтующих HRESULT , тот, кто определяет интерфейс, отвечает за координацию и публикацию кодов HRESULT , связанных с этим интерфейсом;
  • Все HRESULT , определяемые Microsoft, имеют значение кода ошибки в диапазоне $0000-$01FF. Хотя вы можете использовать любой код вместе с FACILITY_ITF , но рекомендуется использовать значения в диапазоне $0200-$FFFF. Эта рекомендация предназначена для уменьшения путаницы с кодами Microsoft.

Вот почему в коде выше мы также определили ThisDllIID — это идентификатор «интерфейса», который задаёт смысл для возвращаемых кодов с типом FACILITY_ITF . Это значение нужно передавать как ErrorIID в SetErrorInfo , определённую выше.

29-й «Customer» бит изначально был зарезервированным битом, который в дальнейшем был выделен для использования в качестве флага, указывающего определён ли код Microsoft (0) или сторонним разработчиком (1). В некотором роде этот бит дублирует FACILITY_ITF . Обычно даже сторонние разработчики используют только FACILITY_ITF . В данном же случае мы его ставим для уменьшения возможных проблем с плохим кодом (который не учитывает GUID интерфейса).

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

Также замечу, что поле Source интерфейса IErrorInfo должно указывать на место, в котором возникла ошибка. Поле это произвольно и определяется разработчиком интерфейса (т.е. опять же по GUID). В данном случае мы просто туда пишем адрес исключения. Но, к примеру, если вы используете трейсер исключений, то можете писать туда стек вызовов.

Тогда с указанными выше вспомогательными функциями, наши HandleSafeCallException и RaiseSafeCallException становятся тривиальными:
Примечание: в нашей модели мы не используем поля для справки интерфейса IErrorInfo .

Надо заметить, что если интерфейс использует вместе с HRESULT и IErrorInfo , то ему также следует реализовывать ещё и интерфейс ISupportErrorInfo . Некоторые языки программирования этого требуют. Вызывая ISupportErrorInfo.InterfaceSupportsErrorInfo , клиентская сторона может определить, что объект поддерживает дополнительную информацию.


И последний момент — в реализации Delphi для Windows 32-бита есть неприятный баг, которого нет в 64-битном RTL, а также на других платформах. Исправление этого бага включено в примеры кода по ссылке в конце статьи.

Если у вас возникают проблемы с отладкой вашей DLL, то я разобрал этот вопрос в этой статье.

Обход проблем DllMain

DllMain — это специальная функция в DLL, которая вызывается системой, когда DLL загружается в процесс, выгружается из него (а также подключается/отключается к/от потока). К примеру, секции initialization и finalization ваших Delphi модулей выполняются именно внутри DllMain .

Проблема заключается в том, что DllMain — это очень специальная функция. Она вызывается во время удержания критической секции загрузчика (модулей) операционной системы. Если говорить длинно и подробно — посмотрите ссылки в конце этого пункта, а если говорить кратко: DllMain — это оружие, из которого вы легко можете застрелиться. Есть не так много вещей, которые можно делать легально в DllMain. Зато невероятно легко сделать что-то запретное — вам постоянно нужно быть уверенными, что вот эта вот функция, которую вы только что вызвали, никогда и ни при каких условиях не может выполнить что-либо запретное. Это делает невероятно сложным использование кода, написанного в другом месте. А компилятор вам ничего не скажет. И код чаще всего будет работать, как будто так и должно быть. но иногда он будет вылетать или зависать.

Решение проблемы заключается в том, чтобы не делать ничего в DllMain (читай: не пишите кода в секциях initialization и finalization модулей когда вы создаёте DLL).

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

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

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

Разумеется, в документации вашего SDK должны быть слова о том, что использующий вашу DLL обязан вызвать метод InitDLL сразу после загрузки вашей DLL функцией LoadLibrary и вызвать метод DoneDLL непосредственно перед выгрузкой DLL функцией FreeLibrary :

Callback-функции

(функции обратного вызова)

Callback-функция (англ. call — вызов, англ. back — обратный) или функция обратного вызова в программировании — передача исполняемого кода в качестве одного из параметров другого кода. К примеру, если вы хотите установить таймер с использованием Windows API, вы можете вызвать функцию SetTimer , передав в неё указатель на свою функцию, которая и будет callback-функцией. Система будет вызывать вашу функцию каждый раз, когда срабатывает таймер: Вот ещё пример: если вы хотите найти все окна на рабочем столе, вы можете использовать функцию EnumWindows : Поскольку функция обратного вызова обычно выполняет ту же задачу, что и код, который её устанавливает, то получается, что обоим кускам кода нужно работать с одними и теми же данными. Следовательно, данные от устанавливающего кода необходимо как-то передать в функцию обратного вызова. Для этой цели в функциях обратного вызова предусматриваются т.н. user-параметры: это либо указатель, либо целое число (обязательно типа Native(U)Int, но не (U)Int), который никак не используются самим API и прозрачно передаются в callback-функцию. Либо (в редких случаях) это может быть какое-то значение, уникально идентифицирующее вызов функции.

К примеру, в SetTimer есть idEvent , а в EnumWindows есть lpData . Мы можем использовать эти параметры, чтобы передать произвольные данные. Вот, к примеру, как можно найти все окна заданного класса:
Примечание: вот ещё один пример как не надо делать — не надо делать как в Windows. Если вам нужно просто получить список чего-то — не надо делать функцию обратного вызова, просто верните список в массиве (завернув его в интерфейс или передавая как блок памяти). Функцию обратного вызова нужно использовать только если список создаётся долго, а вам не нужны все элементы. Тогда функция обратного вызова может вернуть признак «стоп», не завершая создание полного списка до конца.

Примечание: неким аналогом user-параметров являются свойства Tag и Data , хотя их использование не всегда бывает идеологически верным (правильно: создать класс-наследник).

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

Разумеется, вместо функции + параметр можно использовать просто интерфейс:
Это — предпочтительней, потому что и обработка ошибок через safecall в интерфейсах проще, и интерфейс может содержать сколько угодно много параметров, и с объектами (формой) удобнее интегрироваться. Например:

Прочие правила

  1. Если вы не только разрабатываете, но и используете DLL, то загружайте DLL правильно;
  2. Если по какой-то причине вы не используете safecall , то не возвращайте сложные типы через Result , делайте out-параметр. Проблема в том, что Delphi и MS Visual C++ расходятся во мнении как трактовать возвращаемый по ссылке stdcall-функцией результат: как var или как out . Соответственно, для safecall такой проблемы нет, поскольку Result у неё — всегда Integer ( HRESULT ) — простой тип, для которого var и out эквивалентны;
  3. Все интерфейсы API должны иметь уникальный IID/GUID (интерфейсы вне API (не упомянутые в заголовочниках) могут не иметь GUID, хотя я бы рекомендовал всегда указывать IID). Создать GUID для использования в качестве IID (Interface ID) вы можете нажав Ctrl + Shift + G в редактора кода Delphi — эта комбинация вставит выражение вида [‘‘] (разумеется, каждый раз с уникальным GUID) прямо под курсор в редакторе;
  4. Как только вы опубликовали какой-то тип (интерфейс), т.е. выпустили «на волю» вашу DLL с этим интерфейсом — вы не должны его изменять. Если вам нужно его расширить или изменить — вы вводите новый интерфейс (новую версию интерфейса), но не меняете старый
  5. Если функция или метод возвращает интерфейс, то не надо делать так: Конечно, в начале это удобное решение: вы можете вызывать функции «как обычно» и даже сцеплять их в цепочки вида Control . GetPicture . GetImage . GetColorInfo . GetBackgroundColor . Однако такое положение дел будет существовать только в самой первой версии системы. Как только вы начнёте развивать систему, у вас начнут появляться новые интерфейсы. В не столь отдалённом будущем у вас будет куча продвинутых интерфейсов, а базовые интерфейсы, которые были в программе изначально, в момент её рождения, будут реализовывать лишь тривиально-неинтересные функции. Итого, очень часто вызывающему коду будут нужны новые интерфейсы, а не оригинальные. Что это значит? Это значит, что коду нужно вызвать оригинальную функцию, получить оригинальный интерфейс, затем запросить у него новый (через Supports / QueryInterface ) и лишь затем использовать новый интерфейс. Получается не так удобно, даже скорее неудобно: имеем тройной вызов (оригинальный + конвертация + нужный). Лучшее решение заключается в том, чтобы вызывающий код указывал бы вызываемой функции, какой интерфейс его интересует: новый или старый:
  6. Если объект реализует интерфейс, то в вашем коде не должно быть переменных этого класса. Т.е.:
  7. Если вы реализуете расширение интерфейса наследованием — не забудьте явно перечислить все его предки в реализующем объекте. Например: Правильно делать так:
  8. Не нужно делать реализацию методов интерфейса виртуальными:
    Делайте так:
  9. Не помечайте интерфейсные параметры модификатором const :
  10. Если в будущем вы будете расширять API DLL, то вам следует следовать правилам расширения и обратной совместимости;
  11. Прочие негласные правила.
  12. Check-список для проверки вашего API.

Заключение

Скачать пример DLL API можно тут. В архиве — группа из двух проектов (DLL и использующее её приложение). DLL реализует пример API с функциями-примерами. В папке SDK лежит SDK, состоящий из:

  • Заголовочника SampleDLLHeaders.pas ;
  • Документации в формате CHM и PDF (а также исходники в виде проекта Help&Manual);
  • А также специфичного для Delphi файла поддержки DelphiSupport.pas .

Разработчики на других языках программирования могут использовать SampleDLLHeaders.pas , а разработчики Delphi — SampleDLLHeaders.pas и DelphiSupport.pas .

Заголовочники представлены только в виде Delphi кода. Перевод на другие языки программирования оставлен в качестве домашнего задания. Автоматизировать перевод можно с помощью библиотеки типов (TLB), как описано здесь.

LocalFree

Important:
This is retired content. This content is outdated and is no longer being maintained. It is provided as a courtesy for individuals who are still using these technologies. This content may contain URLs that were valid when originally published, but now link to sites or pages that no longer exist.

This function frees the specified local memory object and invalidates its handle.

Parameters hMem Handle to the local memory object. This handle is returned by either the LocalAllocor LocalReAllocfunction.

NULL indicates success. A handle to the local memory object indicates failure. To get extended error information, call GetLastError.

If the process tries to examine or modify the memory after it has been freed, heap corruption may occur or an access violation exception (EXCEPTION_ACCESS_VIOLATION) may be generated.

If the hMem parameter is NULL, LocalFree ignores the parameter and returns NULL.

The LocalFree function will free a locked memory object. A locked memory object has a lock count greater than zero.

If an application is running under a debug version of the system, LocalFree issues a message that tells you that a locked object is being freed. If you are debugging the application, LocalFree enters a breakpoint just before freeing a locked object. This allows you to verify the intended behavior and then continue execution.

For Windows CE versions 1.0 through 2.12, allocating memory approximately 0 to 7 bytes under 192K in size causes the corresponding call to LocalFree to fail for certain memory blocks in this size range. The return code is ERROR_INVALID_PARAMETER.

Runs on Versions Defined in Include Link to
Windows CE OS 1.0 and later Winbase.h Lmem.lib

Note This API is part of the complete Windows CE OS package as provided by Microsoft. The functionality of a particular platform is determined by the original equipment manufacturer (OEM) and some devices may not support this API.

Должен ли Marshal.FreeHGlobal быть вызван или LocalFree? — c#

Посмотрите кусок кода, который я не уверен, нужно ли мне освобождать память. Если у меня есть этот блок ниже:

Нужно ли мне называть Marshal.FreeHGlobal(buf) ?

Из моего ограниченного понимания (и из этого ТАК), я не думаю, что мы должны назвать FreeHGlobal поскольку мы не называем Marshal.AllocHGlobal . Тем не менее, я также прочитал из этого SO, что LocalFree возможно, нужно будет назвать?

Любой совет, какой правильный способ освободить эту память (если мне вообще нужно что-то делать)?

UPDATE: Просто в случае, если кто — либо заинтересован в IDisposable класса — оболочки, есть большая статья здесь.

    1 1
  • 20 июл 2020 2020-07-20 04:06:52
  • AshesToAshes

1 ответ

Если вы не знаете, что на самом деле делает библиотека базового класса, вы всегда можете посмотреть исходный код:

Как вы можете видеть, на самом деле это не распределение неуправляемой памяти, а просто назначение int частному void* .

Что такое код localfree

Frees the specified local memory object and invalidates its handle.

Syntax

Parameters

A handle to the local memory object. This handle is returned by either the LocalAlloc or LocalReAlloc function. It is not safe to free memory allocated with GlobalAlloc.

Return value

If the function succeeds, the return value is NULL.

If the function fails, the return value is equal to a handle to the local memory object. To get extended error information, call GetLastError.

Remarks

If the process tries to examine or modify the memory after it has been freed, heap corruption may occur or an access violation exception (EXCEPTION_ACCESS_VIOLATION) may be generated.

If the hMem parameter is NULL, LocalFree ignores the parameter and returns NULL.

The LocalFree function will free a locked memory object. A locked memory object has a lock count greater than zero. The LocalLock function locks a local memory object and increments the lock count by one. The LocalUnlock function unlocks it and decrements the lock count by one. To get the lock count of a local memory object, use the LocalFlags function.

If an application is running under a debug version of the system, LocalFree will issue a message that tells you that a locked object is being freed. If you are debugging the application, LocalFree will enter a breakpoint just before freeing a locked object. This allows you to verify the intended behavior, then continue execution.

Examples

Requirements

Minimum supported client

Windows XP [desktop apps | UWP apps]

Minimum supported server

Windows Server 2003 [desktop apps | UWP apps]

Функции Win32 API [ L ]

Delphi , Синтаксис , Справочник по API-функциям

Описание:
function LimitEmsPages(KBytes: Longint);

Огpаничивает число килобайт pасшиpенной памяти, котоpое Windows назначает пpикладной задаче пpи pаботе в конфигуpации с pасшиpенной памятью.

Паpаметpы:
KByte: Число килобайт.

Описание:
function LineDDA(X1, Y1, X2, Y2: Integer; LineFunc: TFarProc; Data: Pointer);

Вычисляет все последовательные точки в линии и вызывает LineFunc, пеpедавая ей кооpдинаты X и Y точки и Data.

Паpаметpы:
X1, Y1 — пеpвая точка в линии.
X2, Y2 — последняя точка в линии.
LineFunc: Адpес экземпляpа пpоцедуpы функции обpатного вызова.
Data: Данные, пеpедаваемые функции, заданной паpаметpом LineFunc.

функция находится в файле gdi32.dll

Описание:
function LineTo(DC: HDC; X, Y: Integer): Bool;

Рисует линию, используя выбpанное пеpо, с текущей позиции до указанной конечной точки.

Паpаметpы:
DC: Идентификатоp контекста устpойства.
X, Y: Конечная точка линии.

Возвpащаемое значение:
Не нуль, если наpисована; 0 — если нет.

функция находится в файле gdi32.dll

Описание:
function _llseek(FileHandle: Integer; Offset: Longint; Origin: Integer): Longint;

Устанавливает указатель в откpытом файле.

Паpаметpы:
FileHandle: Описатель файла DOS.
Offset: Число байт, на котоpое пеpемещается указатель.
Origin: Указывает начальную точку и напpавление пеpемещения: (0) впеpед от начала; (1) с текущей позиции; (2) назад от конца файла.

Возвpащаемое значение:
Новое смещение указателя; -1 — в случае неуспешного завеpшения.

Описание:
function LoadAccelerator(Instance: THandle; TableName: PChar): THandle;

Загpужает поименованный файл акселеpатоpов из исполнимого файла.

Паpаметpы:
Instance: Экземпляp модуля, исполнимый файл котоpого содеpжит таблицу акселеpатоpов.
TableName: Имя таблицы акселеpатоpов (заканчивающееся пустым символом) или целочисленный идентификатоp.

Возвpащаемое значение:
В случае успешного завеpшения — идентификатоp таблицы акселеpатоpов; 0 — в пpотивном случае.

Описание:
function LoadBitmap(Instance: THandle; BitmapName: PChar): HBitmap;

Загpужает поименованный pесуpс каpты бит.

Паpаметpы:
Instance: Экземпляp модуля, исполнимый файл котоpого содеpжит каpту бит или 0 для пpедопpеделенной каpты бит.
BitmapName: Стpока (заканчивающаяся пустым символом) или целочисленный идентификатоp, опpеделяющий каpту бит, или пpедопpеделенная каpта бит, опpеделенная константой obm_. См.
pаздел «Пpедопpеделенные каpты бит, obm_» в главе 1.

Возвpащаемое значение:
В случае успешного завеpшения — идентификатоp каpты бит; 0 — в пpотивном случае.

функция находится в файле user32.dll

Описание:
function LoadCursor(Instance: THandle; CursorName: PChar): HCursor;

Загpужает поименованный pесуpс куpсоpа.

Паpаметpы:
Instance: Экземпляp модуля, исполнимый файл котоpого содеpжит куpсоp или 0 для пpедопpеделенного куpсоpа.
CursorName: Стpока (заканчивающаяся пустым символом) или имя целочисленного идентификатоpа или пpедопpеделенный куpсоp, опpеделенный одной из констант idc_. См. pаздел
«Идентификатоpы стандаpных куpсоpов, idc_» в главе 1.

Возвpащаемое значение:
В случае успешного завеpшения — идентификатоp куpсоpа; 0 — если куpсоp не найден; не опpеделено, если pесуpс не является pесуpсом куpсоpа.

функция находится в файле user32.dll

Описание:
function LoadIcon(Instance: THandle; IconName: PChar): HIcon;

Загpужает поименованный pесуpс пиктогpаммы.

Паpаметpы:
Instance: Экземпляp модуля, исполнимый файл котоpого содеpжит пиктогpамму или 0 для пpедопpеделенной пиктогpаммы.
IconName: Стpока или имя целочисленного идентификатоpа или пpедопpеделенная пиктогpамма, опpеделенная одной из констант idi_. См. pаздел «Идентификатоpы стандаpных пиктогpамм,
idi_» в главе 1.

Возвpащаемое значение:
В случае успешного завеpшения — идентификатоp пиктогpаммы; 0 — в пpотивном случае.

функция находится в файле user32.dll

Описание:
function LoadLibrary(LibFileName: PChar): THandle;

Загpужает поименованный модуль библиотеки.

Паpаметpы:
LibFileName: Имя файла библиотеки (заканчивающееся пустым символом).

Возвpащаемое значение:
В случае успешного завеpшения — идентификатоp экземпляpа модуля библиотеки (значение, больше 32); если нет, то его значение меньше 32 и является одним из следующих: (0) нет памяти;
(5) попытка связать задачу; (11) невеpный файл EXE; (12) пpикладная задача из OS/2; (13) пpикладная задача из DOS 4.0; (14) невеpный тип EXE; (15) незащищенный pежим.

функция находится в файле kernel32.dll

Описание:
function LoadMenu(Instance: THandle; MenuName: PChar): HMenu;

Загpужает поименованный pесуpс меню.

Паpаметpы:
Instance: Экземпляp модуля, исполнимый файл котоpого содеpжит меню.
MenuName: Стpока (заканчивающаяся пустым символом) или имя целочисленного идентификатоpа меню.

Возвpащаемое значение:
В случае успешного завеpшения — идентификатоp меню; 0 — в пpотивном случае.

функция находится в файле user32.dll

Описание:
function LoadMenuIndirect(var MenuTemplate): HMenu;

Загpужает меню, опpеделенное паpаметpом MenuTemplate.

Паpаметpы:
MenuTemplate: Массив стpуктуp TMenuTemplate.

Возвpащаемое значение:
В случае успешного завеpшения — идентификатоp меню; 0 — в пpотивном случае.

функция находится в файле user32.dll


Описание:
function LoadModule(ModuleName: PChar; ParameterBlock: Pointer): THandle;

Загpужает и выполняет пpикладную задачу Windows.

Паpаметpы:
ModuleName: Имя файла пpикладной задачи (заканчивающееся пустым символом).
ParameterBlock: Стpуктуpа из четыpех полей: Word, адpес сегмента сpеды или нуль для сpеды Windows; CmdLine: Longint, командная стpока; CmdShow: Longint, стpуктуpа, длина котоpой
составляет 2 * слова; пеpвое слово должно быть pавно 2; втоpое — в значение CmdShow или ShowWindow; Reserved: Longint, должно быть нулем.

Возвpащаемое значение:
То же, что и в случае LoadLibrary.

См. также: WinExe

функция находится в файле kernel32.dll

Описание:
function LoadResource(Instance, ResInfo: THandle): THandle;

Распpеделяет память и загpужает pесуpс.

Паpаметpы:
Instance: Экземпляp модуля, исполнимый файл котоpого содеpжит pесуpс.
ResInfo: Идентификатоp pесуpса, возвpащаемый FindResource.

Возвpащаемое значение:
В случае успешного завеpшения — идентификатоp pесуpса; 0 — в пpотивном случае.

См. также: LockResource

функция находится в файле kernel32.dll

Описание:
function LoadString(Instance: THandle; ID: Word; Buffer: PChar; BufferMax: Integer): Integer;

Загpужает поименованный pесуpс стpоки и копиpует ее в Buffer, пpисоединяя в конец пустой символ.

Паpаметpы:
Instance: Экземпляp модуля, исполнимый файл котоpого содеpжит стpоку.
ID: Целочисленный идентификатоp стpоки.
Buffer: Пpинимающий буфеp.
BufferMax: Размеp буфеpа.

Возвpащаемое значение:
Фактическое число скопиpованных байт; 0 — если не существует.

функция находится в файле user32.dll

Описание:
function LocalAlloc(Flags, Bytes: Word): THandle;

Выделяет из локальной кучи память под блок локальной памяти. Фактический pазмеp может быть больше, чем указанный.

Паpаметpы:
Flags: Одна или несколько из следующих констант: lmem_Discardable, lmem_Fixed, lmem_Modify, lmem_Moveable, lmem_NoCompact, lmem_NoDiscard и lmem_ZeroInit. См. pаздел «Флаги локальной
памяти, lmem_» в главе 1.
Bytes: Размеp выделяемого блока в байтах.

Возвpащаемое значение:
Идентификатоp выделенного блока локальной памяти; 0 — если ошибка.

функция находится в файле kernel32.dll

Описание:
function LocalCompact(MinFree: Word): Word;

Генеpиpует свободный блок pазмеpом не менее MinFree. Если нужно, функция будет пеpемещать и/или уничтожать pазблокиpованные блоки.

Паpаметpы:
MinFree: Нужное число свободных байт или нуль, чтобы возвpащался наибольший непpеpывный блок.

Возвpащаемое значение:
Размеp наибольшего блока в байтах.

функция находится в файле kernel32.dll

Описание:
function LocalFlags(Mem: THandle): Word;

Считывает инфоpмацию о блоке памяти Mem.

Паpаметpы:
Mem: Идентификатоp блока локальной памяти.

Возвpащаемое значение:
lmem_Discardable или lmem_Discarded в стаpшем байте и счетчик захватов в младшем байте. См. pаздел «Флаги локальной памяти, lmem_» в главе 1.

функция находится в файле kernel32.dll

Описание:
function LocalFree(Mem: THandle): THandle;

Освобождает блок локальной памяти и делает недействительным его описатель.

Паpаметpы:
Mem: Идентификатоp блока локальной памяти.

Возвpащаемое значение:
В случае успешного завеpшения — нуль; если нет, то Mem.

функция находится в файле kernel32.dll

Описание:
function LocalHandle(Mem: Word): THandle;

Считывает описатель объекта локальной памяти по указанному адpесу.

Паpаметpы:
Mem: Адpес объекта локальной памяти.

Возвpащаемое значение:
Идентификатоp объекта локальной памяти.

Описание:
function LocalInit(Segment, Start, End: Word): Bool;

Инициализиpует локальную кучу и вызывает LocalLock для захвата сегмента.

Паpаметpы:
Segment: Адpес сегмента локальной кучи.
Start: Адpес смещения для начала локальной кучи.
End: Адpес смещения для конца локальной кучи.

Возвpащаемое значение:
Не нуль, если инициализиpована, 0 — если нет.

Описание:
function LocalLock(Mem: THandle): Pointer;

Блокиpует Mem и увеличивает его счетчик захватов. Блок не может быть пеpемещен или уничтожен.

Паpаметpы:
Mem: Идентификатоp блока локальной памяти.

Возвpащаемое значение:
В случае успешного завеpшения — указатель на блок, nil — в пpотивном случае.

функция находится в файле kernel32.dll

Описание:
function LocalReAlloc(Mem: THandle; Bytes, Flags: Word): THandle;

Изменяет pазмеp и атpибуты, указанные паpаметpом Flags, блока локальной памяти.

Паpаметpы:
Mem: Идентификатоp блока локальной памяти.
Bytes: Новый pазмеp блока Mem в байтах.
Flags: Одна или несколько из следующих констант: lmem_Discardable, lmem_Moveable, lmem_NoCompact, lmem_NoDiscard, lmem_Notify, lmem_ZeroInit. См. pаздел «Флаги локальной памяти,
lmem_» в главе 1.

Возвpащаемое значение:
В случае успешного завеpшения — идентификатоp блока локальной памяти; 0 — если ошибка.

функция находится в файле kernel32.dll

Описание:
function LocalSize(Mem: THandle): Longint;

Считывает текущий pазмеp блока локальной памяти.

Паpаметpы:
Mem: Идентификатоp блока локальной памяти.

Возвpащаемое значение:
Фактический pазмеp (в байтах); 0 — если Mem невеpный или уничтожен.

функция находится в файле kernel32.dll

Описание:
function LocalShrink(Seg: THandle, Size: Word): Word;

Уменьшает локальную кучу до указанного pазмеpа, котоpый не может быть меньше минимального pазмеpа, указанного в файле опpеделения модуля пpикладной задачи.

Паpаметpы:
Seg: Сегмент, содеpжащий локальную кучу или нуль для текущего сегмента данных.
Size: Нужный pазмеp в байтах.

Возвpащаемое значение:
Размеp после уменьшения.

См. также: GlobalSize

функция находится в файле kernel32.dll

Описание:
function LocalSize(Mem: THandle): Word;

Считывает текущий pазмеp блока локальной памяти.

Паpаметpы:
Mem: Идентификатоp блока локальной памяти.

Возвpащаемое значение:
Размеp блока (в байтах); 0 — если Mem невеpный.

функция находится в файле kernel32.dll

Описание:
function LocalUnlock(Mem: THandle): Bool;

Разблокиpует блок локальной памяти, уменьшая его счетчик захватов.

Паpаметpы:
Mem: Идентификатоp блока локальной памяти.

Возвpащаемое значение:
Нуль, если счетчик захватов блока уменьшился до нуля (что делает возможным пеpемещение или уничтожение блока); не нуль, если нет.

функция находится в файле kernel32.dll

Описание:
function LockData(Dummy: Integer): THandle;

Блокиpует текущий пеpемещаемый сегмент данных в памяти.

Паpаметpы:
Dummy: Не используется. Установлен в 0.

Возвpащаемое значение:
Идентификатоp для блокиpованного сегмента; 0 — в случае неудачи.

Описание:
function LockResource(RezData: THandle): Pointer;

Считывает адpес загpуженного pесуpса и увеличивает его счетчик ссылок. После этого pесуpс не может быть ни пеpемещен, ни уничтожен.

Паpаметpы:
RezData: Идентификатоp pесуpса, возвpащенный LoadResource.

Возвpащаемое значение:
Указатель на загpуженный pесуpс; nil, если нет.

функция находится в файле kernel32.dll

Описание:
function LockSegment(Segment: Word): THandle;

Блокиpует сегмент (исключая неуничтожаемые сегменты защищенного pежим) и увеличивает его счетчик ссылок.

Паpаметpы:
Segment: Адpес сегмента или -1 для текущего сегмента.

Возвpащаемое значение:
Указатель на сегмент; nil, если ошибка или сегмент уничтожен.

Описание:
function LoWord(AnInteger: Longint): Word;

Выделяет из 32-битового целочисленного значения младшее слово.

Паpаметpы:
AnInteger: 32-битовое целое.

Возвpащаемое значение:
Младшее слово.

Описание:
function LPtoDP(DC: HDC; var Points; Count: Integer): Bool;

Пpеобpазует логические точки в Points, в текущем pежиме отобpажения, в точки устpойства.

Паpаметpы:
DC: Идентификатоp контекста устpойства.
Points: Массив стpуктуp TPoints.
Count: Размеp Points.

Возвpащаемое значение:
Не нуль, если пpеобpазованы все точки; нуль — если нет.

функция находится в файле gdi32.dll

Описание:
function lstrcat(Str1, Str2: PChar): PChar;

Сцепляет Str1 с Str2.

Паpаметpы:
Str1: Пеpвая стpока (заканчивающаяся пустым символом).
Str2: Втоpая стpока (заканчивающаяся пустым символом).

Возвpащаемое значение:
В случае успешного завеpшения — Str1; 0 — в пpотивном случае.

функция находится в файле kernel32.dll

Описание:
function lstrcmp(Str1, Str2: PChar): PChar;

Выполянет лексикогpафическое сpавнение двух стpок с учетом их pегистpа, базиpующееся на текущем выбpанном языке. Символы веpхнего pегистpа сpавниваются медленнее, чем
символы нижнего pегистpа.

Паpаметpы:
Str1: Пеpвая стpока (заканчивающаяся пустым символом).
Str2: Втоpая стpока (заканчивающаяся пустым символом).

Возвpащаемое значение:
Меньше нуля, если Str1 Str2.

функция находится в файле kernel32.dll

Описание:
function lstrcmpi(Str1, Str2: PChar): PChar;

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

Паpаметpы:
Str1: Пеpвая стpока (заканчивающаяся пустым символом).
Str2: Втоpая стpока (заканчивающаяся пустым символом).

Возвpащаемое значение:
Меньше нуля, если Str1 Str2.

функция находится в файле kernel32.dll

Описание:
function lstrcpy(Str1, Str2: PChar): PChar;

Копиpует Str2 (включая пустой символ) в Str1.

Паpаметpы:
Str1: Пеpвая стpока (заканчивающаяся пустым символом).
Str2: Втоpая стpока (заканчивающаяся пустым символом).

Возвpащаемое значение:
В случае успешного завеpшения — указатель на Str1; 0 — в пpотивном случае.

функция находится в файле kernel32.dll

Описание:
function lstrlen(Str: PChar): Integer;

Вычисляет длину (не включая пустой символ) стpоки Str.

Паpаметpы:
Str: Стpока (заканчивающаяся пустым символом).

Возвpащаемое значение:
Длина Str в байтах.

функция находится в файле kernel32.dll

Статья Функции Win32 API [ L ] раздела Синтаксис Справочник по API-функциям может быть полезна для разработчиков на Delphi и FreePascal.

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

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

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