Структура администратора ресурсов

Структура администратора ресурсов

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

Мы рассмотрим:

• Функцию resmgr_attach() и ее параметры;

• Подстановку своих собственных функций;

• Общую схему работы администратора ресурсов;

• Сообщения, которые должны бы быть сообщениями установления соединения, но таковыми не являются;

• Составные сообщения.

Функция resmgr_attach() и ее параметры

Как вы уже видели в приведенном выше примере с администратором /dev/null, первое, что вы должны сделать — это зарегистрировать у администратора процессов свою точку монтирования. Это делается с помощью функции resmgr_attach(), которая имеет следующий прототип:

int resmgr_attach(void *dpp, resmgr_attr_t *resmgr_attr,

 const char *path, enum _file_type file_type,

 unsigned flags,

 const resmgr_connect_funcs_t *connect_funcs,

 const resmgr_io_funcs_t *io_funcs,

 RESMGR_HANDLE_T *handle);

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

dpp Дескриптор диспетчера (dispatch handle). Обеспечивает интерфейсу диспетчеризации возможность управлять приемом сообщений для вашего администратора ресурсов.
resmgr_attr Управляет характеристиками администратора ресурсов, как обсуждалось ранее.
path Точка монтирования, которую вы регистрируете. Если вы регистрируете дискретную точку монтирования (как, например, в случае с /dev/null или /dev/ser1), клиент должен указывать ее точно, без каких бы то ни было дополнительных компонентов имени пути в ее конце. Если вы регистрируете каталоговую точку монтирования (как было бы, например, в случае с сетевой файловой системой, монтируемой как /nfs), то соответствие тоже должно быть точным, но с той оговоркой, что в этом случае продолжение имени пути допускается; то, что идет после точки монтирования, будет передано функции установления соединения (например, имя пути /nfs/etc/passwd даст совпадение с точкой монтирования сетевой файловой системой, а «остатком» будет etc/passwd). (Эта особенность, кстати, может пригодиться и там, где на первый взгляд логичнее было бы регистрировать дискретную точку монтирования — см. параграф «Регистрация префикса» раздела «Взгляд со стороны администратора ресурсов» — прим. ред.)
file_type Класс администратора ресурсов. См. ниже.
flags Дополнительные флаги, управляющие поведением вашего администратора ресурсов. Эти флаги выбираются из множества _RESMGR_FLAG_BEFORE, _RESMGR_FLAG_AFTER, _RESMGR_FLAG_DIR и константы 0. Флаги «BEFORE» (букв, «перед») и «AFTER» (букв, «после») указывают на то, что ваш администратор ресурсов хочет зарегистрироваться на данной точке монтирования перед или, соответственно, после других. Эти два флага могут быть полезны, если надо реализовать объединенные файловые системы. Мы вскоре вернемся к взаимосвязи этих флагов. Флаг «DIR.» («каталог») указывает на то, что ваш администратор ресурса хочет обслуживать указанную точку монтирования и все, что находится ниже ее — этот стиль характерен для администратора файловой системы, в противоположность администратору ресурсов, регистрирующему дискретную точку монтирования.
connect_funcs и io_funcs Эти параметры являются просто списком функций установления соединения и функций ввода/вывода, которые вы хотите привязать к точке монтирования.
handle Это «расширяемая» структура (также известная как «атрибутная запись»), описывающая монтируемый ресурс. Например, в случае последовательного порта вы могли бы расширить стандартную атрибутную запись POSIX-уровня информацией о базовом адресе последовательного порта, скорости обмена в бодах, и т.д.

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

Когда вы определились с точкой монтирования и хотите ее зарегистрировать, вы должны сообщить администратору процессов, хочет ли ваш администратор ресурсов обрабатывать запросы от кого попало или только от клиентуры, которая помечает свои сообщения установления соединения специальными метками. Например, рассмотрим драйвер очередей сообщений POSIX (mqueue). Ему совершенно ни к чему «обычные» вызовы open() от старых добрых клиентов — он просто не будет знать, что с ними делать. Он примет сообщения только от тех клиентов, которые используют POSIX-вызовы mq_open(), mq_receive(), и т.п. Чтобы не позволять администратору процессов даже перенаправлять «обычные» запросы администратору очередей mqueue, у этого администратора в параметре параметр file_type задается значение _TYPE_MQUEUE. Это означает, что когда клиент пытается с помощью администратора процессов выполнить разрешение имени, при этом явно не определив, что хочет поговорить с администратором ресурсов, зарегистрированным как _FTYPE_MQUEUE, администратор процессов не будет даже рассматривать администратор mqueue как возможный вариант.

Если только вы не делаете что-либо уж очень специфичное, вам лучше всего подойдет значение file_type, равное _FTYPE_ANY, означающее, что ваш администратор ресурсов готов обработать запрос от любого клиента. Полный список именованных констант _FTYPE_* приведен в файле <sys/ftype.h>.

Что касательно флагов «BEFORE» и «AFTER», тут все становится интереснее. Вы можете задать либо один из этих флагов, либо константу 0.

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

Администратор Флаг Очередность
1 _RESMGR_BEFORE 1
2 _RESMGR_AFTER 1, 2
3 0 1, 3 ,2
4 _RESMGR_BEFORE 1, 4, 3, 2
5 _RESMGR_AFTER 1, 4, 3, 5, 2
6 0 1, 4, 6, 3, 5, 2

Из таблицы видно, что первый администратор ресурса, явно определивший флаг, далее не сдвигается со своей позиции.(См. таблицу: администратор ресурсов № 1 был первым определившим флаг «BEFORE»; кто бы теперь ни зарегистрировался, он так и останется первым в списке. Аналогично, администратор ресурсов № 2 был первым определившим флаг «AFTER» — и снова, независимо от того, кто еще будет регистрироваться после него, он всегда остается в списке последним.) Если не определен никакой флаг, это действует как флаг «между». Когда стартует администратор ресурсов № 3 (указав нулевой флаг), он помещается в середину очереди. Как и в случае с флагами «BEFORE» и «AFTER», здесь имеет место упорядочивание, в результате чего все вновь регистрирующиеся «средние» администраторы ресурсов располагаются перед уже зарегистрированными «средними».

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

Имейте в виду, что данное обсуждение применимо только для администраторов ресурсов, регистрируемых при одной и той же точке монтирования. Монтирование «/nfs» с флагом «BEFORE» и «/disk2» с флагом «AFTER» не будет иметь никакого взаимного влияния. Однако, если вы затем будете монтировать еще одну «/nfs» или «/disk2», вот тогда эти флаги и проявят себя.

И наконец, функция resmgr_attach() в случае успешного завершения возвращает дескриптор (handle) в виде небольшого целого числа (или -1 при неудаче). Этот дескриптор можно затем применить для того, чтобы убрать данное имя пути из внутренней таблицы имен путей администратора процессов.

Подстановка своих собственных функций

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

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

Какую функцию запрограммировать первой — это будет зависеть от того, какой администратор ресурсов вы пишете. Если это администратор файловой системы, отвечающий за точку монтирования и все, что под ней, то вам, скорее всего, лучше всего начать с функции io_open(). С другой стороны, если вы пишете администратор ресурса с дискретной точкой монтирования, который выполняет «традиционные» операции ввода/вывода (то есть вы будете общаться с ним преимущественно вызовами типа read() и write()), то лучшей стартовой позицией для вас были бы функции io_read() и/или io_write(). Если же вы пишете администратор ресурса с дискретной точкой монтирования, но вместо «традиционных» операций ввода/вывода основу его функциональности составляют вызовы типа devctl() или ioctl(), то правильнее было бы начать с io_devctl().

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

Это означает, что если вы захотите что-то дополнительно проконтролировать, просто добавьте дополнительный диагностический вызов printf(), чтобы он сказал что-то типа «Я тут!», а затем делайте «то, что надо сделать» — все очень просто.

Вот фрагмент администратора ресурсов, который перехватывает функцию io_open():

// Упреждающая декларация

int io_open(resmgr_context_t*, io_open_t*,

 RESMGR_HANDLE_T*, void*);

int main() {

 // Все как в примере /dev/null,

 // кроме следующего за этой строкой:

 iofunc_func_init(_RESMGR_CONNECT_NFUNCS, &cfuncs,

  _RESMGR_IO_NFUNCS, &ifuncs);

 // Добавьте это для перехвата управления:

 cfuncs.open = io_open;

Если вы описали функцию io_open() корректно, как в этом примере кода, то вы можете вызывать функцию, заданную по умолчанию, из вашей собственной!

int io_open(resmgr_context_t *ctp, io_open_t *msg,

 RESMGR_HANDLE_T *handle, void *extra) {

 printf("Мы в io_open! ");

 return (iofunc_open_default(ctp, msg, handle, extra));

}

Таким образом, вы по-прежнему применяете POSIX-обработчик по умолчанию iofunc_open_default(), но заодно перехватываете управление для вызова printf().

Очевидно, что вы могли бы выполнить аналогичные действия для функций io_read(), io_write(), io_devctl() и любых других, для которых есть обработчики POSIX-уровня по умолчанию. Идея, кстати, действительно отличная, потому что такой подход показывает вам, что клиент вызывает ваш администратор ресурса именно так, как вы предполагаете.

Общая схема работы администратора ресурсов

Как мы уже намекнули выше в разделах, посвященных краткому рассмотрению клиента и администратора ресурсов, последовательность действий начинается на клиентской стороне с вызова open(). Он транслируется в сообщение установления соединения, которое принимается и обрабатывается функцией администратора ресурсов io_open().

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

Но на самом деле роль функции io_open() гораздо значительнее. Она отвечает не только за проверку, может клиент открыть ресурс или нет, но также за следующее:

• инициализацию внутренних параметров библиотеки;

• привязку к запросу контекстного блока;

• привязку к контекстному блоку атрибутной записи.

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

Будучи однажды вызвана, io_open() выпадает из рассмотрения. Клиент может либо прислать сообщение ввода/вывода, либо нет, но в любом случае должен будет однажды завершить «сеанс связи» с помощью сообщения, соответствующего функции close(). Заметьте, что если клиента вдруг постигает внезапная смерть (например, он получает SIGSEGV, или выходит из строя узел, на котором он работает), операционная система автоматически синтезирует сообщение close(), чтобы администратор ресурсов смог корректно завершить сессию. Поэтому вы гарантированно получите сообщение close()!

Сообщения, которые должны быть сообщениями установления соединения, но таковыми не являются

Тут есть один интересный момент, который вы, может быть, для себя уже отметили. Прототип клиентской функции chown() имеет вид:

int chown(const char *path, uid_t owner, gid_t group);

Вспомните: сообщение об установлении соединения всегда содержит имя пути и является либо однократным, либо устанавливает контекст для дальнейших сообщений ввода/ вывода.

Так почему же сообщение, соответствующее клиентской функции chown(), не является сообщением установления соединения? К чему здесь сообщение ввода/вывода, когда в прототипе даже дескриптора файла нет?!

Ответ простой — чтобы облегчить вам жизнь.

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

• Каждой функции приходится вызывать процедуру поиска.

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

В QNX/Neutrino же происходит следующее. Клиент создает составное сообщение — реально это одно сообщение, но оно включает в себя несколько сообщений администратору ресурсов. Без составных сообщений мы могли бы смоделировать функцию chown() чем-то таким:

int chown(const char *path, uid_t owner, gid_t group) {

 int fd, sts;

 if ((fd = open(path, O_RDWR)) == -1) {

  return (-1);

 }

 sts = fchown(fd, owner, group);

 close(fd);

 return (sts);

}

где функция fchown() — это версия функции chown(), ориентированная на файловые дескрипторы. Проблема здесь в том, что мы в этом случае используем три вызова функций (а значит, и три отдельных транзакции передачи сообщений) и привносим дополнительные накладные расходы применением функций open() и close() на стороне клиента.

При использовании составных сообщений в QNX/Neutrino непосредственно клиентским вызовом chown() создается одиночное сообщение, выглядящее примерно так:

Составное сообщение.

Сообщение состоит из двух частей. Первая часть посвящена установлению соединения (подобно сообщению, которое сгенерировала бы функция open()), вторая — вводу/выводу (эквивалент сообщения, генерируемого функцией fchown()). Никакого эквивалента функции close() здесь нет, поскольку мы выбрали сообщение типа _IO_CONNECT_COMBINE_CLOSE, которое гласит: «Открой указанное имя пути, используй полученный дескриптор файла для обработки остальной части сообщения, а когда закончишь дела или столкнешься с ошибкой, закрой дескриптор».

Написанный вами администратор ресурса даже не заметит, вызвал ли клиент функцию chown() или сначала сделал open(), а потом вызвал fchown() и далее close(). Все это скрыто базовым уровнем библиотеки.

Составные сообщения

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

Предположим, что в клиентском процессе есть два или более потоков, работающих с одним дескриптором файла. Один из потоков в клиенте вызывает функцию lseek(), за которой следует read(). Все так, как мы и предполагаем. А вот если другой клиента попробует выполнить ту же самую последовательность операций с тем же самым дескриптором файла, вот тут у нас начнутся проблемы. Поскольку функции lseek() и read() друг о друге ничего не знают, то возможно, например, что первый поток выполнит lseek(), а затем будет вытеснен вторым потоком. Второй поток выполнит свои lseek() и read(), после чего освободит процессор. Проблема здесь состоит в том, что поскольку эти два потока разделяют один и тот же дескриптор файла, у первого потока теперь получается неправильное смещение lseek(), поскольку оно было изменено функциями lseek() и read() второго потока! Эта проблема проявляется также с дескрипторами файлов, которые дублируются (dup()) между процессами, не говоря уже о сети.

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

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

Давайте взглянем на библиотечный вызов readblock() (из <unistd.h>):

int readblock(int fd, size_t blksize, unsigned block,

 int numblks, void *buff);

(Функция writeblock() описывается аналогично.)

Вы можете вообразить для функции readblock() довольно «простенькую» реализацию:

int readblock(int fd, size_t blksize, unsigned block,

 int numblks, void *buff) {

 lseek(fd, blksize * block, SEEK_SET); // Идем к блоку

 read(fd, buff, blksize * numblks);

}

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

int readblock(int fd, size_t blksize, unsigned block,

 int numblks, void *buff) {

 pthread_mutex_lock(&block_mutex);

 lseek(fd, blksize * block, SEEK_SET); // Идем к блоку

 read(fd, buff, blksize * numblks);

 pthread_mutex_unlock(&block_mutex);

}

(Мы здесь предполагаем, что мутекс уже инициализирован.)

Этот код по-прежнему уязвим для «незащищенного» доступа — если некий поток вызовет lseek() на этом файловом дескрипторе без предварительной попытки захвата мутекса, вот у нас уже и ошибка.

Решение проблемы заключается в использовании составного сообщения, аналогично вышеописанному случаю с функцией chown(). В данном случае библиотечная реализация функции readblock() помещает обе операции — lseek() и read() — в единое сообщение и посылает это сообщение администратору ресурсов:

Составное сообщение для функции readblock().

Это работает, потому что передача сообщения является атомарной операцией. С точки зрения клиента, сообщение уходит либо целиком, либо не уходит вообще. Поэтому, вмешательство «незащищенной» функции lseek() становится несущественным — когда администратор ресурсов принимает сообщение с запросом readblock(), он делает это за один прием. (Очевидно, что в этом случае пострадает сама «незащищенная» lseek(), поскольку после отработки readblock() смещение на этом дескрипторе файла будет отличаться от того, которое она хотела установить.)

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

Поделитесь на страничке

Следующая глава >

Похожие главы из других книг

ЦИФРА ЗАКОНА: Письмо несчастья: Может ли "покаянное письмо" спасти системного администратора?

Из книги Журнал «Компьютерра» № 11 от 20 марта 2007 года автора Журнал «Компьютерра»

ЦИФРА ЗАКОНА: Письмо несчастья: Может ли "покаянное письмо" спасти системного администратора? Автор: Павел ПротасовСреди обилия заблуждений, бродящих по умам наших соотечественников, одно из первых мест занимают те, что связаны с законодательством. Об одном из них я и


(8.21) Утерян пароль администратора. Что делать? Доступ к компьютеру полный.

Из книги Win2K FAQ (v. 6.0) автора Шашков Алексей

(8.21) Утерян пароль администратора. Что делать? Доступ к компьютеру полный. Удалите файлы %Windir%system32configsam*. Если W2k установлен на FAT/FAT32, то из Win9x или с дискеты, если на NTFS – придется установить параллельную копию системы или снять жесткий диск и поставить его на другую машину с


1.7. Что такое сервер? (или Курс Молодого Администратора)

Из книги Linux-сервер своими руками автора Колисниченко Денис Николаевич

1.7. Что такое сервер? (или Курс Молодого Администратора) Эта глава предназначена для начинающих системных администраторов, которые вообще с трудом представляют, что такое сервер и с чем его едят. Я принципиально не буду называть таких читателей «чайниками», поскольку сам


Смена расширения: как обойти ограничения администратора и прокси-сервера

Из книги Как найти и скачать в Интернете любые файлы автора Райтман М. А.

Смена расширения: как обойти ограничения администратора и прокси-сервера Надеюсь, некоторые советы в предыдущем разделе помогли вам получить доступ к тем веб-страницам, посещение которых ограничено или запрещено. Конечно, описанные методы работают не в 100 % случаев, и в


3.2.5. Запуск программы с правами администратора

Из книги Первые шаги с Windows 7. Руководство для начинающих автора Колисниченко Денис Н.

3.2.5. Запуск программы с правами администратора Почему Windows XP была настоящим раем для вирусописателя? Иногда полчаса, проведенные в Интернете без антивируса и брандмауэра, заканчивались «поселением» на компьютере с десятка вирусов и шпионских программ. А все из-за того,


5.1.5. Запуск командной строки с правами администратора

Из книги Ubuntu 10. Краткое руководство пользователя автора Колисниченко Д. Н.

5.1.5. Запуск командной строки с правами администратора Командную строку вам придется запускать очень редко, но ее запуск обычно требует прав администратора. Можно щелкнуть на ярлыке Командная строка правой кнопкой мыши и выбрать команду Запуск от имени администратора. А


21.6. Команды системного администратора

Из книги Firebird РУКОВОДСТВО РАЗРАБОТЧИКА БАЗ ДАННЫХ автора Борри Хелен

21.6. Команды системного администратора 21.6.1. Команды free и df— информация о системных ресурсах Команда free выводит информацию об использовании оперативной и виртуальной памяти, а df — об использовании дискового пространства. Из рис. 21.5 видно, что в системе установлено всего


Инструмент администратора

Из книги Введение в QNX/Neutrino 2. Руководство по программированию приложений реального времени в QNX Realtime Platform автора Кёртен Роб

Инструмент администратора Инсталляционный комплект Firebird не содержит инструментов администратора с графическим интерфейсом. У него есть набор инструментов командной строки (исполняемые программы), которые расположены в каталоге /bin каталога инсталляции Firebird. Их


Графические инструменты администратора

Из книги iOS. Приемы программирования автора Нахавандипур Вандад

Графические инструменты администратора Следующий список является лишь выборкой некоторых наиболее популярных элементов. Полный список см. на http://www.ibphoenix.com/main.nfs?a=ibphoenix &page=ibp_admin_tooIs. Database Workbench Database Workbench может соединяться с любым сервером Firebird на любой платформе. Он


Поиск администратора процессов

Из книги Linux и все, все, все... Статьи и колонки в LinuxFormat, 2006-2013 автора Федорчук Алексей Викторович

Поиск администратора процессов Теперь, когда мы знаем основные этапы поиска конкретного администратора ресурса, осталось раскрыть тайну поиска администратора процесса, с которого все начинается. На самом деле все очень просто. По определению, администратору процессов


Взгляд со стороны администратора ресурсов

Из книги автора

Взгляд со стороны администратора ресурсов Давайте теперь посмотрим на вещи с позиции администратора ресурса. Перво-наперво администратор ресурса должен сообщить администратору процессов, что он берет на себя ответственность за некоторую часть пространства имен путей


Библиотека администратора ресурсов

Из книги автора

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


Написание администратора ресурсов

Из книги автора

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


Блокирование в пределах администратора ресурсов

Из книги автора

Блокирование в пределах администратора ресурсов До настоящего момента мы избегали разговоров о возможности блокирования в пределах администратора ресурсов. Мы предполагали, что у нас есть функция-обработчик (например, io_read()), и что данные будут доступны немедленно. А


13.7. Получение ресурсов из библиотеки ресурсов

Из книги автора

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


Настройка окружения администратора

Из книги автора

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