RAS API для непродвинутых

=== RAS API для непродвинутых ===
 Особые благодарности Королеве дельфийского королевства Елене Филипповой, которая подвигла меня на ваяние сего опуса. 
==== Вместо предисловия ====
Я не являюсь профессиональным программистом и никогда не писал статьей по программированию. Поэтому "продвинутых" специалистов прошу не утруждать себя чтением ниже изложенного дабы избежать обструкции по поводу допущенных мною в настоящей статье ошибок и вольностей. 
Итак. Многие из "непродвинутых" дельфийцев, пытаясь использовать интернет-компоненты, сталкиваются с проблемой "удаленного соединения" В настоящей статье под "удаленным соединением" подразумевается подключение к интернет-провайдеру по телефонной сети с помощью модема. Собственно подключаться не трудно - чаще всего такие компоненты с помощью системной библиотеки wsock32.dll сами инициализируют подключение к Интернет по "удаленному соединению". Гораздо труднее бывает после интернет-сессии отключить "удаленное соединение". Поэтому на Круглом столе "Королевства Дельфи" с регулярностью наступления "критических дней" появляются вопросы (в том числе и мои): 
: "как после .... завершить соединение по удаленному доступу?" 
А "продвинутые гуру" от программирования с завидным постоянством на них отвечают: 
"смотри Ras API..." 
Давайте и посмотрим, что такое Ras API и с чем его едят :) 
Ras API - Remote Access Service Application Programming Interface. По-русски: "интерфейс программирования приложений, использующих службу удаленного доступа". Этот интерфейс как раз и служит для соединения по "удаленному доступу", а также отслеживания его состояния, отсоединения от телефонной линии, создания новых "удаленных соединений", изменения свойств уже созданных "соединений" и многого, многого другого. В библиотеку rasapi32.dll включено 97 экспортируемых функций. Даже в Help для Дельфи Microsoft любезно включило описание работы с функциями этой библиотеки. Напечатайте где-нибудь в проекте, например, RasDial и нажмите F1 - убедитесь сами. Казалось бы в чем проблемы? Ан нет! В поставке Дельфи нет интерфейсного модуля для работы с этой библиотекой. Что такое интерфейсный модуль? Это Pascal Unit - *.pas или *.dcu, в котором описаны используемые в DLL константы, типы переменных, функции и процедуры, порядок обращения к ним и др. Например, Windows.pas (обратите внимание на самую первую запись, включаемую в клаузу Uses) - это интерфейсный модуль для системных библиотек kernel32.dll, user32.dll и gdi32.dll и др., который позволяет Pascal-программам обращаться к процедурам и функциям этих библиотек. 
После некоторого скитания по Интернет мне удалось обнаружить столь нужный нам всем интерфейсный модуль для rasapi.dll - называется он RasUnit.pas. 
Весь модуль описывать здесь я не буду. Опишу только пять функций, позволяющих соединиться с Интернет по телефонной линии с помощью модема, отследить состояние "удаленного соединения" и отключиться от телефонной линии по завершению интернет-сеанса. 
==== Получение сведений о всех зарегистрированных в системе "удаленных соединениях". ====
Для получения сведений о всех зарегистрированных в системе "удаленных соединений" используется библиотечная функция Далее под "библиотечной функцией" будем понимать функцию библиотеки rasapi32.dll RasEnumEntries 
Рассмотрим работу этой функции. Прежде всего определим переменные: 
var
 { размер массива из AnsiChar, в который будут помещаться cведения об "удаленных соединениях" }
 BuffSize: Integer; 
 { количество зарегистрированных "удаленных соединений" }
 Entries: Integer; 
 { массив состоящий из переменных, в которые будут помещены сведения об "удаленных соединениях",
 где константа MaxEntries - количество возможных соединений, TRasEntryName - определение (type) записи 
 состоящей из двух полей dwSize и szEntryName (определены в RasUnit.pas) }
 Entry: Array[1..MaxEntries] of TRasEntryName; 
 { необходимые процедурные переменные }
 X, Result_: Integer; 
 { сюда мы поместим названия "удаленных соединений" для дальнейшей работы с ними }
 AllEntries: TStrings; 
Перейдем к описанию работы функции RasEnumEntries. 
1. Определим размер переменной типа TRasEntryName и инициализируем переменную Entry, поместив в поле dwSize полученный размер. 
Entry[1].dwSize := SizeOf(TRasEntryName);
2. Определим размер AnsiChar-массива, в который поместим сведения обо всех "удаленных соединениях" 
BuffSize := SizeOf(TRasEntryName) * MaxEntries;
3. Вызовем функцию RasEnumEntries в результате чего получим искомые результаты:
Result_ := RasEnumEntries(nil, nil, @Entry[1], BuffSize, Entries);
* Result_ - в случае успешного выполнения возвращает 0, в противном случае получим ERROR_BUFFER_TOO_SMALL (буфер слишком маленький) или ERROR_NOT_ENOUGH_MEMORY (не хватает памяти).
* BuffSize - указанный нами размер AnsiChar-массива.
* @Entry[1]- получим указатель на первый элемент массива, в который поместились необходимые нам сведения. 
* Entries - получим количество зарегистрированных в системе "удаленных доступов". 
Дальше уже просто. 
В случае успешного выполнения функции и существования хотя бы одного зарегистрированного "удаленного соединения" заполним нашу переменную 
if (Result_ = 0) and (Entries > 0) then
begin
 AllEntries := TStringList.Create;
 for X := 1 to Entries do
 begin
 AllEntries.Add(Entry[x].szEntryName);
 end;
 {.....здесь мы работает со своей переменной, например,
 помещаем сведения об "удаленных соединениях" в ListBox......}
 AllEntries.Free;
end;
==== Соединение с интернет-сервером через выбранный "удаленный доступ" и получение статуса соединения ====
Для соединения с интернет-сервером используются две библиотечные функции RasGetEntryDialParams и RasDial. Для обработки ошибок, возникших в процессе соединения, используется еще одна библиотечная функция RasGetErrorString. 
Определим необходимые переменные.
Глобальные:
type
 TMyDialParam = record
 AMsg: Integer; // код сообщения
 AState: TRasConnState; // статус соединения (тип переменной определен в RasUnit.pas)
 AError: Integer; // код ошибки
 hRas: ThRASConn;
 end;
var
 MyDialParam: TMyDialParam; // переменная состояния соединения
в эту переменную будет помещен handle (так сказать "ИНН") "удаленного соединения", к этой переменной будет обращаться функция RasHangUp для завершения соединения, тип переменной описан в RasUnit.pas 
Локальные:
var
 { если в "удаленном доступе" не указан пароль пользователя, то эта переменная устанавливается в False
 и появляется приглашение ввести пароль, если пароль указан, то переменная устанавливается в True и приглашение не появляется. }
 Fp: LongBool;
 { переменная, в которую будут переданы параметры "удаленного соединения", 
 описывать тип этой переменной я не буду - он хорошо описан в Win32 Programmer's Reference
 (кто не знает - это один из разделов Help'а, поставляемого вместе с Delphi) и определен в RasUnit.pas }
 DialParams: TRasDialParams;
 { переменная, в которую поместим название "удаленного соединения" }
 AEntryDial: String;
 { результат выполнения библиотечных функций }
 R: Integer;
 { переменная, в которую записывается текст сообщения об ошибке }
 C: Array[0..100] of Char;
Кроме переменных необходимо также определить CallBack-процедуру, которая будет использована в функции RasDial Для не посвященных в дебри программирования от Microsoft. CallBack-функция - это функция "обратного вызова". Служит для обработки некоторых функций и процедур созданных компанией Microsoft. В Pascal'е не применяется. С CallBack-функциями приходится часто сталкиваться при попытках программирования внутри Delphi на API (Application Programming Interface - интерфейс программирования приложений). К чему такие сложности? Не знаю. Возможно это такой стиль программирования от Microsoft. J Т.к. в нашем примере возвращать никаких "CallBack-данных" не требуется, поэтому вместо CallBack-функции определим CallBack-процедуру, да простит меня за это Билл Гейтс (кто не знает - это отец-основатель Microsoft). Библиотечная функция RasDial, вызывая эту процедуру помещает в переменную state код состояния соединения. Stdcall - это способ передачи данных через стек CPU (справа - налево). Зарезервированное слово stdcall необходимо применять при обращении к находящимся в DLL (Dynamic Link Library - динамически подключаемая библиотека) процедурам и функциям, написанных на другом языке программирования - это из Help'а. Я однажды забыл указать этот параметр при обращении к DLL написанной на Pascal и в результате "подвесил" компьютер. Поэтому, всегда при обращении к библиотекам указывайте - stdcall.. 
procedure RasCallBack(msg: Integer; state: TRasConnState; error: Integer); stdcall
{ где
 msg: Integer - код сообщения
 state: TRasConnState - состояние соединения
 error: Integer - код ошибки }
В этой процедуре передадим глобальной переменной MyDialParam значения указанных переменных. 
MyDialParam.AMsg := msg;
MyDialParam.AState := state;
MyDialParam.AError := error;
А также вызовем функцию GetStatusString (будет описана ниже), которая сообщит нам в Label1.Caption о состоянии соединения. 
Form1.Label1.Caption := GetStatusString(MyDialParam.AState, MyDialParam.AError);
Form1.Label1.Update; { на всякий случай }
Все, на этом с CallBack процедурой закончено. 
Переходим к описанию процесса подключения к "удаленному соединения". 
Получим название выбранного нами "удаленного соединения" 
 AEntryDial := ListBox1.Items.Strings[ListBox1.ItemIndex];
Заполним все поля переменной DialParams нолями. 
 FillChar(DialParams, SizeOf(TRasDialParams), 0);
Инициализируем переменную DialParams и поместим в поле szEntryName этой переменной название выбранного "удаленного соединения" 
with DialParams Do
begin
 dwSize := Sizeof(TRasDialParams);
 StrPCopy(szEntryName, FEntry2Dial);
end;
Вызовем библиотечную функцию RasGetEntryDialParams, которая заполнит оставшиеся незаполненными поля переменной DialParams 
 R := RasGetEntryDialParams(nil, DialParams, Fp);
Далее, если все удачно (см. значение переменной R), вызываем библиотечную функцию RasDial, поместив в нее переменную DialParams и указав на описанную выше CallBack-процедуру RasCallBack. В случае ошибок в работе функции RasDial вызовем библиотечную функцию обработки ошибок RasGetErrorString, которая даст нам текст сообщения об ошибке, и выйдем из процедуры. 
if R = 0 then
begin
 { Это для того, чтобы в процессе соединения с сервером наше приложение могло 
 реагировать на сообщения Windows, например, на нажатие кнопок. }
 Application.ProcessMessages;
 R := RasDial(nil, nil, DialParams, 0, @RasCallback, hRAS);
 if R <> 0 then
 begin
 RasGetErrorString(R, C, 100);
 MessageBox(0, C, 'Ошибка!', MB_OK);
 Exit;
 end;
end;
6. На этом все!
Напоследок опишем
 function GetStatusString(State: TRasConnState; Error: Integer): String 
которая даст нам состояние соединения. Думаю, что переменные State и Error уже не требуют объяснения. 
if Error <> 0 then
begin
 RasGetErrorString(Error, C, 100);
 Result := C;
end
else
begin
 S := ;
 case State of
 RASCS_OpenPort:
 S := 'Opening port';
 // .................................
 // вырезано ....
 // .................................
 RASCS_Disconnected:
 S := 'Disconnected';
 end;
 Result := S;
end;
Теперь уже совсем все с подключением "удаленного соединения" и получением его статуса! 
==== Завершение удаленного соединения ====
Самый распространенный вопрос по рассматриваемой нами теме это - 
"как после .... завершить соединение по удаленному доступу?" 
После того, что мы уже рассмотрели, ответ на этот вопрос покажется очень простым - надо вызвать библиотечную функцию RasHangUp с одной единственной уже описанной нами переменной hRas: ThRASConn. 
 RasHangUp(hRas)
Удачи! 
Интерфейсный модуль RasUnit.pas и проект, использующий рассмотренные нами функции библиотеки rasapi32.dll, прилагается: Файлы проекта + RasUnit : RasAPI.zip (23 K) 
==== Примечания: ====
Author: Александр Терехов
Source: https://delphisources.ru/pages/faq/base/ras_api_for_beginners.html
ID: 01472