Delphi World - это проект, являющийся сборником статей и малодокументированных возможностей  по программированию в среде Delphi. Здесь вы найдёте работы по следующим категориям: delphi, delfi, borland, bds, дельфи, делфи, дэльфи, дэлфи, programming, example, программирование, исходные коды, code, исходники, source, sources, сорцы, сорсы, soft, programs, программы, and, how, delphiworld, базы данных, графика, игры, интернет, сети, компоненты, классы, мультимедиа, ос, железо, программа, интерфейс, рабочий стол, синтаксис, технологии, файловая система...
Мониторинг сетей в Delphi

Оформил: DeeCo

Автор: Александр (Rouse_)


Вступление:

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

  1. Объявление функции (для Windows 9x-Me и Windows NT, 2000, XP)
  2. Краткое объяснение параметров функции
  3. Список структур передаваемых функции
  4. Краткое описание структур (по одной для каждого типа Windows)
  5. Пример вызова функции и обработка результатов

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

Демонстрационная программа

Совет: прочитайте внимательно всю статью, так как те места, которые прокомментированы в начале статьи, я не буду объяснять повторно в ее конце или середине.

Примечание:

  • Все объявления функций и примеры написаны с учетом динамического подключения библиотек.
  • Места указанные мной как "Не используется" - означают, что не используется мной :), т.е. не критичны.
  • Весь код дан только как пример использования функций и не может являться образцом написания программ.

Надеюсь, после прочтение данного материала у вас исчезнут большинство вопросов "Как". Возможно, появятся вопросы "Почему", в этом случае я всегда готов помочь, так как только из-за обилия вопросов "Как" и практически полного отсутствия "Почему", я и взялся за написание этой статьи. ;)

Итак, начнем.


Краткая таблица приведения типов:

А начнем мы с небольшого отступления. Кроме тех функций, которые будут приведены в данной статье, существует еще много полезных, достойных вашего изучения. Есть одно "Но". Описания этих функций даны в MSDN с учетом синтаксиса C++, а не Delphi, я попытаюсь исправить этот недостаток, и приведу небольшую таблицу, цель которой помочь вам в трансляции описания функций и структур на Delphi. Таблица не может претендовать на полноту, возможно я где-то напутал (надеюсь что нет) с диапазоном значений, но основной принцип приведения здесь показан правильно.
C++ Delphi Range
Char ShortInt -128 to 127
- Char 1 ASCII character (0 to 255)
Int, Short SmallInt -32,768 to 32,767
Long Integer / LongInt -2,147,483,647 to 2,147,483,647
Unsigned Char Byte 0 to 255
Unsigned Int / Unsigned Short Word 0 to 65,535
Unsigned Long Cardinal / Longword 0 to 4,294,967,295
Float Single 3.4E-38 TO 3.4E+38
Double Double / Comp 1.7E-308 TO 1.7E+308
Long Double Extended 3.4E-4932 TO 3.4E+4932
Void Pointer N/A -- an untyped pointer
- Boolean True or False
String - a C++ standard object
- String An array of up to 255 ASCII characters
char FAR * Pchar Pointer to a null-terminated string
unsigned short FAR * PWORD Type of pointer
LPWSTR PWideChar Type of pointer
LPTSTR PAnsiChar Type of pointer

Данные действительны для Delphi v7.0

Определение доступных ресурсов

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

Объявление функции для Windows 9х - Ме:

var
  NetShareEnum: function(pszServer: PChar;
    sLevel: Cardinal;
    pbBuffer: PChar;
    cbBuffer: Cardinal;
    pcEntriesRead,
    pcTotalAvail: Pointer): DWORD; stdcall;

Параметры:
pszServer - должен содержать имя удаленного компьютера на котором должна выполнится функция, если выполняем у себя то данному параметру можно присвоить NIL.
sLevel - должен содержать идентификатор структуры.
pbBuffer - должен содержать указатель на массив структур.
cbBuffer - должен содержать размер массива структур.
pcEntriesRead - должен содержать указатель на переменную в которую запишется количество общих ресурсов доступных на данный момент.
pcTotalAvail - не используется.

Объявление функции для Windows NT:

var
  NetShareEnum: function(ServerName: PWChar;
    Level: DWORD;
    Bufptr: Pointer;
    Prefmaxlen: DWORD;
    EntriesRead,
    TotalEntries,
    resume_handle: LPDWORD): DWORD; stdcall;

Параметры:
ServerName - должен содержать имя удаленного компьютера на котором должна выполнится функция, если выполняем у себя то данному параметру можно присвоить NIL.
Level - должен содержать идентификатор структуры.
Bufptr - должен содержать адрес указателя на массив структур.
Prefmaxlen - должен содержать максимальную длину возвращенных данных в байтах, если не ставить ограничение то данному параметру нужно присвоить DWORD(-1)
EntriesRead - должен содержать указатель на переменную в которую запишется количество общих ресурсов доступных на данный момент.
TotalEntries - не используется
Resume_handle - не используется, должен быть NIL

В случае успешного выполнения результат обеих функций равен нулю. Обратите внимание, на то, что функция использующаяся в Windows 9x-Me получает именно указатель на массив структур, в то время как другая функция получает адрес указателя, это критично!!!

Результаты выполнения будут сохранены в массиве структур переданных функции при ее вызове. Существует 6 типов структур передаваемых функции NetShareEnum

  • SHARE_INFO_0 - только Windows NT
  • SHARE_INFO_1 - только Windows NT
  • share_info_1 - только Windows 9х-Ме
  • SHARE_INFO_2 - только Windows NT
  • share_info_50 - только Windows 9х-Ме
  • SHARE_INFO_502 - только Windows NT

Я остановлюсь на двух из них (по одной для каждого типа Windows :)

Структура share_info_50:

Объявление структуры:

type
  TShareInfo50 = packed record
    shi50_netname: array[0..12] of Char;
    shi50_type: Byte;
    shi50_flags: Word;
    shi50_remark: PChar;
    shi50_path: PChar;
    shi50_rw_password: array[0..8] of Char;
    shi50_ro_password: array[0..8] of Char;
  end;

Поля:
shi50_netname - содержит строку содержащую сетевое имя ресурса
shi50_type - определяет тип ресурса (подробнее в MSDN)
shi50_flags - содержит информацию о правах доступа к ресурсу
shi50_remark - указатель на строку содержащую необязательный комментарий к ресурсу
shi50_path - содержит локальное расположение ресурса
shi50_rw_password - содержит пароль на запись - чтение
shi50_ro_password - содержит пароль на чтение

Реально получить значение двух последних полей можно только при получении информации о своём компьютере, в остальных случаях они остаются пустыми.

Структура SHARE_INFO_2:

Объявление структуры:

type
  TShareInfo2 = packed record
    shi2_netname: PWChar;
    shi2_type: DWORD;
    shi2_remark: PWChar;
    shi2_permissions: DWORD;
    shi2_max_uses: DWORD;
    shi2_current_uses: DWORD;
    shi2_path: PWChar;
    shi2_passwd: PWChar;
  end;
  PShareInfo2 = ^TShareInfo2;
  TShareInfo2Array = array[0..512] of TShareInfo2;
  PShareInfo2Array = ^TShareInfo2Array;

Поля:
shi2_netname - Содержит указатель на строку содержащую имя ресурса
shi2_type - определяет тип ресурса (подробнее в MSDN)
shi2_remark - указатель на строку содержащую необязательный комментарий к ресурсу
shi2_permissions - содержит информацию о правах доступа к ресурсу
shi2_max_uses - определяет максимальное кол-во подключений к ресурсу
shi2_current_uses - определяет кол-во текущих подключений
shi2_path - содержит указатель на строку содержащую локальное расположение ресурса
shi2_passwd - содержит указатель на строку содержащую пароль

Итак, теперь у вас есть вся информация для написания первой программы. Создадим новый проект. Добавим на форму два элемента - ListBox, назовем его lbxShares и Button, назовем ее btnGetShares. В интерфейсную часть модуля добавим описания структур и функций, так, как они были даны выше. Единственное отличие функцию для NT мы назовем NetShareEnumNT, для того чтобы не было двух одинаковых функций с разными параметрами. У вас должно получится что-то вроде этого:

unit Main;

interface

uses...;

type
  TMainForm = class(TForm)
    lbxShares: TListBox;
    btnGetShares: TButton;
  private
    { Private declarations }
  public
    { Public declarations }
  end;

type
  TShareInfo50 = packed record
    ...
  end;

type
  TShareInfo2 = packed record
    ...
  end;
  PShareInfo2 = ...

var
  NetShareEnumNT: function (...): DWORD; stdcall;
  NetShareEnum: function (...): DWORD; stdcall;

  var
    MainForm: TMainForm;

implementation

{$R *.dfm}

end.

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

function TMainForm.IsNT(var Value: Boolean): Boolean;
var
  Ver: TOSVersionInfo;
  BRes: Boolean;
begin
  Ver.dwOSVersionInfoSize := SizeOf(TOSVersionInfo);
  BRes := GetVersionEx(Ver);
  if not BRes then
  begin
    Result := False;
    Exit;
  end
  else
    Result := True;
  case Ver.dwPlatformId of
    VER_PLATFORM_WIN32_NT: Value := True; //Windows NT - подходит
    VER_PLATFORM_WIN32_WINDOWS: Value := False; //Windows 9x-Me - подходит
    VER_PLATFORM_WIN32s: Result := False; //Windows 3.x - не подходит
  end;
end;

Если данная функция вернула результат True, значит, определение версии системы прошло успешно, и тип системы будет храниться в переменной Value (True - значит NT, False - 9x, Me), в противном случае определение типа системы неудачно или система является Windows 3.x, в этом случае придется завершить выполнение программы. Данная функция будет у нас практически самой главной, так как она будет указывать нашей программе какую часть кода исполнять. Теперь обратите внимание, наши функции только объявлены, но за ними не закреплено никакого исполняемого кода. Если бы мы использовали статическое связывание и заранее определили библиотеки и имена библиотечных функций, гарантированно получили бы в процессе выполнения ошибку, так как библиотека SVRAPI отсутствует в Windows NT, а NETAPI32 отсутствует в Windows 9x-Me. Для этого мы должны после определения типа системы загрузить требуемую библиотеку, и уже только после этого связать наши функции с библиотечными.

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

procedure TMainForm.btnGetSharesClick(Sender: TObject);
var
  i: Integer;
  FLibHandle: THandle;
  ShareNT: PShareInfo2Array; //<= Перемеменные
  entriesread, totalentries: DWORD; //<= для Windows NT
  Share: array[0..512] of TShareInfo50; //<= Переменные
  pcEntriesRead, pcTotalAvail: Word; //<= для Windows 9x-Me
  OS: Boolean;
begin
  lbxShares.Items.Clear;
  if not IsNT(OS) then
    Close; //Определяем тип системы

  if OS then
  begin //Код для NT
    FLibHandle := LoadLibrary('NETAPI32.DLL'); //Загружаем библиотеку
    if FLibHandle = 0 then
      Exit;
    //Связываем функцию
    @NetShareEnumNT := GetProcAddress(FLibHandle, 'NetShareEnum');
    if not Assigned(NetShareEnumNT) then //Проверка
    begin
      FreeLibrary(FLibHandle);
      Exit;
    end;
    ShareNT := nil; //Очищаем указатель на массив структур
    //Вызов функции
    if NetShareEnumNT(nil, 2, @ShareNT, DWORD(-1),
      @entriesread, @totalentries, nil) <> 0 then
    begin //Если вызов неудачен выгружаем библиотеку
      FreeLibrary(FLibHandle);
      Exit;
    end;
    if entriesread > 0 then //Обработка результатов
      for i := 0 to entriesread - 1 do
        lbxShares.Items.Add(string(ShareNT^[i].shi2_netname));

  end
  else
  begin //Код для 9х-Ме
    FLibHandle := LoadLibrary('SVRAPI.DLL'); //Загружаем библиотеку
    if FLibHandle = 0 then
      Exit;
    //Связываем функцию
    @NetShareEnum := GetProcAddress(FLibHandle, 'NetShareEnum');
    if not Assigned(NetShareEnum) then //Проверка
    begin
      FreeLibrary(FLibHandle);
      Exit;
    end;
    if NetShareEnum(nil, 50, @Share, SizeOf(Share),
      @pcEntriesRead, @pcTotalAvail) <> 0 then //Вызов функции
    begin //Если вызов неудачен выгружаем библиотеку
      FreeLibrary(FLibHandle);
      Exit;
    end;
    if pcEntriesRead > 0 then //Обработка результатов
      for i := 0 to pcEntriesRead - 1 do
        lbxShares.Items.Add(string(Share[i].shi50_netname));
  end;
  FreeLibrary(FLibHandle); //Не забываем выгрузить библиотеку
end;

При выполнении этого кода ListBox заполнится названиями общих ресурсов, которые берутся из массива структур переданных функции. Как вы поняли, массив заполняется в результате выполнения функции. Немного модифицировав код (см. описание структур) вы получите комментарии к ресурсу, пароли, пути к папкам общих ресурсов и т.д. Вы можете заметить, при возникновении ошибок, я просто завершал работу процедуры оператором Exit. Это сделано только для того, чтобы не загромождать код обработчиками, в реальной программе такое, естественно, не допустимо.

Примечание: обычно в NT используются функции NetApiBufferAllocate и NetApiBufferFree - при помощи них выделяется и освобождается память под массив структур. Я вам показал немного другое объявление структуры TShareInfo2Array = array [0..512] of TShareInfo2; Здесь память уже выделена. Я предпочитаю пользоваться именно таким объявлением структуры, поэтому эти функции в данной статье рассмотрены не будут. При желании вы можете посмотреть в MSDN принцип их использования.


Закрытие локального ресурса

Получать список общих ресурсов вы научились. Теперь рассмотрим функцию NetShareDel которая позволит нам закрыть выбранный общий ресурс.

Обьявление функции для 9х - Ме Windows:

var
  NetShareDel: function(pszServer,
    pszNetName: PChar;
    usReserved: Word): DWORD; stdcall;

Параметры: ServerName - должен содержать имя удаленного компьютера, если закрываем свои ресурсы то данному параметру нужно присвоить NIL.
NetName - указатель на строку содержащую имя закрываемого ресурса
Reserved - не используется, должен быть равен нулю

Как вы заметили, обе функции не используют никаких структур. Нас интересует только второй параметр, содержащий имя закрываемого ресурса (подробнее по остальным параметрам см. MSDN). В качестве имени передается не путь к ресурсу, а именно имя ресурса которое мы определили при помощи кода данного выше. В случае успешного выполнения функций, их результат будет равен нулю. Итак, добавим к нашей программе еще одну кнопку и назовем ее btnCloseShares. Обработчик этой кнопки будет содержать код, определяющий текущий выбранный элемент в списке текущих общих ресурсов, и будет передавать его имя (Strings[xx]) в качестве второго параметра функции. (Не забудьте добавить объявления функций в интерфейсную часть программы, и как и в прошлый раз функция для NT будет иметь название NetShareDelNT(…) )

procedure TMainForm.btnCloseSharesClick(Sender: TObject);
var
  OS: Boolean;
  FLibHandle: THandle;
  Name9x: array[0..12] of Char;
  NameNT: PWChar;
  i: Integer;
  ShareName: string;
begin
  if not IsNT(OS) then
    Close; //Определяем тип системы

  if lbxShares.Items.Count = 0 then
    Exit;
  for i := 0 to lbxShares.Items.Count - 1 do
    if lbxShares.Selected[i] then
      Break; //Ищем выбранный элемент
  if not lbxShares.Selected[i] then
    Exit; //Если не найден уходим
  ShareName := lbxShares.Items.Strings[i];

  if OS then
  begin //Код для NT
    FLibHandle := LoadLibrary('NETAPI32.DLL');
    if FLibHandle = 0 then
      Exit;
    @NetShareDelNT := GetProcAddress(FLibHandle, 'NetShareDel');
    if not Assigned(NetShareDelNT) then //Проверка
    begin
      FreeLibrary(FLibHandle);
      Exit;
    end;
    i := SizeOf(WideChar) * 256;
    GetMem(NameNT, i); //Выделяем память под переменную
    StringToWideChar(ShareName, NameNT, i); //Преобразуем в PWideChar
    NetShareDelNT(nil, NameNT, 0); //Удаляем ресурс
    FreeMem(NameNT); //Освобождаем память
  end
  else
  begin //Код для 9х-Ме
    FLibHandle := LoadLibrary('SVRAPI.DLL');
    if FLibHandle = 0 then
      Exit;
    @NetShareDel := GetProcAddress(FLibHandle, 'NetShareDel');
    if not Assigned(NetShareDel) then //Проверка
    begin
      FreeLibrary(FLibHandle);
      Exit;
    end;
    FillChar(Name9x, SizeOf(Name9x), #0); //Очищаем массив
    move(ShareName[1], Name9x[0], Length(ShareName)); //Заполняем массив
    NetShareDel(nil, @Name9x, 0); //Удаляем ресурс
  end;
  FreeLibrary(FLibHandle);
end;

Заметьте, что я опять не делаю никаких проверок на результат выполнения функции, как я объяснял выше, во первых, чтобы не загромождать демонстрационный код, а во вторых - в данной процедуре не используются результаты выполнения функций (они не критичны), но в реальной программе вы обязаны обработать результаты и в случае неуспешного выполнения (результат не равен нулю) уведомить об этом пользователя. Обратите внимание, что в версии кода для Windows 9x-Me я преобразую имя ресурса ShareName в массив Char и передаю указатель на него. Это обязательное действие, так как PСhar(ShareName) попросту не сработает. Подробнее о том почему выбран именно такой размер массива смотрите в MSDN


Открытие локального ресурса

Теперь нам следует научится открывать общий ресурс. Для этого воспользуемся функцией NetShareAdd

Объявление функции для 9х - Ме Windows:

var
  NetShareAdd: function(pszServer: Pchar;
    SLevel: Cardinal;
    PbBuffer: PChar;
    CbBuffer: Word): DWORD; stdcall;

Параметры:
pszServer - должен содержать имя удаленного компьютера на котором должна выполнится функция, если открываем локальный ресурс то данному параметру нужно присвоить NIL.
sLevel - должен содержать идентификатор структуры.
pbBuffer - должен содержать указатель на структуру.
cbBuffer - должен содержать размер структуры.

Обьявление функции для Windows NT:

var
  NetShareAdd: function(servername: PWideChar;
    level: DWORD;
    buf: Pointer;
    parm_err: LPDWORD): DWORD; stdcall;

Параметры:
servername - должен содержать имя удаленного компьютера на котором должна выполнится функция, если открываем локальный ресурс то данному параметру нужно присвоить NIL.
level - должен содержать идентификатор структуры.
buf - должен содержать указатель на структуру.
parm_err - содержит указатель члена структуры вызывающий ошибку

Заметьте что в функции для NT также используется указатель а не адрес указателя. Обе функции используют структуры описанные выше. Для демонстрации их возможностей воспользуемся уже разобранными нами структурами share_info_50 и SHARE_INFO_502. Параметр parm_err в рамках этой статьи я разбирать не буду, подробности в MSDN, можете присвоить ему nil. Итак, добавьте объявления функций в интерфейсную часть модуля (не забудьте про переименование процедуры для NT), добавьте еще одну кнопку на форму и измените ее имя на btnAddShares. В обработчик OnClick этой кнопки мы поместим код позволяющий нам выбрать папку, общий доступ к которой мы хотим открыть. Для этого нам нужно написать еще одну небольшую функцию, показывающую диалог выбора папки. Вот она:

function TMainForm.SelectDirectory: string;
var
  lpItemID: PItemIDList;
  BrowseInfo: TBrowseInfo;
  DisplayName: array[0..MAX_PATH] of Char;
  TempPath: array[0..MAX_PATH] of Char;
begin
  FillChar(BrowseInfo, sizeof(TBrowseInfo), #0);
  BrowseInfo.hwndOwner := Handle;
  BrowseInfo.pszDisplayName := @DisplayName;
  BrowseInfo.lpszTitle := 'Specify a directory';
  BrowseInfo.ulFlags := BIF_RETURNONLYFSDIRS;
  lpItemID := SHBrowseForFolder(BrowseInfo);
  if Assigned(lpItemId) then
  begin
    SHGetPathFromIDList(lpItemID, TempPath);
    GlobalFreePtr(lpItemID);
  end
  else
    Result := '';
  Result := string(TempPath);
end;

Для выполнения данной функции добавьте в раздел uses модули
  ShellAPI и ShlObj.Вот сам код открытия ресурса: procedure
    TMainForm.btnAddSharesClick(Sender: TObject);
const
  STYPE_DISKTREE = 0;
  ACCESS_ALL = 258;
  SHI50F_FULL = 258;
var
  FLibHandle: THandle;
  Share9x: TShareInfo50;
  ShareNT: TShareInfo2;
  TmpDir, TmpName: string;
  TmpDirNT, TmpNameNT: PWChar;
  OS: Boolean;
  TmpLength: Integer;
begin
  TmpDir := SelectDirectory; //Определяем путь к будущему ресурсу
  TmpName := InputBox('Share name', 'Enter name', 'Test');
    //Определяем имя под которым он будет виден в сети
  if TmpDir = '' then
    Exit;

  if not IsNT(OS) then
    Close; //Выясняем тип системы

  if OS then
  begin //Код для NT
    FLibHandle := LoadLibrary('NETAPI32.DLL');
    if FLibHandle = 0 then
      Exit;
    @NetShareAddNT := GetProcAddress(FLibHandle, 'NetShareAdd');
    if not Assigned(NetShareAddNT) then
    begin
      FreeLibrary(FLibHandle);
      Exit;
    end;
    TmpLength := SizeOF(WideChar) * 256; //Определяем необходимый размер

    GetMem(TmpNameNT, TmpLength); //Конвертируем в PWChar
    StringToWideChar(TmpName, TmpNameNT, TmpLength);
    ShareNT.shi2_netname := TmpNameNT; //Имя

    ShareNT.shi2_type := STYPE_DISKTREE; //Тип ресурса
    ShareNT.shi2_remark := ''; //Комментарий
    ShareNT.shi2_permissions := ACCESS_READ; //Доступ
    ShareNT.shi2_max_uses := DWORD(-1); //Кол-во максим. подключ.
    ShareNT.shi2_current_uses := 0; //Кол-во тек подкл.

    GetMem(TmpDirNT, TmpLength);
    StringToWideChar(TmpDir, TmpDirNT, TmpLength);
    ShareNT.shi2_path := TmpDirNT; //Путь к ресурсу

    ShareNT.shi2_passwd := nil; //Пароль

    NetShareAddNT(nil, 2, @ShareNT, nil); //Добавляем ресурс
    FreeMem(TmpNameNT); //освобождаем память
    FreeMem(TmpDirNT);
  end
  else
  begin //Код для 9x
    FLibHandle := LoadLibrary('SVRAPI.DLL');
    if FLibHandle = 0 then
      Exit;
    @NetShareAdd := GetProcAddress(FLibHandle, 'NetShareAdd');
    if not Assigned(NetShareAdd) then
    begin
      FreeLibrary(FLibHandle);
      Close;
    end;
    FillChar(Share9x.shi50_netname, SizeOf(Share9x.shi50_netname), #0);
    move(TmpName[1], Share9x.shi50_netname[0], Length(TmpName)); //Имя
    Share9x.shi50_type := STYPE_DISKTREE; //Тип ресурса
    Share9x.shi50_flags := SHI50F_RDONLY; //Доступ
    FillChar(Share9x.shi50_remark,
      SizeOf(Share9x.shi50_remark), #0); //Комментарий
    FillChar(Share9x.shi50_path,
      SizeOf(Share9x.shi50_path), #0);
    Share9x.shi50_path := PAnsiChar(TmpDir); //Путь к ресурсу
    FillChar(Share9x.shi50_rw_password,
      SizeOf(Share9x.shi50_rw_password), #0); //Пароль полного доступа
    FillChar(Share9x.shi50_ro_password,
      SizeOf(Share9x.shi50_ro_password), #0); //Пароль для чтения
    NetShareAdd(nil, 50, @Share9x, SizeOf(Share9x));
  end;
  FreeLibrary(FLibHandle);
end;

Обратите внимание на то, что переменные TmpNameNT и TmpDirNT освобождаются только после выполнения функции. Это критично, в противном случае структура переданная функции будет с двумя "незаполненными" полями. Также обратите внимание на то что в версии кода для Windows 9х все поля - представляющие собой массив Char элементов, вначале очищаются функцией FillChar, во избежание искажения данных (знак окончания строки равен #0). Данный код открывает новый ресурс на полный доступ. Рассматривать все виды открытия в данной статье я не имею возможности, за подробностями опять же отсылаю к MSDN.


Скрытие и показ ресурсов

Эта глава будет маленькая, никакого кода приводить не буду. Только объясню как можно добиться скрытия и показа ресурсов. Вы наверное уже обратили внимание на ресурсы в имени которых в конце стоит знак доллара. Это так называемые системные ресурсы. Манипулировать с ними я крайне не советую. Объясню почему. Во первых, ко всем этим ресурсам стоит полный доступ. Во вторых, при закрытии некоторых из системных ресурсов доступ к вашему компьютеру по сети может исчезнуть. В третьих, однажды я проэксперементировал с активацией и скрытием над системными ресурсами, после этого возникли неожиданные осложнения. Я попытался сделать активным ресурс "C$" в Windows XP, после этого я его, естественно, скрыл и перезагрузил систему. Каково же было мое удивление, когда я увидел что у меня появился ресурс "C" ( "C$" тоже присутствовал) дающий полный доступ к моему системному диску. Повторное его закрытие ничего не дало, ресурс снова появлялся после каждой перезагрузки системы. Проблема решилась тотальной правкой реестра (ключей уже к сожалению не помню). И это еще не самое страшное что может произойти. Как вы уже поняли скрыть ресурс можно простым добавлением к его имени знака доллара. Активировать, обратной операцией. Это естественно не правильно, но работает. Второй вариант скрыть ресурс, это удалить его и создать копию его но с правами SHI50F_SYSTEM или указанием shi502_security_descriptor (для этого нужно использовать структуру SHARE_INFO_502).
Не проводите манипуляции над следующими ресурсами:
IPC$, ADMIN$, PRINT$, WWWROOT$, BIOSINFO$, A$, B$, C$, D$, E$, F$, G$, H$, I$, J$, K$, L$, M$, N$, O$, P$, Q$, R$,S$, T$, U$, V$, W$, X$, Y$, Z$

У вас может возникнуть вопрос: "Для чего скрывать ресурсы?" Объясню, в моей домашней локальной сети ресурсы скрываются для того чтобы о них никто кроме нескольких избранных не знал. К примеру папки с исходниками, которые остальным "пользователям - не программистам" ни к чему. Некоторые таким образом прячут "экстремальные" фильмы ;) от детей своих "друзей - сетевиков".


Получение списка текущих сессий

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

Объявление функции для Windows 9х - Ме:

var
  NetSessionEnum: function(pszServer: PChar;
    sLevel: DWORD;
    pbBuffer: Pointer;
    cbBuffer: DWORD;
    pcEntriesRead,
    pcTotalAvial: Pointer): integer; stdcall;

Параметры:
pszServer - должен содержать имя удаленного компьютера на котором должна выполнится функция, если смотрим у себя то данному параметру нужно присвоить NIL.
sLevel - должен содержать идентификатор структуры.
pbBuffer - должен содержать указатель на массив структур.
cbBuffer - должен содержать размер массива структур.
pcEntriesRead - указатель на переменную содержащую общее кол-во структур
pcTotalAvial - не используется

Объявление функции для Windows NT:

var
  NetSessionEnum: function(ServerName,
    UncClientName,
    Username: PWChar;
    Level: DWORD;
    bufptr: Pointer;
    prefmaxlen: DWORD;
    entriesread,
    totalentries,
    resume_handle: LPDWORD): DWORD; stdcall;

Параметры:
ServerName - должен содержать имя удаленного компьютера на котором должна выполнится функция, если смотрим у себя то данному параметру нужно присвоить NIL.
UncClientName - содержит указатель на строку содержащую имя сессии о которой мы хотим получить информацию, если нужно просмотреть все сессии параметру нужно присвоить NIL.
UserName - содержит указатель на строку содержащую имя пользователя о котором мы хотим получить информацию, если нужно просмотреть всё, параметру нужно присвоить NIL.
Level - должен содержать идентификатор структуры.
Bufptr - должен содержать адрес указателя на массив структур.
Prefmaxlen - должен содержать максимальную длину возвращенных данных в байтах, если не ставить ограничение то данному параметру нужно присвоить DWORD(-1)
Entriesread - должен содержать указатель на переменную в которую запишется количество общих ресурсов доступных на данный момент.
Totalentries - не используется
Resume_handle - не используется, должен быть NIL

Существует также 6 типов структур передаваемых функции NetSessionEnum.
SESSION_INFO_0 - только Windows NT
SESSION_INFO_1 - только Windows NT
SESSION_INFO_2 - только Windows NT
SESSION_INFO_10 - только Windows NT
SESSION_INFO_502 - только Windows NT
session_info_50 - только Windows 9x-Me

Я опять рассмотрю только две из них как наиболее полные по своей сути.

Структура session_info_50:

Описание структуры:

type
  TSessionInfo50 = packed record
    sesi50_cname: PChar;
    sesi50_username: PChar;
    sesi50_key: Cardinal;
    sesi50_num_conns: Word;
    sesi50_num_opens: Word;
    sesi50_time: Cardinal;
    sesi50_idle_time: Cardinal;
    sesi50_protocol: Byte;
    pad1: Byte;
  end;

Поля:
sesi50_cname - Содержит указатель на строку содержащую имя компьютера установившего сессию
sesi50_username - Содержит указатель на строку содержащую имя пользователя установившего сессию
sesi50_key - содержит значение при помощи которого мы будем завершать сессию
sesi50_num_conns - содержит число подключений сделанных в течении сессии
sesi50_num_opens - содержит кол-во файлов открытых в течении сессии
sesi50_time - содержит время в секундах в течение которого сессия была активна
sesi50_idle_time - содержит время в секундах в течение которого сессия была неактивна
sesi50_protocol - содержит имя протокола, при помощи которого клиент связывается с сервером
Pad1 - небольшой выравниватель структуры, не используется

Структура SESSION_INFO_502

Описание структуры:

type
  TSessionInfo502 = packed record
    Sesi502_cname: PWideChar;
    Sesi502_username: PWideChar;
    Sesi502_num_opens: DWORD;
    Sesi502_time: DWORD;
    Sesi502_idle_time: DWORD;
    Sesi502_user_flags: DWORD;
    Sesi502_cltype_name: PWideChar;
    Sesi502_transport: PWideChar;
  end;
  PSessionInfo502 = ^TSessionInfo502;
  TSessionInfo502Array = array[0..512] of TSessionInfo502;
  PSessionInfo502Array = ^TSessionInfo502Array;

Поля:
sesi502_cname - Содержит указатель на строку содержащую имя компьютера установившего сессию
sesi502_username - Содержит указатель на строку содержащую имя пользователя установившего сессию
sesi502_num_opens - содержит кол-во файлов открытых в течении сессии
sesi502_time - содержит время в секундах в течение которого сессия была активна
sesi502_idle_time - содержит время в секундах в течение которого сессия была неактивна
sesi50_user_flags - значение описывающее как пользователь установил сессию (см. MSDN)
sesi502_cltype_name - тип клиента установившего сессию, не используется
sesi502_transport - содержит имя протокола, при помощи которого клиент связывается с сервером

Немного объясню значение полей time и idle_time. Что означает когда сессия не активна? Представьте, вы к примеру открыли какой-то ресурс на удаленной машине, просто посмотреть, что в нем находится (к примеру имена файлов). В то время когда вы ничего не делаете, не копируете, не запускаете, не открываете файлы, сессия не активна, она ждет ваших действий. Как только вы начинаете копировать файл (опять же к примеру) с удаленной машины, сессия становится активной до окончания копирования.

Обратите внимание на поле key структуры TSessionInfo50, оно содержит уникальный идентификатор при помощи которого мы сможем завершить сессию. MSDN советует сохранить значение этого поля во временной переменной, что мы и сделаем. Создадим в разделе public массив SessionCloseKey: array [0..512] of SmallInt; В нем мы будем хранить все ключи для закрытия сессий в Windows 9x-Me. Тут очень интересный момент. Если вы посмотрите на тип key то заметите что он является Cardinal (unsigned long), а массив для хранения ключей я сделал с типом SmallInt (Short). Дело в том, что в функции NetSessionDel, которую мы рассмотрим чуть позже, значение, в которое мы должны будем передать ключ для закрытия сессии, имеет тип SmallInt (Short). Я так и не понял почему это так, либо недосмотр программистов Microsoft, либо это они просто так шутят ;).

Теперь добавим на форму ListView и назовем его lwSessions. Перейдем в режим редактирования и создадим пять колонок со следующими значениями Caption - Cname, UserName, Num_Opens, Time, Idle_Time. Соответственно вы поняли что в них будут отображаться имя компьютера, имя пользователя, кол-во открытых файлов, активное и неактивное время полученных нами сессий. Заметьте что активное и неактивное время сессий будет даваться нам в виде кол-ва секунд (тип Cardinal). Предлагаю написать небольшую функцию, задача которой будет преобразовывать кол-во секунд в более привычную форму отображения.

function TMainForm.CardinalToTimeStr(Value: Cardinal): string;
var
  d, h, m, s: Real;
begin
  d := 0;
  h := 0;
  m := 0;
  s := Value;
  if s > 59 then
  begin
    m := int(s / 60);
    s := s - (m * 60);
  end;
  if m > 59 then
  begin
    h := int(m / 60);
    m := m - (h * 60);
  end;
  if h > 23 then
  begin
    d := int(h / 24);
    h := h - (d * 24);
  end;
  Result := '';
  if (d > 0) then
    Result := Result + floattostr(d) + ' d. ';
  if (h < 9) then
    Result := Result + '0' + floattostr(h) + ':'
  else
    Result := Result + floattostr(h) + ':';
  if (m < 9) then
    Result := Result + '0' + floattostr(m) + ':'
  else
    Result := Result + floattostr(m) + ':';
  if (s < 9) then
    Result := Result + '0' + floattostr(s)
  else
    Result := Result + floattostr(s);
end;

Теперь добавим на форму кнопку и назовем ее btnGetSessions. Не забудьте добавить объявления функций и структур (про переименование функции для NT помните?). Вот сам код получения текущих сессий:

procedure TMainForm.btnGetSessionsClick(Sender: TObject);
var
  OS: Boolean;
  FLibHandle: THandle;
  SessionInfo50: array[0..512] of TSessionInfo50;
  SessionInfo502: PSessionInfo502Array;
  TotalEntries, EntriesReadNT: DWORD;
  EntriesRead, TotalAvial: Word;
  i: integer;
begin
  lvSessions.Items.Clear;

  if not IsNT(OS) then
    Close; //Выясняем тип системы

  if OS then
  begin //Код для NT
    FLibHandle := LoadLibrary('NETAPI32.DLL');
    if FLibHandle = 0 then
      Exit;
    @NetSessionEnumNT := GetProcAddress(FLibHandle, 'NetSessionEnum');
    if not Assigned(NetSessionEnumNT) then
    begin
      FreeLibrary(FLibHandle);
      Exit;
    end;
    SessionInfo502 := nil;
    if NetSessionEnumNT(nil, nil, nil, 502, @SessionInfo502, DWORD(-1),
      @entriesreadNT, @totalentries, nil) = 0 then
      for i := 0 to EntriesReadNT - 1 do
      begin
        with lvSessions.Items.Add do //Заполнение данными из структуры
        begin
          Caption := string(SessionInfo502^[i].sesi502_cname); //Имя компьютера
          SubItems.Add(SessionInfo502^[i].sesi502_username); //Имя пользователя
          SubItems.Add(IntToStr(SessionInfo502^[i].sesi502_num_opens));
            //Открытых ресурсов
          SubItems.Add(CardinalToTimeStr(SessionInfo502^[i].Sesi502_Time));
            //Время активное
          SubItems.Add(CardinalToTimeStr(SessionInfo502^[i].sesi502_idle_time));
            //Время не активное
        end;
      end;
  end
  else
  begin //Код для Windows 9x-Me
    FLibHandle := LoadLibrary('SVRAPI.DLL');
    if FLibHandle 0 then
      Exit;
    @NetSessionEnum := GetProcAddress(FLibHandle, 'NetSessionEnum');
    if not Assigned(NetSessionEnum) then
    begin
      FreeLibrary(FLibHandle);
      Exit;
    end;
    if NetSessionEnum(nil, 50, @SessionInfo50, SizeOf(SessionInfo50),
      @EntriesRead, @TotalAvial) = 0 then
      for i := 0 to EntriesRead - 1 do
      begin
        with lvSessions.Items.Add do //Заполнение данными из структуры
        begin
          Caption := string(SessionInfo50[i].Sesi50_cname); //Имя компьютера
          SubItems.Add(SessionInfo50[i].Sesi50_username); //Имя пользователя
          SubItems.Add(IntToStr(SessionInfo50[i].sesi50_num_opens));
            //Открытых ресурсов
          SubItems.Add(CardinalToTimeStr(SessionInfo50[i].Sesi50_Time));
            //Время активное
          SubItems.Add(CardinalToTimeStr(SessionInfo50[i].sesi50_idle_time));
            //Время не активное
          SessionCloseKey[i] := SessionInfo50[i].sesi50_key;
            //Уникальный идентификатор для закрытия
        end;
      end;
  end;
  FreeLibrary(FLibHandle);
end;
Обратите внимание на то, как я сохраняю ключи для закрытия сессий. Я их просто заношу в массив в том порядке, в каком были получены сами сессии. Это сделано для простоты. Если в списке, отображающем наши сессии, не применять сортировки, то порядковый номер выделенной для закрытия сессии и ее идентификатор в массиве совпадут.

Завершение сессий

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

Объявление функции для 9х - Ме Windows:
var
  NetSessionDel: function(pszServer: PChar;
    PszClientName: PChar;
    SReserved: SmallInt): DWORD; stdcall;

Параметры:
pszServer - должен содержать имя удаленного компьютера на котором должна выполнится функция, если завершается сессия у себя то данному параметру нужно присвоить NIL.
pszClientName - должен содержать имя клиента чья сессия завершается
sReserved - должен содержать уникальный ключ для завершения сессии (тот который мы получили предыдущей функцией)

Объявление функции для Windows NT:

var
  NetSessionDel: function(ServerName,
    UncClientName,
    Username: PWChar): DWORD; stdcall;

Параметры:
ServerName - должен содержать имя удаленного компьютера на котором должна выполнится функция, если завершается сессия у себя то данному параметру нужно присвоить NIL.
uncClientName - должен содержать имя клиента чья сессия завершается, если параметр NIL, завершатся все сессии указанные в параметре username
username - должен содержать имя пользователя чья сессия завершается, если параметр NIL, завершатся все сессии указанные в параметре uncClientName

Как вы можете заметить, никаких структур данные функции не используют. Напишем процедуру которая будет завершать выбранную нами в lvSessions сессию по имени клиента. Для этого поместите на форму еще одну кнопку и назовите ее btnCloseSession. При нажатии на эту кнопку мы будем искать выделенную сессию и брать значение находящееся в колонке CName, к полученному нами значению прибавим '\\' (в варианте для NT, в варианте для 9x-Me оставим как есть) и передадим в качестве второго параметра функции. В качестве третьего параметра в версии кода для NT передадим NIL, а в версии кода для Windows 9x-Me тот самый уникальный ключ, сохраненный нами ранее в массиве. Он будет находится по номеру соответствующему порядковому номеру сессии в lvSessions (если вы конечно не применяли сортировку). Вот как этот код выглядит:

procedure TMainForm.btnCloseSessionClick(Sender: TObject);
var
  OS: Boolean;
  FLibHandle: THandle;
  CNameNT: PWideChar;
  CName9x: PAnsiChar;
  Key: SmallInt;
  i: Integer;
begin
  if not IsNT(OS) then
    Close; //Выясняем тип системы

  if not Assigned(lvSessions.Selected) then
    Exit;
  i := lvSessions.Selected.Index; //Определяем номер выбранной сессии

  if OS then
  begin
    FLibHandle := LoadLibrary('NETAPI32.DLL');
    if FLibHandle = 0 then
      Exit;
    @NetSessionDelNT := GetProcAddress(FLibHandle, 'NetSessionDel');
    if not Assigned(NetSessionDelNT) then
    begin
      FreeLibrary(FLibHandle);
      Exit;
    end;
    //Преобразуем данные в требуемый вид
    CNameNT := PWChar(WideString('\\' + lvSessions.Items.Item[i].Caption));
    NetSessionDelNT(nil, CNameNT, nil);
  end
  else
  begin
    FLibHandle := LoadLibrary('SVRAPI.DLL');
    if FLibHandle = 0 then
      Exit;
    @NetSessionDel := GetProcAddress(FLibHandle, 'NetSessionDel');
    if not Assigned(NetSessionDel) then
    begin
      FreeLibrary(FLibHandle);
      Exit;
    end;
    //Преобразуем данные в требуемый вид
    CName9x := PAnsiChar(lvSessions.Items.Item[i].Caption);
    key := SessionCloseKey[i]; //Берем ключ из массива
    NetSessionDel(nil, CName9x, Key);
  end;
  FreeLibrary(FLibHandle);
end;

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

Теперь я приведу способ получения списка открытых файлов на компьютере. Для этого рассмотрим функцию NetFileEnum.

Объявление функции для 9х - Ме Windows:

var
  NetFileEnum: function(pszServer,
    pszBasePath: PChar;
    sLevel: DWORD;
    pbBuffer: Pointer;
    cbBuffer: DWORD;
    pcEntriesRead,
    pcTotalAvail: Pointer): Integer; stdcall;

Параметры:
pszServer - должен содержать имя удаленного компьютера на котором должна выполнится функция, если смотрим у себя то данному параметру нужно присвоить NIL.
pszBasePath - должен содержать каталог открытые файлы из которого мы хотим посмотреть, если мы хотим просмотреть все открытые файлы параметру нужно присвоить NIL.
sLevel - должен содержать идентификатор структуры.
pbBuffer - должен содержать указатель на массив структур.
cbBuffer - должен содержать размер структуры.
pcEntriesRead - указатель на переменную содержащую общее кол-во структур
pcTotalAvial - не используется

Объявление функции для Windows NT:

var
  NetFileEnum: function(servername,
    basepath,
    username: PWChar;
    level: DWORD;
    bufptr: Pointer;
    prefmaxlen: DWORD;
    entriesread,
    totalentries,
    resume_handle: LPDWORD): DWORD; stdcall;

Параметры:
ServerName - должен содержать имя удаленного компьютера на котором должна выполнится функция, если смотрим у себя то данному параметру нужно присвоить NIL.
BasePath - должен содержать каталог открытые файлы из которого мы хотим посмотреть, если мы хотим просмотреть все открытые файлы параметру нужно присвоить NIL.
UserName - содержит указатель на строку содержащую имя пользователя о котором мы хотим получить информацию, если нужно просмотреть всё, параметру нужно присвоить NIL.
Level - должен содержать идентификатор структуры.
Bufptr - должен содержать адрес указателя на массив структур.
Prefmaxlen - должен содержать максимальную длину возвращенных данных в байтах, если не ставить ограничение то данному параметру нужно присвоить DWORD(-1)
Entriesread - должен содержать указатель на переменную в которую запишется количество общих ресурсов доступных на данный момент.
Totalentries - не используется
Resume_handle - не используется, должен быть NIL

Существует три типа структур использующихся функцией:
FILE_INFO_2 - используется под Windows NT
FILE_INFO_3 - используется под Windows NT
file_info_50 - используется под Windows 9x-Me

Рассмотрим две из них:

Структура file_info_50:

Описание структуры:

type
  TFileInfo50 = packed record
    fi50_id: Cardinal;
    fi50_permissions: WORD;
    fi50_num_locks: WORD;
    fi50_pathname: PChar;
    fi50_username: PChar;
    fi50_sharename: PChar;
  end;

Поля:
Fi50_id - Содержит идентификационный номер открытого файла
Fi50_permissions - Содержит уровень доступа с которым открыт файл (см. MSDN)
Fi50_num_locks - содержит кол-во блокировок файла (см. MSDN)
Fi50_pathname - содержит полный путь к открытому файлу
Fi50_username - содержит имя пользователя или компьютера открывшего файл (см. MSDN)
Fi50_sharename - содержит имя общего ресурса в котором находится открытый файл

Структура FILE_INFO_3

Описание структуры:

type
  TFileInfo3 = packed record
    fi3_id: DWORD;
    fi3_permissions: DWORD;
    fi3_num_locks: DWORD;
    fi3_pathname: PWChar;
    fi3_username: PWChar;
  end;
  PFileInfo3 = ^TFileInfo3;
  TFileInfo3Array = array[0..512] of TFileInfo3;
  PFileInfo3Array = ^TFileInfo3Array;

Поля:
Fi3_id - Содержит идентификационный номер открытого файла
Fi3_permissions - Содержит уровень доступа с которым открыт файл (см. MSDN)
Fi3_num_locks - содержит кол-во блокировок файла (см. MSDN)
Fi3_pathname - содержит полный путь к открытому файлу
Fi3_username - содержит имя пользователя или компьютера открывшего файл (см. MSDN)

Не поленитесь и посмотрите в MSDN о всех отмеченых полях, я не буду рассматривать их подробно, так как нам они не понадобятся. Продолжим написание кода. Добавьте на форму ListView с именем lvFiles и создайте на нем три колонки с названиями ID, PathName, UserName. Также добавьте кнопку и назовите ее btnGetFiles. Не забудьте добавить обьявления функций и структуры в интерфейсную часть. Теперь напишем код который покажет нам список открытых файлов на нашем компьютере. Вот он:

procedure TMainForm.btnGetFilesClick(Sender: TObject);
var
  OS: Boolean;
  FLibHandle: THandle;
  FileInfoNT: PFileInfo3Array;
  FileInfo9x: array[0..512] of TFileInfo50;
  TotalEntries, EntriesReadNT: DWORD;
  EntriesRead, TotalAvial: Word;
  i: integer;
begin
  lvfiles.Items.Clear;

  if not IsNT(OS) then
    Close; //Выясняем тип системы

  if OS then
  begin //Код для NT
    FLibHandle := LoadLibrary('NETAPI32.DLL');
    if FLibHandle = 0 then
      Exit;
    @NetFileEnumNT := GetProcAddress(FLibHandle, 'NetFileEnum');
    if not Assigned(NetFileEnumNT) then
    begin
      FreeLibrary(FLibHandle);
      Exit;
    end;
    FileInfoNT := nil;
    if NetFileEnumNT(nil, nil, nil, 3, @FileInfoNT, DWORD(-1), @entriesreadNT,
      @totalentries, nil) = 0 then
      for i := 0 to EntriesReadNT - 1 do
      begin
        with lvFiles.Items.Add do //Заполнение данными из структуры
        begin
          Caption := string(IntToStr(FileInfoNT^[i].fi3_id)); //Идентификатор
          SubItems.Add(FileInfoNT^[i].fi3_pathname); //Путь к файлу
          SubItems.Add(FileInfoNT^[i].fi3_username); //Имя пользователя
        end;
      end;
  end
  else
  begin //Код для Windows 9x-Me
    FLibHandle := LoadLibrary('SVRAPI.DLL');
    if FLibHandle = 0 then
      Exit;
    @NetFileEnum := GetProcAddress(FLibHandle, 'NetFileEnum');
    if not Assigned(NetFileEnum) then
    begin
      FreeLibrary(FLibHandle);
      Exit;
    end;
    if NetFileEnum(nil, nil, 50, @FileInfo9x, SizeOf(FileInfo9x), @EntriesRead,
      @TotalAvial) = 0 then
      for i := 0 to EntriesRead - 1 do
      begin
        with lvFiles.Items.Add do //Заполнение данными из структуры
        begin
          Caption := string(IntToStr(FileInfo9x[i].fi50_id)); //Идентификатор
          SubItems.Add(FileInfo9x[i].fi50_pathname); //Путь к файлу
          SubItems.Add(FileInfo9x[i].fi50_username); //Имя пользователя
        end;
      end;
  end;
  FreeLibrary(FLibHandle);
end;

Без комментариев :)


Закрытие открытого файла

Теперь рассмотрим функции NetFileClose и NetFileClose2, при помощи которых мы будем закрывать открытые по сети файлы.

Функция NetFileClose2 - используется только в Windows 9x-Me

var
  NetFileClose2: function(pszServer: PChar;
    UlFileId: LongWord): DWORD; stdcall;

Поля:
pszServer - должен содержать имя удаленного компьютера, если закрываем у себя то данному параметру нужно присвоить NIL.
UlFileId - должен содержать уникальный идентификатор открытого файла

Функция NetFileClose - используется только в Windows NT

var
  NetFileClose: function(ServerName: PWideChar;
    FileId: DWORD): DWORD; stdcall;

Поля:
ServerName - должен содержать имя удаленного компьютера, если закрываем у себя то данному параметру нужно присвоить NIL.
FileId - должен содержать уникальный идентификатор открытого файла

Как вы видите с этими функциями все предельно просто, в качестве первого параметра передадим NIL (закрывает у себя), в качестве второго, идентификатор файла отображающийся в поле ID. Поместите на форму кнопку, измените ее имя на btnCloseFile, не забудьте добавить объявления функций (для NT переименовывать не надо). Вот пример кода:

procedure TMainForm.btnCloseFileClick(Sender: TObject);
var
  OS: Boolean;
  FLibHandle: THandle;
  i: Integer;
begin
  if not IsNT(OS) then
    Close; //Выясняем тип системы

  if not Assigned(lvFiles.Selected) then
    Exit;
  i := lvFiles.Selected.Index; //Определяем номер выбранного файла

  if OS then
  begin //Код для NT
    FLibHandle := LoadLibrary('NETAPI32.DLL');
    if FLibHandle = 0 then
      Exit;
    @NetFileClose := GetProcAddress(FLibHandle, 'NetFileClose');
    if not Assigned(NetFileClose) then
    begin
      FreeLibrary(FLibHandle);
      Exit;
    end;
    NetFileClose(nil, StrToInt(lvFiles.Items.Item[i].Caption)); //Закрываем
  end
  else
  begin //Код для Windows 9x-Me
    FLibHandle := LoadLibrary('SVRAPI.DLL');
    if FLibHandle = 0 then
      Exit;
    @NetFileClose2 := GetProcAddress(FLibHandle, 'NetFileClose2');
    if not Assigned(NetFileClose2) then
    begin
      FreeLibrary(FLibHandle);
      Exit;
    end;
    NetFileClose2(nil, StrToInt(lvFiles.Items.Item[i].Caption)); //Закрываем
  end;
  FreeLibrary(FLibHandle);
end;

Этот код также оставлю без комментариев.


Определение входящего и исходящего трафика

Вот мы и подобрались к заключительной части статьи. Часто (практически всегда) можно услышать советы использовать собственноручно написанный прокси - сервер для определения трафика сети (чего там скрывать, и сам этим грешу, не охота объяснять по 15 раз на день прописные истины ;) Узнать трафик можно и не используя прокси - сервер. Для этого достаточно использовать всего лишь одну функцию библиотеки IPHLPAPI.DLL, которая поставляется со всеми версиями Windows. Её то мы и рассмотрим:

Объявление функции (все версии Windows):

var
  GetIfTable: function(pIfTable: PMibIfTable;
    pdwSize: PULONG;
    bOrder: Boolean): DWORD; stdcall;

Параметры:
pIfTable - должен содержать указатель на структуру
pdwSize - должен содержать размер структуры
bOrder - указывает, нужна ли сортировка в возвращаемом массиве

В качестве первого параметра функция использует указатель на структуру, вот само описание структуры

type
  TMibIfTable = packed record
    dwNumEntries: DWORD;
    Table: TMibIfArray;
  end;
  PMibIfTable = ^TMibIfTable;

Поля:
dwNumEntries - определяет размерность массива представленного вторым параметром
Table - является массивом структур

Структура сама по себе крайне неинформативна, нас интересует второе ее поле, также представляющее собой структуру, даю её описание:

type
  TMibIfRow = packed record
    wszName: array[0..255] of WideChar;
    dwIndex: DWORD;
    dwType: DWORD;
    dwMtu: DWORD;
    dwSpeed: DWORD;
    dwPhysAddrLen: DWORD;
    bPhysAddr: array[0..7] of Byte;
    dwAdminStatus: DWORD;
    dwOperStatus: DWORD;
    dwLastChange: DWORD;
    dwInOctets: DWORD;
    dwInUcastPkts: DWORD;
    dwInNUCastPkts: DWORD;
    dwInDiscards: DWORD;
    dwInErrors: DWORD;
    dwInUnknownProtos: DWORD;
    dwOutOctets: DWORD;
    dwOutUCastPkts: DWORD;
    dwOutNUCastPkts: DWORD;
    dwOutDiscards: DWORD;
    dwOutErrors: DWORD;
    dwOutQLen: DWORD;
    dwDescrLen: DWORD;
    bDescr: array[0..255] of Char;
  end;
  TMibIfArray = array[0..512] of TMibIfRow;
  PMibIfRow = ^TMibIfRow;
  PmibIfArray = ^TmibIfArray;

Поля:
wszName - Указатель на строку содержащую имя интерфейса
dwIndex - Определяет индекс интерфейса
dwType - Определяет тип интерфейса (см. MSDN)
dwMtu - Определяет максимальную скорость передачи
dwSpeed - Определяет текущую скорость передачи в битах в секунду
dwPhysAddrLen - Определяет длину адреса содержащегося в bPhysAddr
bPhysAddr - Содержит физический адрес интерфейса (если проще то его, немного видоизмененный, МАС адрес)
dwAdminStatus - Определяет активность интерфейса
dwOperStatus - Содержит текущий статус интерфейса (см. MSDN)
dwLastChange - Содержит последний измененный статус
dwInOctets - Содержит количество байт принятых через интерфейс
dwInUcastPkts - Содержит количество направленных пакетов принятых интерфейсом
dwInNUCastPkts - Содержит количество ненаправленных пакетов принятых интерфейсом (включая Броадкаст и т.п.)
dwInDiscards - Содержит количество забракованных входящих пакетов (даже если они не содержали ошибки)
dwInErrors - Содержит количество входящих пакетов содержащих ошибки
dwInUnknownProtos - Содержит количество забракованных входящих пакетов со структурой неизвестного протокола
dwOutOctets - Содержит количество байт отправленных интерфейсом
dwOutUCastPkts - Содержит количество направленных пакетов отправленных интерфейсом
dwOutNUCastPkts- Содержит количество ненаправленных пакетов отправленных интерфейсом (включая Броадкаст и т.п.)
dwOutDiscards- Содержит количество забракованных исходящих пакетов (даже если они не содержали ошибки)
dwOutErrors- Содержит количество исходящих пакетов содержащих ошибки
dwOutQLen - Содержит длину очереди данных
dwDescrLen - Содержит размер массива bDescr
bDescr - Содержит описание интерфейса

Как вы видите в этой структуре содержится уйма информации, которую мы и будем использовать (часть её ;) Заметьте, интерфейсом является не обязательно некое физическое устройство (например, сетевая карта), но на этом я останавливаться не буду. Если кому-то это интересно, посмотрите, что об этом говорит MSDN.

Итак, добавьте на форму ListView, назовем его lvTraffic, создайте в ней четыре колонки со следующими именами (Caption) - bDescr, bPhysAddr (MAC), dwInOctets и dwOutOctets. В них мы будем выводить наименование интерфейса, его МАС адрес, общее кол-во принятых и отправленных байт. Добавьте описания структур и функции в интерфейсную часть модуля. Примечание, если вы добавляете структуры в том виде, в каком они даны, поменяйте очередность их объявления, т.е. TMibIfRow должна быть объявлена первой. Теперь добавьте на форму таймер, он будет отвечать за ежесекундное обновление информации о трафике, назовем его tmrTraffic. Вот сам код определения текущего входящего - исходящего трафика:

procedure TMainForm.tmrTrafficTimer(Sender: TObject);
// Вспомогательная функция, преобразующая МАС адрес к "нормальному" виду
//Определяем специальный тип, чтобы можно было передать в функцию массив
type
  TMAC = array[0..7] of Byte;
  //В качестве первого значения массив, второе значение, размер данных в массиве
  function GetMAC(Value: TMAC; Length: DWORD): string;
  var
    i: Integer;
  begin
    if Length = 0 then
      Result := '00-00-00-00-00-00'
    else
    begin
      Result := '';
      for i := 0 to Length - 2 do
        Result := Result + IntToHex(Value[i], 2) + '-';
      Result := Result + IntToHex(Value[Length - 1], 2);
    end;
  end;

  //Сама процедура
var
  FLibHandle: THandle;
  Table: TMibIfTable;
  i: Integer;
  Size: Integer;
begin
  tmrTraffic.Enabled := False; //Приостанавливаем на всякий случай таймер
  lvTraffic.Items.BeginUpdate;
  lvTraffic.Items.Clear; //Очищаем список
  FLibHandle := LoadLibrary('IPHLPAPI.DLL'); //Загружаем библиотеку
  if FLibHandle = 0 then
    Exit;
  @GetIfTable := GetProcAddress(FLibHandle, 'GetIfTable');
  if not Assigned(GetIfTable) then
  begin
    FreeLibrary(FLibHandle);
    Close;
  end;

  Size := SizeOf(Table);
  if GetIfTable(@Table, @Size, False) = 0 then //Выполняем функцию
    for i := 0 to Table.dwNumEntries - 1 do
    begin
      with lvTraffic.Items.Add do
      begin //Выводим результаты
        Caption := string(Table.Table[i].bDescr); //Наименование интерфейса
        SubItems.Add(GetMAC(TMAC(Table.Table[i].bPhysAddr),
          Table.Table[i].dwPhysAddrLen)); //MAC адрес
        SubItems.Add(IntToStr(Table.Table[i].dwInOctets)); //Всего принято байт
        SubItems.Add(IntToStr(Table.Table[i].dwOutOctets));
          //Всего отправлено байт
      end;
    end;
  lvTraffic.Items.EndUpdate;
  FreeLibrary(FLibHandle);
  tmrTraffic.Enabled := True; //Не забываем активировать таймер
end;

Заметьте, я определяю новый тип данных TMAC для передачи массива, в котором содержится сам MAC адрес в функцию для преобразования его в более привычный вид. Обратите внимания на код TMAC(Table.Table[i].bPhysAddr), это передача массива, обязательно нужно указать, что массив передается как тип TMAC, в противном случае компилятор выдаст ошибку несовместимости типов.

Вот кажется и все, что я хотел вам рассказать. Поэкспериментируйте с данным кодом, я раскрыл не все возможности приведенных мной функций. Дерзайте ;) Заключение

В заключение хочу сказать. Вся информация была получена в результате тщательного изучения MSDN и выяснением особо острых моментов под С#. Если у вас возникнут какие либо вопросы по статье, сперва посмотрите как это реализовано в прилагаемой к статье программе. Не стесняйтесь пройтись по коду под отладкой. Посмотрите также сам MSDN. Кстати некоторые (и что самое удивительное не один и тот же человек) присылали мне письма с вопросом, что такое MSDN. Отвечаю: MSDN - сокращение от MicroSoft Developer Network, огромная база содержащая практически всю (если не всю) информацию о продуктах компании Microsoft. Распространяется обычно на трех - четырех компакт дисках, или доступна в интернете по адресу http://msdn.microsoft.com/

Код проверялся под следующими системами: Windows 98, Windows 98 SE, Windows Me, Windows NT 4.0 Server, Windows 2000 Professional, Windows 2000 Server и Windows XP Professional.

Код совместим с Delphi 5.0 Professional, Delphi 6.0 Enterprise SP1, Delphi 7.0 Enterprise (остальных версий Delphi просто не оказалось в наличии :)

Отдельно хочется выразить благодарность Анатолию Подгорецкому и Игорю Шевченко за время потраченное на изучение статьи и указание на допущенные мной ошибки.

Проект Delphi World © Выпуск 2002 - 2024
Автор проекта: USU Software
Вы можете выкупить этот проект.