SvrAPI для непродвинутых. Часть1

Предыстория.

Как-то потребовалось программным способом открыть доступ к сетевому ресурсу. После прочтения (в очередной раз) хелпа от Дельфи стало ясно, что сетевым администрированием занимаются функции семейства Net*. А в частности для открытия, закрытия доступа к сетевому ресурсу, а также получения или изменения информации о нем применяются функции подсемейства NetShare*. Однако, как написано в хелпе, все эти функции работают только под WinNT. К сожалению...

Но раз Win'9x открывает (закрывает) доступ, значит, можно сделать то же самое и программно. И тут начались поиски.

В хелпе сказано, что для Си++ надо применять заголовочный модуль LmShare.h, в котором даны прототипы функций библиотеки netapi32.dll. Посмотрев эту библиотеку утилитой tdump.exe (лежит в директории Delphi_X\Bin), я увидел, что библиотека netapi32.dll (для Win'9x) кроме одной экспортируемой функции netbios более ничего не содержит. Далее включаю обычный поиск текста "NetShareAdd" в файлах *.dll. Поиск выдал результат - среди нескольких библиотек эту строку содержит и библиотека svrapi.dll.

Следуя проторенным путем, т.е. через tdump.exe, увидел, что эта библиотека содержит почти все экспортируемые функции с названиями Net*. Ура! Место, где "собака порылась" найдено! Осталось только эту собаку откопать.

Ясно, что без знания прототипов этих функций, сами по себе их названия - пустой хлам. Включаю поиск в Интернет строки "svrapi". И вот он, заветный заголовочный модуль! SvrApi.pas. Лежит, голубчик на JEDI! Написал его Petr Vones , за что ему большое спасибо! В этом модуле даны прототипы следующих классов Net-функций:

- ACCESS
- SHARE
- SESSION
- CONNECTION
- FILE
- SERVER
- SECURITY

Описание Net-функций приведено в хелпе от Дельфи. Их работа одинакова как для WinNT, так и для Win'9x. Правда, для Win'9x следует использовать несколько другие аргументы. О чем ниже и пойдет речь.

Функция NetShareAdd

Все семейство NetShare*, а это:

- NetShareAdd - открывает доступ к сетевому ресурсу
- NetShareDel - закрывает доступ к сетевому ресурсу
- NetShareEnum - показывает количество и свойства ресурсов с открытым доступом
- NetShareGetInfo - дает полную информацию об открытом сетевом ресурсе
- NetShareSetInfo - меняет некоторые значения открытого сетевого ресурса

здесь я описывать не буду, т.к. намереваюсь выложить примеры для работы со всеми функциями NetShare* на своем сайте . Опишу только одну - NetShareAdd

Итак функция NetShareAdd .
Вот ее прототип:

    function NetShareAdd(const pszServer: PChar;
    sLevel: SmallInt; pbBuffer: Pointer;
     cbBuffer: Word): NET_API_STATUS; stdcall;

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

Подключение внешней функции

Если не вдаваться в подробности, то в подключении внешних функций оказывается нет ничего сложного. В Unit.pas после описания класса TForm и до клаузы implementation следует написать:

    Function NetShareAdd(ServerName : PChar;
     Level : Integer; pbBuffer : Pointer;
    BufferSize : Integer) : Integer; stdcall; external 'svrapi.dll';

На что следует обратить внимание. Во-первых, я вольно заменил типы некоторых переменных на более привычный Integer (хотя по большому счету этого делать не следует). Во-вторых, stdcall - это способ передачи данных через стек, применяемый для Паскаля. И, в-третьих, external 'svrapi.dll' - означает, что функция находится во внешней библиотеке с названием svrapi.dll.
Теперь к аргументам.

Аргументы функции

ServerName - сетевое имя компьютера, для локального можно писать Nill .
Что можно сказать об этом аргументе. Для работы с локальной машиной проблем не возникает. Например, если сетевое имя машины Toshiba, то в этот параметр надо писать '\\Toshiba' или указать Nill . А вот для сетевой машины возникают проблемы. Испытывая NetShareAdd в сети, я постоянно результатом работы функции получал значение 65, что означает - "Нет доступа к сети" . Оказалось, чтобы открыть доступ к сети для NetShare-функций, надо "Разрешить удаленное управление этим сервером" (делается в "Пароли" из "Панели управления"). Тогда директории Windows присваивается сетевое имя ADMIN$ и после подключения этого имени в качестве сетевого диска, "сезам" открывается. Все это можно сделать программно: с помощью описываемой функции открыть доступ с именем ADMIN$ к любой директории, в том числе несуществующей ( см. ниже ) и с помощью одной из трех WNetAddConnection подключить сетевой диск для этого имени. Тогда все сработает на ура.

Level - уровень администрирования. Для WinNT применяют три уровня 1,2 и 502, для Win'9x следует применять уровень 50.

pbBuffer - указатель на структуру, в которую будем заносить все данные, необходимые для открытия доступа к ресурсу. На этой структуре следует остановиться более подробно. Вот она сама:

    Type
        TShareInfo50 = packet record
        shi50_netname: array[0..LM20_NNLEN] of Char;

    //сетевое имя

        shi50_type: Byte;

    //тип ресурса

        shi50_flags: Short;

    //флаг доступа

        shi50_remark: PChar;

    // комментарий

        shi50_path: PChar;

     // путь к ресурсу

        shi50_rw_password: array[0..SHPWLEN] of Char;

    //пароль полного доступа

        shi50_ro_password: array[0..SHPWLEN] of Char;

    //пароль "только чтение" доступа

        end;

shi50_netname - сетевое имя, по обращению к которому, будет доступен сетевой ресурс. Сетевое имя должно быть уникальным. Константа LM20_NNLEN имеет значение 12, т.е. сетевое имя не должно быть более 12-ти символов.

shi50_type - тип ресурса, может иметь следующие значения:

- STYPE_DISKTREE = 0; - будем открывать доступ к директории;
- STYPE_PRINTQ = 1; - будем открывать доступ к принтеру;

Есть еще две константы, но честно говоря, я их не тестировал:

- STYPE_DEVICE = 2; - будем открывать доступ к коммуникационному устройству;
- STYPE_IPC = 3; - открывает доступ к межпроцессорной коммуникации.

shi50_flags - флаг доступа, может иметь следующие значения:

- SHI50F_RDONLY = $0001; - доступ только на чтение;
- SHI50F_FULL = $0002; - полный доступ;
- SHI50F_DEPENDSON = SHI50F_RDONLY or SHI50F_FULL; - доступ в зависимости от введенного пароля;
- SHI50F_ACCESSMASK = SHI50F_RDONLY or SHI50F_FULL - доступ в зависимости от введенного пароля;
- SHI50F_PERSIST = $0100; - ресурс будет записан в Registry и при перезагрузке компьютера доступ будет открываться в автомате. Для Win'95 почему-то доступ запоминается и без указания этого параметра.
- SHI50F_SYSTEM = $0200; - в проводнике не будет видно, что доступ к этому ресурсу открыт.

shi50_remark - комментарий

shi50_path - полный физический путь к устройству. Здесь надо учесть следующее:

1. Если путь указать строчными (маленькими) буквами, то в проводнике не будет видно, что доступ к устройству открыт.
2. Если указать несуществующий путь, то доступ к такому устройству все равно будет открыт. Например, если открыть доступ к несуществующей папке C:\TEST333 и присвоить ей сетевое имя TEST66, то в сетевом окружении будет видно сетевое имя TEST66. Если затем создать папку с именем TEST333, то под этой папкой появится "синяя ручка".

shi50_rw_password - пароль для полного доступа;
shi50_ro_password - пароль "только чтение", где SHPWLEN = 8, т.е. максимальное количество символов в пароле с учетом символа под номером 0 составляет 9 штук.

И наконец, последний аргумент
BufferSize - размер буфера, в котором находятся необходимые для открытия доступа данные.

Теперь перейдем к значению, возвращаемому функцией NetShareAdd .

Результат функции

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

    NO_ERROR = 0;

    'Все в порядке'

    (из LmErr.pas):
    NERR_BASE = 2100;

    базовая ошибка для NERR_

    ;
    NERR_NetNotStarted = NERR_BASE+2

    'Сеть недоступна'

    ;
    NERR_UnknownServer = NERR_BASE+3

    'Неизвестный сервер'

    ;
    NERR_ServerNotStarted = NERR_BASE+14

    'Сервер не работает'

    ;
    NERR_UnknownDevDir = NERR_BASE+16

    'Сетевое устройство отсутствует'

    ;
    NERR_RedirectedPath = NERR_BASE+17

    'Переназначенное устройство'

    ;
    NERR_DuplicateShare = NERR_BASE+18

    'Сетевое имя уже существует'

    ;
    NERR_BufTooSmall = NERR_BASE+23

    'Слишком маленький буфер для данных!'

    ;
    NERR_NetNameNotFound = NERR_BASE+210

    'Сетевое имя не существует'

    ;
    NERR_InvalidComputer = NERR_BASE+251

    'Неверное имя компьютера'

    ;
    NERR_ShareNotFound = NERR_BASE+292

    'Сетевое устройство не обнаружено'

    .

Однако бывают и другие ошибки. Почему-то функции SysErrorMessage (GetLastError) и WNetGetLastError при работе с NetShare* (по крайней мере у меня) не срабатывают, поэтому значения ошибок, коды которых менее 2100, я взял из ошибок, общих для всей операционной системы. (из Windows.pas:)

    ERROR_NOT_ENOUGH_MEMORY = 8

    'Недостаточно памяти'

    ;
    ERROR_BAD_NETPATH = 53

    'Неверное сетевое имя'

    ;
    ERROR_NETNAME_DELETED = 64

    'Сетевой ресурс более недоступен'

    ;
    ERROR_NETWORK_ACCESS_DENIED = 65

    'Отсутствует доступ к сети'

    ;
    ERROR_BAD_DEV_TYPE = 66

    'Неверный тип сетевого ресурса'

    ;
    ERROR_BAD_NET_NAME = 67

    'Не найдено сетевое имя'

    ;
    ERROR_INVALID_PARAMETER = 87

    'Неверный параметр'

    ;
    ERROR_INVALID_LEVEL = 124

    'Неверный уровень администрирования'

    ;

Т.е. если результатом функции возвращается число больше, чем 2100, то смотри модуль LmErr.pas, если меньше - то модуль Windows.pas.

На этом вроде все. Но, лучше один раз увидеть, чем... Поэтому посмотрим, как все работает на практике.

Пример работы NetShareAdd

Для директории C:\Temp локальной машины откроем доступ с именем "TEST", паролем для чтения "QWE", для полного доступа "ASDF", комментарием "This is a network machine's commentary", с автоматическим открытием доступа при перезагрузке компьютера. И заодно закроем доступ.

На новую форму следует положить две кнопочки и привести вид Unit1.pas в соответствие с ниже приведенным кодом.

    unit Unit1;
    interface
    uses
        Windows, Messages, SysUtils, Classes,
        Graphics, Controls, Forms, Dialogs, StdCtrls ;
    type
        TForm1 = class(TForm)
            Button1: TButton;
            Button2: TButton;
            procedure Button1Click(Sender: TObject);
            procedure Button2Click(Sender: TObject);
    private

    { Private declarations }

    public

    { Public declarations }

    end;

    //подключаем две функции из библиотеки svrapi.dll

    Function NetShareAdd(Servername : PChar; Level : Integer; Buffer : Pointer;
        BufferSize : Integer) : Integer; stdcall; external

    'svrapi.dll'

    ;
    Function NetShareDel(Servername : PChar;
           NetName : PChar; Reserved:DWORD): DWORD;
        stdcall; external

    'svrapi.dll'

    ;

    //необходимые константы

    const

    {см. LmCons.pas}

        LM20_NNLEN = 12;
        SHPWLEN = 8;
        SHI50F_RDONLY = 1;
        SHI50F_FULL = 2;
        STYPE_DISKTREE = 0;
        SHI50F_PERSIST = $0100;
        SHI50F_SYSTEM = $0200;

    {см. LmErr.pas}

        NERR_BASE = 2100;
        NERR_NetNotStarted = NERR_BASE+2;
        NERR_UnknownServer = NERR_BASE+3;
        NERR_ServerNotStarted = NERR_BASE+14;
        NERR_UnknownDevDir = NERR_BASE+16;
        NERR_RedirectedPath = NERR_BASE+17;
        NERR_DuplicateShare = NERR_BASE+18;
        NERR_BufTooSmall = NERR_BASE+23;
        NERR_NetNameNotFound = NERR_BASE+210;
        NERR_InvalidComputer = NERR_BASE+251;
        NERR_ShareNotFound = NERR_BASE+292;

    //формируем тип для записи с необходимыми параметрами

    Type
    TShareInfo50 = Record
        shi50_netname: Array[0..LM20_NNLEN] Of Char;

    //сетевое имя

        shi50_type: Byte;

    //тип ресурса

        shi50_flags: Short;

    //флаг доступа

        shi50_remark: PChar;

    // комментарий

        shi50_path: PChar;

    // путь к ресурсу

        shi50_rw_password: Array[0..SHPWLEN] Of Char;

    //пароль полного доступа

        shi50_ro_password: Array[0..SHPWLEN] Of Char;

    //пароль "только чтение" доступа

    End; {Record}
    var
        Form1: TForm1;
    implementation

    {$R *.DFM}

    procedure TForm1.Button1Click(Sender: TObject);
    Var
        info50: TShareInfo50;
        rc, cb: Integer;
        ServerName, Path, NetName,
            ErrMes, ErrCap, Comment:String;
        MessIconBtn:Byte;
    begin

    //установим необходимые параметры

    ServerName:=

    ''

    ;
    Path:=

    'C:\TEMP'

    ;
    NetName:=

    'TEST'

    ;
    Comment:=

    'This is a network machine''s commentary'

    ;

    //заполним буфер

    FillChar(info50, sizeof(info50), 0);
    With info50 Do
    Begin

    {With}

    StrCopy(shi50_netname, PChar(NetName));

    //сетевое имя

    shi50_type := STYPE_DISKTREE;

     //подключать будем диск

    shi50_remark:=PChar(Comment);

    //комментарий

    shi50_flags:= SHI50F_RDONLY OR SHI50F_FULL

    //доступ определяется паролем

                        OR SHI50F_PERSIST;

    //и пишется в Registry

    shi50_path:=PChar(Path);

    //путь

    StrPCopy(shi50_rw_password,

    'ASDF'

    );

    //пароль для полного доступа

    StrPCopy(shi50_ro_password,

    'QWE'

    );

    // пароль для "только чтение"

    End;{With}

    //установим размер буфера

    cb := sizeof(info50);

    //основная функция

    rc := NetShareAdd(PChar(ServerName), 50, @info50, cb);

    //сформируем текст сообщений об успехе или ошибках

    ErrMes:=

    'Доступ к устройству "'

    +NetName+

    '" открыт!'

    ;
    ErrCap:=

    'Все в порядке!'

    ;
    MessIconBtn:=MB_OK OR MB_ICONINFORMATION;

    //проверка ошибок

    If rc <> 0 Then
    Begin

    {ошибка}

    ErrCap:='

    Ошибка!'

    ;
    MessIconBtn:=MB_OK OR MB_ICONERROR;
    Case rc Of

    //расшифровка ошибок

        ERROR_NOT_ENOUGH_MEMORY : ErrMes:=

    'Недостаточно памяти'

    ;
        ERROR_BAD_NETPATH : ErrMes:=

    '"'

    +Servername+

    '" - неверное сетевое имя!'

    ;
        ERROR_NETNAME_DELETED : ErrMes:=

    'Сетевой ресурс более недоступен'

    ;
        ERROR_NETWORK_ACCESS_DENIED: ErrMes:=

    'Отсутствует доступ к сети'

    ;
        ERROR_BAD_DEV_TYPE : ErrMes:=

    'Неверный тип сетевого ресурса'

    ;
        ERROR_BAD_NET_NAME : ErrMes:=

    'Не найдено сетевое имя'

    ;
        ERROR_INVALID_PARAMETER : ErrMes:=

    'Неверный параметр'

    ;
        ERROR_INVALID_LEVEL : ErrMes:=

    'Неверный уровень администрирования'

    ;

        NERR_InvalidComputer:ErrMes:=

    'Неверное имя компьютера!'

    ;
        NERR_UnknownServer:ErrMes:=

    'Неизвестный сервер!'

    ;
        NERR_UnknownDevDir:ErrMes:=

    'Устройство "'

    +Path+

    '" отсутствует!'

    ;
        NERR_ServerNotStarted:ErrMes:=

    'Сервер не работает!'

    ;
        NERR_RedirectedPath:ErrMes:=

    'Переназначенный путь!'

    ;
        NERR_DuplicateShare:ErrMes:=

    'Сетевое имя "'

    +NetName+

    '" уже существует!'

    ;
        NERR_BufTooSmall:ErrMes:=

    'Слишком маленький буфер для данных!'

    ;
    Else
    End; {Case}
    End; {ошибка}

    //выдадим сообщение

    MessageBox(Application.Handle,PChar(ErrMes),
            PChar(ErrCap),MessIconBtn);
    end;

    procedure TForm1.Button2Click(Sender: TObject);
    Var
    rc:DWord;
    Servername,NetName, ErrMes, ErrCap:String;
    MessIconBtn:Byte;
    begin
    ServerName:=

    ''

    ;
    NetName:=

    'TEST'

    ;
    rc:=NetShareDel(PChar(ServerName),
                PChar(NetName),0);
    ErrMes:=

    'Доступ к устройству "'

    +NetName+

    '" закрыт!'

    ;
    ErrCap:=

    'Все в порядке!'

    ;
    MessIconBtn:=MB_OK OR MB_ICONINFORMATION;

    If rc <> 0 Then
    Begin

    {ошибка}
    //ошибка

    ErrCap:=

    'Ошибка!'

    ;
    MessIconBtn:=MB_OK OR MB_ICONERROR;

    Case rc Of

    //расшифровка ошибок

        ERROR_BAD_NETPATH:ErrMes:=

    '"'+Servername

    +

    '" - неверное сетевое имя!'

    ;
        ERROR_INVALID_PARAMETER:ErrMes:=

    'Неверный параметр!'

    ;
        NERR_NetNotStarted:ErrMes:=

    'Сеть недоступна!'

    ;
        NERR_ServerNotStarted:ErrMes:=

    'Сервер не работает!'

    ;
        NERR_NetNameNotFound:ErrMes:=

    'Устройство не существует!'

    ;
        NERR_ShareNotFound:ErrMes:=

    'Сетевое имя "'

    +NetName+

    '" не найдено!'

    ;
    Else

    //смотри ошибки для NetShareAdd или

        ErrMes:=

    'Неизвестная сетевая ошибка!'

    ;
    End; {Case}
    End;{ошибка}
    MessageBox(Application.Handle,PChar(ErrMes),
                   PChar(ErrCap),MessIconBtn);
    end;
    end.

Не забываем, что все, о чем говорилось в этой статье, относится к Win'9x!