Взаимодействие
приложений С++Builder
с операционной системой
1.1 Обработка ошибок выполнения функций api Windows
При работе вашего приложения могут
возникать всевозможные ошибки, связанные
с неправильными исходными данными,
ошибками пользователя, ограничениями
системы и т.д. Имеется несколько механизмов
обработки подобных ошибок. Ошибки
выполнения операций и библиотечных
функций С++ обычно обрабатываются с
помощью механизма исключений. Работа
е исключениями подробно рассмотрена в
источниках, так что останавливаться на
этом достаточно хорошо известном вопросе
мы не будем. Не будем обсуждать и вопросы,
связанные с управляющим словом FPU,
которое определяет обработку ошибок
выполнения операций с плавающей запятой.
Эти вопросы также подробно рассмотрены
в источниках. В данной книге в основном
будут использоваться функцииAPIWindows. А в них принят
несколько иной подход к фиксации и
обработке ошибок.
Большинство функций APIWindowsвозвращают результат,
который позволяет понять, произошла ли
ошибка при выполнении функции. Это может
быть булево значениеFalse,
свидетельствующее об ошибке, значенияNULL.,0xFFFFFFFF,
-1 и др. При этом система вырабатывает
некоторый код ошибки в виде 32-разрядного
числа.
Выполнив вызов какой-то функции APIWindowsи поняв по возвращенному
результату, что произошла ошибка, код
этой ошибки можно определить, вызвав
функциюGetLastError. Она
объявлена в файлеwinbase.hследующим образом:
DWORD
GetLastError (VOID);
Функция возвращает код ошибки в виде
целого числа. Вызывать GetLastErrorнадо сразу после вызова функции, ошибку
выполнения которой требуется определить
Дело в том, что многие функции При своем
успешном выполнении задают код ошибки,
равный 0, и тем самым стирают код ошибки,
связанной с вызовом предыдущей функцииAPIWindows.
Вы можете и в своих собственных функциях
использовать аналогичный механизм
сообщений об ошибках. Это делается с
помощью функции SetLastError:
VOID
SetLastError (In DWORD dwERRCode);
Аргументом при вызове этой функции
задается устанавливаемый вами код
ошибки. Код должен содержать 1 в 29-ом
бите. Этот бит указывает, что код задан
приложением. Во всех системных кодах
29-ый бит равен 0. Так что установка его
в 1 гарантирует, что ваш код не перекроется
с каким-то системным кодом.
Список системных кодов ошибок дается
в файле WINNT.Hэтот список кодов мало о чем говорит. ВC++Builderимеется функцииSysErrorMessage,
которая позволяет получить текстовое
объяснение ошибки, причем на русском
языке. Функция объявлена в файлеSysUtils.hppследующим образом
AnsiString
__fastcall SysErrorMessage (int ErrorCode);
Единственный параметр этой функции —
код ошибки. А возвращаемое ею значение
— это текстовое пояснение.
Вызовы функций GetLastErrorиSetLastErrorудобно
объединить, введя в свою библиотеку,
например, следующую функцию:
AnsiString
ErrorCode (void)
{
Return SysErrorMessage (GetLastError);
}
Она возвращает строку, соответствующую
коду ошибки. Вызывать подобную функцию
надо только в случае, если по результатам
предшествующего вызова функции API
Windowsясно, что ошибка есть.
Тогда вызов вашей функции, сообщающей
об ошибке, может быть оформлен, например,
следующим образом:
ShowMessage
(“Ошибка: ”
+ ErrorCode());
Можете проверить работу этой функции
на простом примере. Перенесите на форму
окно редактирования Editи кнопку, в обработчике щелчка на которой
напишите код:
AnsiString
S
= Edit1->Text;
If
(! DeleteFile (S))
ShowMessage
(“Ошибка
при
удалении
файла
‘“ + S + “’:n” + ErrorCode ();
В этом коде вызывается функция APIWindowsDeleteFile,
которая удаляет файл, имя которого
передано в нее в качестве аргумента.
Функция возвращаетfalse,
если при ее выполнении произошла ошибка.
В этом случае в приведенном коде
вызывается функцияShowMessage,
в которую передается сообщение об
ошибке, возвращаемое нашей функциейErrorCode. Выполните
приложение и посмотрите на его реакцию
при различных текстах в окнеEdit1.
Вы получите сообщения «Не удается
найти указанный файл», «Синтаксическая
ошибка в имени файла, имени папки или
метке тома», «Отказано в доступе»
и др. Как видите, очень простым оператором
безо всяких проверок типаifилиswitchвы получили
очень неплохую и разнообразную диагностику
ошибок.
Иногда в приложений все-таки надо знать,
о каких ошибках сообщает тот или иной
код. Тогда в зависимости от значения
кода можно с помощью операторов ifиswitchпредусмотреть
те или иные действия. Приводить в данной
книге полный перечень ошибок очень
накладно: он займет более 70 страниц —
целую главу. Но вы легко можете получить
подобный перечень сами. Поместите на
форму компонентMemoи
кнопкуButton, в обработчик
щелчка которой запищите код:
AnsiString
S = Edit1->Text;
for
(DWORD err=0; err < 600000; err++)
{
Buff
= SysErrorMessage
(err);
if
(Buff
!= “”)
Memo1->Lines->Add
(AnsiString (err) + “ ” + Buff);
}
Вы получите в окнеMemo1список всех ошибок в формате: код ошибки,
пояснение. Можете сохранить его в
текстовом документе и использовать,
когда потребуется. Часть этого списка
приведена ниже:
0Операция успешно завершена
1 Неверная функция
2 Не удается найти указанный файл
3 Системе не удается найти указанный
путь
4 Системе не удается открыть файл
5 Отказано в доступе
6 Неверный дескриптор
7 Повреждены управляющие блоки памяти
8 Недостаточно памяти для обработки
команды
Помимо функции
SystemErrorMessage,
введенной в
С++Builder, имеется
более мощная
функция FormatMessage,
объявленная в
API Windows:
DWODR FormatMessage (DWORD ddwFlags,
LPCVOID lpSource,
DWORD dwMessageId, DWORD
dwLanguageId,
LPTSYR lpBuffer, DWORD nSize,
Va_list
*Arguments);
Параметр dwFlagsявляетсямножеством флагов, определяющих
параметры процесса форматирования.
ПараметрdwMessageIdопределяет идентификатор требуемого
сообщения, параметрdwLanguageIdзадает идентификатор языка выдаваемого
сообщения. ПараметрыlpBuffer
иnSizeопределяют
буфер, в который будет помещено .сообщение,
и его размер. ПараметрArgumentsявляется указателем на массив значений,
которые используются для вставки в
отформатированное сообщение. Честно
говоря, я так и не смог придумать разумный
пример, когда имело бы смысл использовать
функциюFormatMessageвместо
рассмотренной выше функцииSysErrorMessage.
Так что не будем останавливаться на
описании многочисленных параметров
этой функции. Вы можете «посмотреть
их во встроенной справке С++Builder.
Ограничусь примером задавив этих
параметров, который чаще всего используется
на практике:
LPTSTR
MsgBuf;
FormatMessage
(FORMAT_MESSAGE_ALLCOTE_BUFFER |
FORMAT_MESSAGE_FORM_SYSTEM, NULL,
GetLastError (),
0, (LPTSTR) & MsgBuf, 0, NULL);
Label1->Caption
= MsgBuf;
В первом аргументе задан флаг
FORMAT_MESSAGE_ALLCOTE_BUFFER,
указывающий, что сообщение должно
заноситься в буфер, и флагFORMAT_MESSAGE_FORM_SYSTEM,
определяющий, что берутся системные
сообщения. Функция может брать и другие
виды сообщений из модулей, ресурсов и
т.п., но на этой экзотике мы не будем
останавливаться. В качестве индекса
сообщенияdwMessageIdзадается значение, возвращающееся
функциейGetLastError.
ПеременнаяMsgBufуказывает на буфер, в который заносится
сообщение. Остальные аргументы в вызове
функции заданы нулевыми, что обеспечивает
стандартный вид сообщения на языке,
установленном по умолчанию. Как видите,
даже упрощенный вариант использованияFormatMessageнамного более
громоздкий, чем вызов функцииSysErrorMessage, при тех же
результатах.
Соседние файлы в папке API
- #
- #
- #
замечания
Каждый поток будет иметь свой последний код ошибки. Windows API установит последний код ошибки в вызывающем потоке.
Вы всегда должны вызывать GetLastError сразу после проверки возвращаемого значения функции API Windows.
Большинство функций Windows API устанавливают последний код ошибки, когда они терпят неудачу. Некоторые также установят последний код ошибки, когда они преуспеют. Существует ряд функций, которые не устанавливают последний код ошибки. Всегда обращайтесь к документации по функциям Windows API.
Невозможно использовать FORMAT_MESSAGE_FROM_SYSTEM без FORMAT_MESSAGE_IGNORE_INSERTS при использовании функции FormatMessage для получения описания кода ошибки.
Вступление
API Windows предоставляется с помощью C-вызываемого интерфейса. Успех или сбой вызова API сообщаются строго через возвращаемые значения. Исключения не являются частью документированного контракта (хотя некоторые реализации API могут вызывать исключения SEH , например, при передаче аргумента lpCommandLine только для чтения в CreateProcess ).
Сообщение об ошибке грубо относится к одной из четырех категорий:
- Только возвращаемое значение
- Возвращаемое значение с дополнительной информацией о сбое
- Возвращаемое значение с дополнительной информацией об отказе и успехе
- Возвращаемое значение
HRESULT
Документация для каждого вызова API явно вызывается, как сообщаются ошибки. Всегда обращайтесь к документации.
Ошибка, сообщенная только возвратным значением
Некоторые вызовы API возвращают единый флаг отказа / успеха без какой-либо дополнительной информации (например, GetObject ):
if ( GetObjectW( obj, 0, NULL ) == 0 ) {
// Failure: no additional information available.
}
Сообщается об ошибке с сообщением об ошибке
В дополнение к возвращаемому значению отказа / успеха некоторые вызовы API также устанавливают последнюю ошибку при сбое (например, CreateWindow ). Документация обычно содержит следующую стандартную формулировку для этого случая:
Если функция завершается успешно, возвращаемое значение <значение успеха API> .
Если функция не работает, возвращаемое значение <значение ошибки API> . Чтобы получить расширенную информацию об ошибке, вызовите GetLastError .
if ( CreateWindowW( ... ) == NULL ) {
// Failure: get additional information.
DWORD dwError = GetLastError();
} else {
// Success: must not call GetLastError.
}
Очень важно, что вы вызываете GetLastError() НЕМЕДЛЕННО. Последний код ошибки может быть перезаписан любой другой функцией, поэтому, если есть дополнительная функция вызова между неудавшейся функцией и вызовом GetLastError() , возврат из GetLastError() больше не будет надежным. Будьте особенно осторожны при работе с конструкторами C ++.
Как только вы получите код ошибки, вам нужно будет его интерпретировать. Вы можете получить полный список кодов ошибок в MSDN на странице Системные коды ошибок (Windows) . Кроме того, вы можете посмотреть в своих файлах заголовков системы; файл со всеми константами кода ошибки — winerror.h . (Если у вас есть официальный SDK от Microsoft для Windows 8 или новее, это находится в shared папке с папкой include.)
Заметки о вызове GetLastError() на других языках программирования
.net (C #, VB и т. д.)
С .net вы не должны P / Invoke в GetLastError() напрямую. Это связано с тем, что среда выполнения .net сделает другие вызовы Windows API одним и тем же потоком за вашей спиной. Например, сборщик мусора может вызвать VirtualFree() если он найдет достаточно памяти, которую он больше не использует, и это может произойти между вашим назначенным вызовом функции и вашим вызовом GetLastError() .
Вместо этого .net предоставляет Marshal.GetLastWin32Error() , которая будет извлекать последнюю ошибку из последнего вызова P / Invoke, который вы сами сделали. Используйте это вместо прямого вызова GetLastError() .
(.net, похоже, не мешает вам импортировать GetLastError() любом случае, я не уверен, почему.)
Идти
Различные средства, предоставляемые Go для вызова DLL-функций (которые находятся как в syscall пакета, syscall и в пакете golang.org/x/sys/windows ), возвращают три значения: r1 , r2 и err . r2 никогда не используется; вы можете использовать пустой идентификатор. r1 — возвращаемое значение функции. err является результатом вызова GetLastError() но преобразуется в тип, реализующий error , поэтому вы можете передать его вызывающим функциям для обработки.
Поскольку Go не знает, когда вызывать GetLastError() а когда нет, он всегда будет возвращать ошибку nil . Поэтому типичная идиома обработки ошибок Go
r1, _, err := syscall.Syscall12(CreateWindowW.Addr(), ...)
if err != nil {
// handle err
}
// use r1
не будет работать. Вместо этого вы должны проверить r1 точно так же, как и на C, и использовать только err если это указывает, что функция возвратила ошибку:
r1, _, err := syscall.Syscall12(CreateWindowW.Addr(), ...)
if r1 == 0 {
// handle err
}
// use r1
Сообщается об ошибке с дополнительной информацией о сбоях и успехах
Некоторые вызовы API могут преуспеть или сбой более чем одним способом. API обычно возвращают дополнительную информацию как для успешных вызовов, так и для ошибок (например, CreateMutex ).
if ( CreateMutexW( NULL, TRUE, L"Global\MyNamedMutex" ) == NULL ) {
// Failure: get additional information.
DWORD dwError = GetLastError();
} else {
// Success: Determine which mutex was returned.
if ( GetLastError() == ERROR_ALREADY_EXISTS ) {
// Existing mutex object returned.
} else {
// Newly created mutex object returned.
}
}
Ошибка, сообщенная как значение HRESULT
HRESULT s — числовые 32-битные значения, где биты или диапазоны бит кодируют четко определенную информацию. MSB — это флаг отказа / успеха, а остальные бит хранят дополнительную информацию. Отказ или успех можно определить с помощью макросов FAILED или SUCCEEDED . HRESULT s обычно используются совместно с COM, но также отображаются в реализациях, отличных от COM (например, StringCchPrintf ).
const size_t cchBuf = 5;
wchar_t buffer[cchBuf] = { 0 };
HRESULT hr = StringCchPrintfW( buffer, cchBuf, L"%s", L"Hello, world!" );
if ( FAILED( hr ) ) {
// Failure: Determine specific reason.
switch ( hr ) {
case STRSAFE_E_INSUFFICIENT_BUFFER:
// Buffer too small; increase buffer and retry.
...
case STRSAFE_E_INVALID_PARAMETER:
// Invalid parameter; implement custom error handling (e.g. logging).
...
default:
// Some other error code; implement custom error handling (e.g. logging).
...
}
}
Преобразование кода ошибки в строку сообщения
GetLastError возвращает числовой код ошибки. Чтобы получить описательное сообщение об ошибке ( например , для отображения пользователю), вы можете вызвать FormatMessage :
// This functions fills a caller-defined character buffer (pBuffer)
// of max length (cchBufferLength) with the human-readable error message
// for a Win32 error code (dwErrorCode).
//
// Returns TRUE if successful, or FALSE otherwise.
// If successful, pBuffer is guaranteed to be NUL-terminated.
// On failure, the contents of pBuffer are undefined.
BOOL GetErrorMessage(DWORD dwErrorCode, LPTSTR pBuffer, DWORD cchBufferLength)
{
if (cchBufferLength == 0)
{
return FALSE;
}
DWORD cchMsg = FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
NULL, /* (not used with FORMAT_MESSAGE_FROM_SYSTEM) */
dwErrorCode,
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
pBuffer,
cchBufferLength,
NULL);
return (cchMsg > 0);
}
В C ++ вы можете значительно упростить интерфейс, используя класс std::string :
#include <Windows.h>
#include <exception>
#include <stdexcept>
#include <memory>
#include <string>
typedef std::basic_string<TCHAR> String;
String GetErrorMessage(DWORD dwErrorCode)
{
LPTSTR psz = NULL;
const DWORD cchMsg = FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM
| FORMAT_MESSAGE_IGNORE_INSERTS
| FORMAT_MESSAGE_ALLOCATE_BUFFER,
NULL, // (not used with FORMAT_MESSAGE_FROM_SYSTEM)
dwErrorCode,
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
reinterpret_cast<LPTSTR>(&psz),
0,
NULL);
if (cchMsg > 0)
{
// Assign buffer to smart pointer with custom deleter so that memory gets released
// in case String's c'tor throws an exception.
auto deleter = [](void* p) { ::HeapFree(::GetProcessHeap(), 0, p); };
std::unique_ptr<TCHAR, decltype(deleter)> ptrBuffer(psz, deleter);
return String(ptrBuffer.get(), cchMsg);
}
else
{
throw std::runtime_error("Failed to retrieve error message string.");
}
}
ПРИМЕЧАНИЕ. Эти функции также работают для значений HRESULT . Просто измените первый параметр из DWORD dwErrorCode на HRESULT hResult . Остальная часть кода может оставаться неизменной.
Опубликовано: 10 августа 2017
Исправлено: 10 августа 2017
Версия документа: 1.0
Когда ты вызываешь функцию Windows, она проверяет переданные ей параметры, а затем пытается выполнить свою работу. Если ты передал недопустимый параметр или если данную операцию нельзя выполнить по какой‐то другой причине, она возвращает значение, свидетельствующее об ошибке.
Типы данных для возвращаемых значений
Большинство функций Windows возвращают следующие типы:
- Процедуры:
- Подпрограммы‐процедуры Sub не возвращают значений. Такие функции всегда (или почти всегда) выполняется успешно, хотя их количество в Windows очень мало. Пример: функция ExitProcess.
- BOOL или Boolean:
- Если вызов функции оканчивается неудачей, то возвращается ложь False (она же 0), в остальных случаях возвращается любое другое число, отличное от нуля. Однако не пытайся сравнить это число с True, лучше просто сравнивать с нулём.
- HANDLE:
- Если вызов функции оканчивается неудачей, то обычно возвращается NULL, что эквивалентно нулю, в остальных случаях возвращаемое значение идентифицирует объект, которым ты можешь манипулировать. Однако некоторые функции вместо NULL в случае ошибки возвращают константу INVALID_HANDLE_VALUE, например, функция CreateFile. В документации для каждой функции чётко указано, что именно она возвращает при ошибке: NULL или INVALID_HANDLE_VALUE.
- PVOID или Any Ptr:
- Если вызов функции оканчивается неудачей, то возвращается NULL, в остальных случаях PVOID сообщает адрес блока данных в памяти.
- HRESULT:
- Если вызов функции оканчивается неудачей, то возвращается ошибочный код HRESULT, в остальных случаях значение говорит об успехе операции. Подробнее о HRESULT →
- Integer, Long или DWORD:
- Это значение — «крепкий орешек». Функции, которые возвращают значения каких‐либо счётчиков, обычно возвращают Integer, Long или DWORD. Если по какой‐либо причине функция не сумела сосчитать то, что ты хотел, она обычно возвращает 0 или -1, всё зависит от конкретной функции. Лучше всего проверь в документации, каким именно значением функция уведомляет об ошибке.
Почему же произошла ошибка?
При возникновении ошибки необходимо разобраться почему вызов данной функции оказался неудачен. За каждой ошибкой закреплён свой код — 32‐битное целое число.
Функция Windows, обнаружив ошибку, через механизм локальной памяти потока сопоставляет соответствующий код ошибки с вызывающим потоком. Это позволяет потокам работать независимо друг от друга, не вмешиваясь в чужие ошибки. Когда функция вернёт управление, её возвращаемое значение будет указывать на то, что произошла какая‐то ошибка. Какая именно — можно узнать, вызвав функцию GetLastError.
Declare Function GetLastError()As DWORD
Она просто возвращает числовое значение, характеризующее код ошибки.
Список кодов ошибок лежит в заголовочной файле winwinerror.bi. Здесь приведена его небольшая часть, чтобы примерно представлять, на что он похож:
Const ERROR_SUCCESS = 0
Const NO_ERROR = 0
Const ERROR_INVALID_FUNCTION = 1
Const ERROR_FILE_NOT_FOUND = 2
Const ERROR_PATH_NOT_FOUND = 3
Const ERROR_TOO_MANY_OPEN_FILES = 4
Const ERROR_ACCESS_DENIED = 5
Const ERROR_INVALID_HANDLE = 6
Функцию GetLastError необходимо вызывать сразу же после неудачного вызова функции Windows, иначе код ошибки может быть потерян.
Некоторые функции Windows всегда завершаются успешно, но по разным причинам. Например, попытка создать объект ядра «событие» с определённым именем может быть успешна потому, что оно действительно создано, либо потому, что такой объект уже существует. Но иногда нужно знать причину успеха. Для возврата этой информации корпорация Microsoft предпочла использовать механизм установки кода последней ошибки. Так что и при успешном выполнении некоторых функций ты можешь использовать GetLastError и получать дополнительную информацию. К таким функциям относится, например, CreateEvent.
Ты наверняка спросишь, составит ли корпорация Microsoft полный список всех кодов ошибок, возможных в каждой функции. Ответ: нет. Такого списка никогда не будет, уж слишком сложно его составлять и поддерживать для всё новых и новых версий системы.
Проблема с подобным списком ещё и в том, что ты вызываешь одну функцию, а она может обратиться к другой, та — к следующей и так далее. Любая из этих функций может завершиться неудачно и по самым разным причинам. Иногда функция более высокого уровня сама справляется с ошибкой в одной из вызванных ею функций, и в конечном счёте выполняет то, что от неё хотели. В общем, для создания такого списка пришлось бы проследить цепочки вызовов в каждой функции, что очень трудно. А с появлением новой версии операционной системы цепочки вызовов пришлось бы пересматривать заново.
Определение собственных кодов ошибок
Механизм установки кода ошибки можно использовать и в собственных функциях. Предположим, ты пишешь библиотечную функцию, к которой будут обращаться другие части программы или вообще другие программы. Вызов этой функции по каким‐либо причинам может оказаться неудачным и тебе придётся тоже сообщать об этом. С этой целью ты просто устанавливаешь код последней ошибки в потоке и возвращаешь значение False, INVALID_HANDLE_VALUE, NULL или что‐то другое, более подходящее в твоём случае.
SetLastError
Чтобы установить код последней ошибки вызывай функцию SetLastError и передай ей нужной число.
Declare Sub SetLastError( _
ByVal dwErrorCode As DWORD _
)
Можно использовать коды ошибок, уже определённые в winerror.bi, если они подходят. Если ты считаешь, что ни один из кодов ошибок из winerror.bi не годится для ошибки, возможной в твоей функции, можно определить свой код.
Формат кода ошибки
Код ошибки представляет 32‐битное беззнаковое число, которое разбито на поля:
| Биты числа | 31 30 | 29 | 28 | 27 26 25 24 23 22 21 20 19 18 17 16 | 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 |
|---|---|---|---|---|---|
| Описание | Степень тяжести | Кем определён | Зарезервировано | Подсистема (facility code) | Код ошибки |
- Биты 30 и 31:
- Представляют собой степень тяжести ошибки. В двоичном виде: 00 — успех, 01 — информация, 10 — предупреждение, 11 — ошибка.
- Бит 29:
- Корпорация Microsoft обещала, что никогда не будет его устанавливать. Следовательно, если ты определяешь собственный код ошибки, то установи этот бит в 1 для гарантии, что твой код ошибки не будет конфликтовать с кодами, определёнными Microsoft.
- Бит 28:
- Зарезервирован. Должен быть 0.
- Биты с 16 по 27:
- Код подсистемы (facility code). Определяется корпорацией Microsoft. Указывает на компонент операционной системы, вызвавший ошибку.
- Биты с 0 по 15:
- Код ошибки. Определяется корпорацией Microsoft или пользователем.
Подробнее об этих полях будет рассказано в следующих статьях. На данный момент единственное важное для тебя поле — это бит 29. Чтобы гарантировать непересекаемость кодов ошибок от Microsoft, установи его в 1. В переводе на числа это означает, что твой код ошибки должен быть больше, чем &h20000000 или 536870912 в десятичном виде.
Получение описания ошибки
Для получения текстового описания ошибки подойдёт функция FormatMessage. Использовать её можно так:
#include "windows.bi"
Const BufferSize As Integer = 4096 - 1
' Строка с ошибкой
Dim ErrorMessage As WString * (BufferSize + 1) = Any
' Вызов функции с неправильным параметром
GetProcessId(NULL)
' Получить код ошибки
Dim dwError As DWORD = GetLastError()
' Получить строку по коду ошибки
FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM + FORMAT_MESSAGE_IGNORE_INSERTS, NULL, dwError, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), @ErrorMessage, BufferSize, NULL)
' Выводим описание ошибки на консоль
Print ErrorMessage
Each thread will have its own last error code. The Windows API will set the last error code on the calling thread.
You should always call the GetLastError function immediately after checking a Windows API function’s return value.
The majority of Windows API functions set the last error code when they fail. Some will also set the last error code when they succeed. There are a number of functions that do not set the last error code. Always refer to the Windows API function’s documentation.
It is unsafe to use FORMAT_MESSAGE_FROM_SYSTEM without FORMAT_MESSAGE_IGNORE_INSERTS when using the FormatMessage function to get a description of an error code.
Introduction
The Windows API is provided by means of a C-callable interface. Success or failure of an API call is reported strictly through return values. Exceptions aren’t part of the documented contract (although some API implementations can raise SEH exceptions, e.g. when passing a read-only lpCommandLine argument to CreateProcess).
Error reporting roughly falls into one of four categories:
- Return value only
- Return value with additional information on failure
- Return value with additional information on failure and success
HRESULTreturn value
The documentation for each API call explicitly calls out, how errors are reported. Always consult the documentation.
Error reported by return value only
Some API calls return a single failure/success flag, without any additional information (e.g. GetObject):
if ( GetObjectW( obj, 0, NULL ) == 0 ) {
// Failure: no additional information available.
}
Error reported with additional information on failure
In addition to a failure/success return value, some API calls also set the last error on failure (e.g. CreateWindow). The documentation usually contains the following standard wording for this case:
If the function succeeds, the return value is <API-specific success value>.
If the function fails, the return value is <API-specific error value>. To get extended error information, call GetLastError.
if ( CreateWindowW( ... ) == NULL ) {
// Failure: get additional information.
DWORD dwError = GetLastError();
} else {
// Success: must not call GetLastError.
}
It is vital that you call GetLastError() IMMEDIATELY. The last error code can be overwritten by any other function, so if there’s an extra function call between the function that failed and the call to GetLastError(), the return from GetLastError() will no longer be reliable. Take extra caution when dealing with C++ constructors.
Once you get an error code, you will need to interpret it. You can get a comprehensive list of error codes on MSDN, at the System Error Codes (Windows) page. Alternatively, you can look in your system header files; the file with all the error code constants is winerror.h. (If you have Microsoft’s official SDK for Windows 8 or newer, this is in the shared subfolder of the include folder.)
Notes on calling GetLastError() in other programming languages
.net languages (C#, VB, etc.)
With .net, you should not P/Invoke to GetLastError() directly. This is because the .net runtime will make other Windows API calls on the same thread behind your back. For instance, the garbage collector might call VirtualFree() if it finds enough memory that it is no longer using, and this can happen between your intended function call and your call to GetLastError().
Instead, .net provides the Marshal.GetLastWin32Error() function, which will retrieve the last error from the last P/Invoke call that you yourself made. Use this instead of calling GetLastError() directly.
(.net does not seem to stop you from importing GetLastError() anyway; I’m not sure why.)
Go
The various facilities provided by Go for calling DLL functions (which reside in both package syscall and package golang.org/x/sys/windows) return three values: r1, r2, and err. r2 is never used; you can use the blank identifier there. r1 is the function’s return value. err is the result of calling GetLastError() but converted into a type that implements error, so you can pass it up to calling functions to handle.
Because Go does not know when to call GetLastError() and when not to, it will always return a non-nil error. Therefore, the typical Go error-handling idiom
r1, _, err := syscall.Syscall12(CreateWindowW.Addr(), ...)
if err != nil {
// handle err
}
// use r1
will not work. Instead, you must check r1, exactly as you would in C, and only use err if that indicates the function returned an error:
r1, _, err := syscall.Syscall12(CreateWindowW.Addr(), ...)
if r1 == 0 {
// handle err
}
// use r1
Error reported with additional information on failure and success
Some API calls can succeed or fail in more than one way. The APIs commonly return additional information for both successful invocations as well as errors (e.g. CreateMutex).
if ( CreateMutexW( NULL, TRUE, L"Global\MyNamedMutex" ) == NULL ) {
// Failure: get additional information.
DWORD dwError = GetLastError();
} else {
// Success: Determine which mutex was returned.
if ( GetLastError() == ERROR_ALREADY_EXISTS ) {
// Existing mutex object returned.
} else {
// Newly created mutex object returned.
}
}
Error reported as HRESULT value
HRESULTs are numeric 32-bit values, where bits or bit ranges encode well-defined information. The MSB is a failure/success flag, with the remaining bits storing additional information. Failure or success can be determined using the FAILED or SUCCEEDED macros. HRESULTs are commonly used with COM, but appear in non-COM implementations as well (e.g. StringCchPrintf).
const size_t cchBuf = 5;
wchar_t buffer[cchBuf] = { 0 };
HRESULT hr = StringCchPrintfW( buffer, cchBuf, L"%s", L"Hello, world!" );
if ( FAILED( hr ) ) {
// Failure: Determine specific reason.
switch ( hr ) {
case STRSAFE_E_INSUFFICIENT_BUFFER:
// Buffer too small; increase buffer and retry.
...
case STRSAFE_E_INVALID_PARAMETER:
// Invalid parameter; implement custom error handling (e.g. logging).
...
default:
// Some other error code; implement custom error handling (e.g. logging).
...
}
}
Converting an error code into a message string
GetLastError returns a numerical error code. To obtain a descriptive error message (e.g., to display to a user), you can call FormatMessage:
// This functions fills a caller-defined character buffer (pBuffer)
// of max length (cchBufferLength) with the human-readable error message
// for a Win32 error code (dwErrorCode).
//
// Returns TRUE if successful, or FALSE otherwise.
// If successful, pBuffer is guaranteed to be NUL-terminated.
// On failure, the contents of pBuffer are undefined.
BOOL GetErrorMessage(DWORD dwErrorCode, LPTSTR pBuffer, DWORD cchBufferLength)
{
if (cchBufferLength == 0)
{
return FALSE;
}
DWORD cchMsg = FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
NULL, /* (not used with FORMAT_MESSAGE_FROM_SYSTEM) */
dwErrorCode,
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
pBuffer,
cchBufferLength,
NULL);
return (cchMsg > 0);
}
In C++, you can simplify the interface considerably by using the std::string class:
#include <Windows.h>
#include <exception>
#include <stdexcept>
#include <memory>
#include <string>
typedef std::basic_string<TCHAR> String;
String GetErrorMessage(DWORD dwErrorCode)
{
LPTSTR psz = NULL;
const DWORD cchMsg = FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM
| FORMAT_MESSAGE_IGNORE_INSERTS
| FORMAT_MESSAGE_ALLOCATE_BUFFER,
NULL, // (not used with FORMAT_MESSAGE_FROM_SYSTEM)
dwErrorCode,
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
reinterpret_cast<LPTSTR>(&psz),
0,
NULL);
if (cchMsg > 0)
{
// Assign buffer to smart pointer with custom deleter so that memory gets released
// in case String's c'tor throws an exception.
auto deleter = [](void* p) { ::HeapFree(::GetProcessHeap(), 0, p); };
std::unique_ptr<TCHAR, decltype(deleter)> ptrBuffer(psz, deleter);
return String(ptrBuffer.get(), cchMsg);
}
else
{
throw std::runtime_error("Failed to retrieve error message string.");
}
}
NOTE: These functions also work for HRESULT values. Just change the first parameter from DWORD dwErrorCode to HRESULT hResult. The rest of the code can remain unchanged.
Given in this article is a class that handles Windows API error with CWinException.
- Download demo project — 34.3 KB
- Download source — 3 KB
Introduction
When using the Windows API, I think that it is better to get a well-formatted error description rather than its unmeaningful code. I also thought it may be useful that when you have some class based mostly on Win32 API functions to have it throw exceptions that can be handled in the caller block if needed. CWinException class solves those problems — it is derived from MFC’s CException class and can be thrown by any API function that sets the ‘last error’ if it fails. Also, if a function does not set the last error, then you can set the last error to an appropriate value from the source block and catch the exception in the caller block.
Description
The CWinException class makes it possible to get the last error, set the value of the last error, or clear it (set it to ERROR_SUCCESS). It can also get a formatted error description and report it.
Usage
void CService::Start_1(void) throw() { if (!::StartService(m_schService, NULL, NULL)) { throw new CWinException(); } } BOOL CService::Start_2(void) { return ::StartService(m_schService, NULL, NULL); } include "WinException.h" include "Service.h" public: CService m_cService; void CMainFrame::OnStartService_1(void) { try { m_cService.Start_1(); } catch(CWinException* e) { e->ReportError(); e->Delete(); } } void CMainFrame::OnStartService_2(void) { if(m_cService.Start_1() == FALSE) { CWinException e; e.ReportError(); } }
Base Class
CException
Class Members
CWinException()— default constructor, gets last error on initializeCWinException(DWORD dwErrCode)— sets last error code todwErrCodeon initialize
operator LPCTSTR() const— gives direct access to error description stored inCStringtypeprivatevariableoperator DWORD() const— gives direct access to error code stored inDWORDtypeprivatevariable
BOOL SetLastError(DWORD dwErrorCode)— sets last error todwErrorCode, returnsTRUEif successfulDWORD GetLastError(void)— regets last error code and returns it
BOOL ToString(DWORD dwErrorCode, CString& szError)— writesdwErrCodeerror description toszError, returnsTRUEif successfulBOOL GetLastErrorString(CString& szLastError)— writes last error description toszError, returnsTRUEif successful.void ClearLastError(void)— sets last error to 0 (ERROR_SUCCESS), clears it.
virtual BOOL GetErrorMessage( LPTSTR lpszError, UINT nMaxError, PUINT pnHelpContext = NULL )— provides text about an error that has occurred. Returnstrueif successful.lpszError— a pointer to a buffer that will receive an error messagenMaxError— the maximum number of characters the buffer can hold, including theNULLterminator. IfNULL, all characters will be written.pnHelpContext— alwaysNULL
virtual int ReportError( UINT nType = MB_OK, UINT nMessageID = 0)— reports error text in a message box to the user. Returns anAfxMessageBoxvalue.nType— any combination of the message-box styles.nMessageID— Specifies the resource ID (stringtable entry) of a message to add to the standard error messagestring(Run-time error : %d) if notNULL.
History
- 26th December, 2001: Initial version
This member has not yet provided a Biography. Assume it’s interesting and varied, and probably something to do with programming.
Given in this article is a class that handles Windows API error with CWinException.
- Download demo project — 34.3 KB
- Download source — 3 KB
Introduction
When using the Windows API, I think that it is better to get a well-formatted error description rather than its unmeaningful code. I also thought it may be useful that when you have some class based mostly on Win32 API functions to have it throw exceptions that can be handled in the caller block if needed. CWinException class solves those problems — it is derived from MFC’s CException class and can be thrown by any API function that sets the ‘last error’ if it fails. Also, if a function does not set the last error, then you can set the last error to an appropriate value from the source block and catch the exception in the caller block.
Description
The CWinException class makes it possible to get the last error, set the value of the last error, or clear it (set it to ERROR_SUCCESS). It can also get a formatted error description and report it.
Usage
void CService::Start_1(void) throw() { if (!::StartService(m_schService, NULL, NULL)) { throw new CWinException(); } } BOOL CService::Start_2(void) { return ::StartService(m_schService, NULL, NULL); } include "WinException.h" include "Service.h" public: CService m_cService; void CMainFrame::OnStartService_1(void) { try { m_cService.Start_1(); } catch(CWinException* e) { e->ReportError(); e->Delete(); } } void CMainFrame::OnStartService_2(void) { if(m_cService.Start_1() == FALSE) { CWinException e; e.ReportError(); } }
Base Class
CException
Class Members
CWinException()— default constructor, gets last error on initializeCWinException(DWORD dwErrCode)— sets last error code todwErrCodeon initialize
operator LPCTSTR() const— gives direct access to error description stored inCStringtypeprivatevariableoperator DWORD() const— gives direct access to error code stored inDWORDtypeprivatevariable
BOOL SetLastError(DWORD dwErrorCode)— sets last error todwErrorCode, returnsTRUEif successfulDWORD GetLastError(void)— regets last error code and returns it
BOOL ToString(DWORD dwErrorCode, CString& szError)— writesdwErrCodeerror description toszError, returnsTRUEif successfulBOOL GetLastErrorString(CString& szLastError)— writes last error description toszError, returnsTRUEif successful.void ClearLastError(void)— sets last error to 0 (ERROR_SUCCESS), clears it.
virtual BOOL GetErrorMessage( LPTSTR lpszError, UINT nMaxError, PUINT pnHelpContext = NULL )— provides text about an error that has occurred. Returnstrueif successful.lpszError— a pointer to a buffer that will receive an error messagenMaxError— the maximum number of characters the buffer can hold, including theNULLterminator. IfNULL, all characters will be written.pnHelpContext— alwaysNULL
virtual int ReportError( UINT nType = MB_OK, UINT nMessageID = 0)— reports error text in a message box to the user. Returns anAfxMessageBoxvalue.nType— any combination of the message-box styles.nMessageID— Specifies the resource ID (stringtable entry) of a message to add to the standard error messagestring(Run-time error : %d) if notNULL.
History
- 26th December, 2001: Initial version
This member has not yet provided a Biography. Assume it’s interesting and varied, and probably something to do with programming.
Это перевод The Way WinAPI Shows Errors. Автор: Christian Wimmer.
Я увидел интересный ответ в обсуждении на форуме, в котором я участвовал. Вопрос был не на тему этого поста, но ответ с кодом был достаточно интересным, чтобы написать эту статью. Код был написан на Delphi, но, очевидно, писался в стиле простого C (даже с использованием goto!).
Однако сейчас я хочу поговорить о том, как WinAPI обычно возвращает ошибки, и почему код в ответе — не самый удачный способ.
Тот код выглядел не в точности так. Я просто выделил из него существенные части для вашего удобства (исходный код напоминал C ещё больше, чем этот пример):
function IsUserMemberOf(const Group : PSID) : BOOL;
var
hToken : HANDLE;
begin
result := false;
// получаем Token и сохраняем в hToken ... (короткий вариант)
if not OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY or TOKEN_DUPLICATE, hToken) then
goto Exit;
// дополнительные действия для Vista и много-много gotos
if not CheckTokenMembership(hToken, Group, @result) then
begin
goto Exit_1;
end;
:Exit_1
CloseHandle(hToken);
goto Exit;
:Exit
end;
В чём проблема с возвращаемым значением этой функции? Во-первых, возвращаемое значение каждой WinAPI функции проверяется и функция возвращает управление, если вызываемая WinAPI функция возвращает ошибку. Это — очень хорошо, потому что я видел кучу кода, который ничем таким не озаботились. Но здесь есть другая проблема. Существует два возможных набора результатов, которые мы бы хотели сообщить нашему вызывающему:
- Является ли пользователь членом группы. Это, очевидно, двоичное значение (скажем,
True/False). - Произошла ли ошибка. Аналогично, это двоичное значение (
True/False).
Диапазон возвращаемых значений вашей функции варьируется от False(0) до True(1). Функция проецирует два набора с разным смыслом в один набор — результат функции. Если один из вызовов WinAPI завершиться неудачей, то функция выйдет и вернёт False. Очевидно, что значение False используется для двух совершенно разных по смыслу результатов. Одно из которых говорит вызывающему, что пользователь не входит в группу, а другое — что функция почему-то завершилась с ошибкой. А мы соединили эти два смысла в одно значение. Т.е. мы потеряли информацию! Теперь мы не можем отличить один случай от другого без дополнительной (мета) информации. Всегда думайте об этом, когда вы проектируете заголовок функции. Но, конечно же, эта информация есть у нас в другом месте. В самом WinAPI. GetLastError даст вам значение ошибки, которое (я надеюсь) позволит нам решить нашу проблему. Так что всё нормально? Хотелось бы верить! Посмотрите на это:
function GetMyUserName : String;
var
nSize : Cardinal;
p : PChar;
begin
nSize := 0;
if not GetUserName(nil, nSize) and (GetLastError() = ERROR_INSUFFICIENT_BUFFER) then
begin
GetMem(p, nSize * sizeof(CHAR));
try
if not GetUserName(p, nSize) then
RaiseLastOSError;
result := p;
finally
FreeMem(p);
end;
end
else
RaiseLastOSError;
end;
begin
...
xy := GetMyUserName();
bIsMember := IsUserMemberOf(aGroup);
if not bIsMember and (GetLastError() <> 0) then
ОШИБКА //...обработка;
end;
Функция IsUserMemberOf вызывается после успешного вызова GetMyUserName, которая первым делом просит WinAPI-функцию GetUserName вернуть размер строки, содержащей имя пользователя. В этом случае GetLastError вернёт ERROR_INSUFFICIENT_BUFFER (122dec), которая говорит нам, что необходимо использовать больший блок памяти.
В итоге GetMyUserName вернёт нам имя пользователя (успешно). Предположим, что IsUserMemberOf также будет успешна, но при этом она говорит нам, что пользователь не входит в группу. Тогда возвращаемое значение будет False, а тот факт, что большинство WinAPI функций не изменяют значение LastError при успешном вызове, говорит нам, что GetLastError всё ещё будет равно ERROR_INSUFFICIENT_BUFFER (122dec): поэтому когда мы будем проверять bIsMember — мы попадём на ветку кода с обработкой ошибки, хотя никакой ошибки не было.
Как вы видите, даже хотя мы можем получить дополнительные сведения от GetLastError, но эта функция реализована так, что мы не можем воспользоваться этой информацией.
Если вы планируете написать такую функцию, я призываю вас не использовать техники языка C. Почему? Просто посмотрите как выглядит этот стиль:
function GetUserNameW(lpBuffer: LPWSTR; nSize: PDWORD): BOOL;
var
pUserName: PWideChar;
res: NTSTATUS;
bRes: Boolean;
begin
bRes := FALSE;
if (nSize = nil) then
begin
SetLastError(ERROR_INVALID_PARAMETER);
goto Exit;
end;
if (lpBuffer = NIL) or (nSize^ <= InternalGetMinUserNameLength()) then
begin
nSize^ := InternalGetMinUserNameLength();
SetLastError(ERROR_INSUFFICIENT_BUFFER);
goto Exit;
end;
// Это не совсем нужно, но просто добавить сложности в пример
pUserName := HLOCAL(LocalAlloc(LPTR, InternalGetMinUserNameLength() * sizeof(WIDECHAR));
// TODO: проверить pUserName на nil: если nil - goto Exit
res := NtGetUserName(pUserName, InternalGetMinUserNameLength() * sizeof(WIDECHAR));
if NT_FAILED(res) then
begin
SetLastError(NtStatusToDosError(res));
goto Exit_1;
end;
if not StrCopyMemory(lpBuffer, pUserName) then
goto Exit_1;
bRes := TRUE;
:Exit_1
FreeMem(pUserName);
goto Exit;
:Exit
result := bRes;
end;
Выглядит сложно, не так ли? Я не собирался писать точную реализацию GetUserName (положа руку на сердце — я её не знаю), а вместо этого показал возможную реализацию. Так что WinAPI использует результат функции только как указание на ошибку. False означает провал операции, а GetLastError сообщит вам, что именно пошло не так. True означает, что всё в порядке.
Нам действительно нужно всё это в Delphi? Вообще-то нет. Если вы хотите написать библиотеку (DLL), которую нужно вызывать из других языков, то вы можете использовать SetLastError. И вам придётся возвращать результат функции в var/out параметре (по ссылке). Конечно же, вы также можете использовать тип HRESULT в качестве возвращаемого значения.
function GetUserNameW(lpBuffer: LPWSTR; nSize: PDWORD): HRESULT;
Если же вы хотите написать просто функцию для Delphi, то вы можете использовать исключения.
procedure GetUserNameW(Name: PWideChar; var nSize: DWORD);
Большой плюс — что вы можете использовать функцию как функцию, поскольку результат функции теперь может прямо указывать на результат операции, а не на признак ошибки.
function GetUserNameW(Name: PWideChar; var nSize: DWORD) : PWideChar;
begin
if (nSize = nil) then
begin
SetLastError(ERROR_INVALID_PARAMETER);
RaiseLastOsError;
end;
... // не буду копировать весь код - идею вы уловили
end;
Как вы можете видеть, мы вызываем RaiseLastOsError, которая возбуждает исключение EOsError. Оно содержит код от GetLastError в своём свойстве ErrorCode. Мы даже можем создать свой собственный класс исключения (к примеру, JWSCL использует EJwsclWinCallFailedException), и вообще убрать вызов SetLastError! И, конечно же, вы можете использовать родной строковый тип Delphi, что сделает эту функцию очень простой в использовании.
function GetUserName() : String;
Функция возвращает строку. Она не возвращает пустой строки при ошибке, как делали те реализации, что мы нашли в интернете. Иначе у нас была бы та же самая проблема с двумя наборами значений, спроецированными на одно.
function IsUserMemberOf(const Group : PSID) : Boolean;
var
hToken : HANDLE;
begin
result := false;
// получаем Token и сохраняем в hToken ... (короткий вариант)
if not OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY or TOKEN_DUPLICATE, hToken) then
RaiseLastOsError;
// дополнительные действия для Vista и много-много RaiseLastOsError;
if not CheckTokenMembership(hToken, Group, @result) then
RaiseLastOsError;
end;
Улучшенный вариант этой функции есть в JEDI Component Library (JCL) в файле JclSecurity.pas. Забудьте обо всех других реализациях!! Используйте эту:
function IsGroupMember(RelativeGroupID: DWORD): Boolean;
var
psidAdmin: Pointer;
Token: THandle;
Count: DWORD;
TokenInfo: PTokenGroups;
HaveToken: Boolean;
I: Integer;
const
SE_GROUP_USE_FOR_DENY_ONLY = $00000010;
begin
Result := not IsWinNT;
if Result then // на Win9x и ME нет безопасности
Exit;
psidAdmin := nil;
TokenInfo := nil;
HaveToken := False;
try
Token := 0;
HaveToken := OpenThreadToken(GetCurrentThread, TOKEN_QUERY, True, Token);
if (not HaveToken) and (GetLastError = ERROR_NO_TOKEN) then
HaveToken := OpenProcessToken(GetCurrentProcess, TOKEN_QUERY, Token);
if HaveToken then
begin
{$IFDEF FPC}
Win32Check(AllocateAndInitializeSid(SECURITY_NT_AUTHORITY, 2,
SECURITY_BUILTIN_DOMAIN_RID, RelativeGroupID, 0, 0, 0, 0, 0, 0,
psidAdmin));
if GetTokenInformation(Token, TokenGroups, nil, 0, @Count) or
(GetLastError <> ERROR_INSUFFICIENT_BUFFER) then
RaiseLastOSError;
TokenInfo := PTokenGroups(AllocMem(Count));
Win32Check(GetTokenInformation(Token, TokenGroups, TokenInfo, Count, @Count));
{$ELSE FPC}
Win32Check(AllocateAndInitializeSid(SECURITY_NT_AUTHORITY, 2,
SECURITY_BUILTIN_DOMAIN_RID, RelativeGroupID, 0, 0, 0, 0, 0, 0,
psidAdmin));
if GetTokenInformation(Token, TokenGroups, nil, 0, Count) or
(GetLastError <> ERROR_INSUFFICIENT_BUFFER) then
RaiseLastOSError;
TokenInfo := PTokenGroups(AllocMem(Count));
Win32Check(GetTokenInformation(Token, TokenGroups, TokenInfo, Count, Count));
{$ENDIF FPC}
for I := 0 to TokenInfo^.GroupCount - 1 do
begin
{$RANGECHECKS OFF} // Массив [0..0] - игнорируем ERangeError
Result := EqualSid(psidAdmin, TokenInfo^.Groups[I].Sid);
if Result then
begin
// Учесть denied ACE с SID администратора
Result := TokenInfo^.Groups[I].Attributes and SE_GROUP_USE_FOR_DENY_ONLY
<> SE_GROUP_USE_FOR_DENY_ONLY;
Break;
end;
{$IFDEF RANGECHECKS_ON}
{$RANGECHECKS ON}
{$ENDIF RANGECHECKS_ON}
end;
end;
finally
if TokenInfo <> nil then
FreeMem(TokenInfo);
if HaveToken then
CloseHandle(Token);
if psidAdmin <> nil then
FreeSid(psidAdmin);
end;
end;
function IsAdministrator: Boolean;
begin
Result := IsGroupMember(DOMAIN_ALIAS_RID_ADMINS);
end;
Это же можно сделать и на JWSCL:
uses
JwaWindows,
JwsclToken,
JwsclKnownSid,
JwsclUtils;
var
Token : TJwSecurityToken;
begin
JwInitWellKnownSIDs;
Token := TJwSecurityToken.CreateTokenEffective(TOKEN_READ or TOKEN_QUERY or TOKEN_DUPLICATE);
try
Token.ConvertToImpersonatedToken(DEFAULT_IMPERSONATION_LEVEL, MAXIMUM_ALLOWED);
if Token.CheckTokenMembership(JwAdministratorsSID) then
// Пользователь - член группы Администраторы
finally
Token.Free;
end;
end;