Дозвон с использованием WinAPI(RAS-функции) (Borland Builder C++)

Дозвон к провайдеру в Windows реализуется через набор так называемых RAS(Remoute Access System)-функций. Как понятно из названия данная подсистема АПИ (API) реализует набор возможностей по удаленyому доступу на сервер. Дозвон и установка соединения - это малая часть функциональности - более полное описание вы можете посмотреть в МСДН. В данной статье будет рассмотрено исключительно дозвон и установка соединения.
Для начала вам необходимо подключить заголовочные файлы windows.h,ras.h и raserror.h
Все функции реализованы в RASAPI32.DLL, т.е. что бы использовать функции необходимо или загрузить библиотеку динамически, либо подключить .lib-файл в проект. Последнее гораздо проще и быстрее - с некоторыми оговорками. При динамической загрузке ВЫ управляете процессом и должны обрабатывать внештатные ситуации. Если по каким либо причинам библиотека отсутствует, изменена или еще что-нибудь не так - в случае динамической загрузки вы можете вывести осмысленное сообщение, при статической линковке приложение просто не запустится. В общем во всех ситуациях, когда скорость не особо критична я использую динамическую загрузку - но это не значит, что это правильно - просто мне так больше нравится :)
Т.е. следующим вашим шагом будет объявление указателей на функции:
typedef DWORD (__stdcall *pRasGetErrorString)(UINT, LPTSTR, DWORD);
typedef DWORD (__stdcall *pRasDial)(LPRASDIALEXTENSIONS, LPTSTR,
LPRASDIALPARAMS, DWORD, LPVOID, LPHRASCONN);
typedef DWORD (__stdcall *pRasEnumConnections)
(LPRASCONN, LPDWORD, LPDWORD);
typedef DWORD (__stdcall *pRasGetConnectStatus)
(HRASCONN, LPRASCONNSTATUS);
typedef DWORD (__stdcall *pRasEnumEntries)(LPTSTR, LPTSTR,
LPRASENTRYNAME, LPDWORD, LPDWORD);
typedef DWORD (__stdcall *pRasGetEntryDialParams)(LPTSTR,
LPRASDIALPARAMS,LPBOOL);
typedef DWORD (__stdcall *pRasHangUp)(HRASCONN);
pRasGetErrorString fRasGetErrorString;
pRasDial fRasDial;
pRasEnumConnections fRasEnumConnections;
pRasGetConnectStatus fRasGetConnectStatus;
pRasEnumEntries fRasEnumEntries;
pRasGetEntryDialParams fRasGetEntryDialParams;
pRasHangUp fRasHangUp;
HINSTANCE hRasInstance;
HRASCONN hRas;
Надеюсь здесь понятно все - назначение функций я поясню далее.
Для работы приложения мне необходимы 7 функций - но это в моем случае. Если вы предполагаете, что пользователь будет вводить имя и пароль в приложении, вместо использования соединения по умолчанию - то часть функций вам не пригодится, зато понадобятся другие :). Как уже сказал - более подробное пояснение смотрите в МСДН - ну а я продолжу.
Теперь необходимо реализовать функцию, которая будет выводить всякую фигню на экран - типа "Набор номера...","Установка соединения....", и т.п.
Выглядит эта функция так(имя может быть любым):
VOID WINAPI RasCallback(HRASCONN hrasconn, UINT unMsg,
RASCONNSTATE rascs, DWORD dwError, DWORD dwExtendedError)
{
String S = "";
if (dwError) {
// Error occurred, show the error string.
char buff[256];
fRasGetErrorString(dwError, buff, sizeof(buff));
fmMainFtp->LogEvents(buff);
String MsgError = "При подключении произошла следующая ошибка:\n"+String(buff);
if(Application->MessageBoxA(MsgError.c_str(),"Ошибка",MB_ICONQUESTION+MB_OKCANCEL)==IDOK){
fRasHangUp(hrasconn);
fmMainFtp->ttTimer->Enabled = true;
return;
}
else fmMainFtp->Close();
return;
}
switch (rascs) {
// Build a status string based on the
// status message.
case RASCS_PortOpened :
S = "Открытие порта..."; break;
case RASCS_DeviceConnected :
S = "Соединение..."; break;
case RASCS_Authenticate :
S = "Вход в сеть..."; break;
case RASCS_Authenticated :
S = "Проверка пользователя и пароля"; break;
case RASCS_Connected : {
S = "Вход в сеть";
// fmMainFtp->bbSend->Enabled = true;
fmMainFtp->ttTimerClick->Enabled = true;
break;
}
case RASCS_Disconnected :
S = "Disconnected"; break;
}
// Show the status message in the memo.
if (S != "")
fmMainFtp->LogEvents(S);
}обратите внимание на важный момент - функция объявлена как WINAPI - и она объявлена вне класса формы. Если возникла ошибка, она обрабатывается и если нужно, устанавливается соединение или форма закрывается. Если все нормально - обрабатываются события соединения. LogEvents - функция отвечающая за вывод информации на форму и запись в файл.
void __fastcall TfmMainFtp::LogEvents(String Msg){
String Message = DateTimeToStr(Now())+"\t"+Msg;
mmLog->Lines->Add(Message);
Message=Message+"\n";
Word Year, Month, Day;
TDateTime Current = Now();
DecodeDate(Current,Year,Month,Day);
TVarRec arg[3] = {2,Month,Day};
String M = Format("%2.2d",&arg[1],0);
String D = Format("%2.2d",&arg[2],0);
String LogFile = "message"+IntToStr(Year)+M+D+".log";
String tmp = GetCurrentDir();
SetCurrentDir(DirProg+"\\log");
ofstream file(LogFile.c_str(),ios::app);
file << Message.c_str();
file.close();
SetCurrentDir(tmp);
return;
}
В приложении соответственно необходимо контролировать наличие поддиректории \log. Все остальное я надеюсь особых пояснений не требует. Сообщение формируется, выводится в данном случае в мемо (mmLog) после чего записывается в файл лога с именем "messageYYYYMMDD.log" - где YYYY - год, MM - месяц ну и день понятно.
Следующий шаг - собственно загрузка длл и функций из нее:
MutexFirst = CreateMutex(NULL,true,"MyMutex");
int Res = GetLastError();
if(Res==ERROR_ALREADY_EXISTS){
First = 1;
Close();
}
hRas = 0;
hRasInstance = LoadLibrary("RASAPI32.DLL");
if (!hRasInstance) {
LogEvents("Unable to load RAS DLL.");
Close();
return;
}
fRasGetErrorString = (pRasGetErrorString)
GetProcAddress(hRasInstance, "RasGetErrorStringA");
fRasDial = (pRasDial)
GetProcAddress(hRasInstance, "RasDialA");
fRasEnumConnections = (pRasEnumConnections)
GetProcAddress(hRasInstance, "RasEnumConnectionsA");
fRasGetConnectStatus = (pRasGetConnectStatus)
GetProcAddress(hRasInstance, "RasGetConnectStatusA");
fRasEnumEntries = (pRasEnumEntries)
GetProcAddress(hRasInstance, "RasEnumEntriesA");
fRasGetEntryDialParams = (pRasGetEntryDialParams)GetProcAddress(hRasInstance, "RasGetEntryDialParamsA");
fRasHangUp = (pRasHangUp)
GetProcAddress(hRasInstance, "RasHangUpA");
if (!fRasGetErrorString || !fRasDial || !fRasEnumConnections ||
!fRasGetConnectStatus || !fRasEnumEntries || !fRasHangUp) {
LogEvents("Ошибка загрузки функций дозвона");
Close();
return;
}
Здесь выполнена простейшая защита от повторного запуска окна. Сейчас это не важно. Важно то что проверяется загрузка длл и загрузка всех функций - если что-то не заладилось - выводим сообщение и прекращаем работу.
Следующее, что необходимо выполнить - нужно проверить на наличие уже установленного соединения или попытки установить его.
HRASCONN TfmMainFtp::CheckForConnections()
{
if(GetInternetConnection())return (void*)(1);
char buff[256];
RASCONN rc;
rc.dwSize = sizeof(RASCONN);
DWORD numConns;
DWORD size = rc.dwSize;
// Enumerate the connections.
DWORD res = fRasEnumConnections(&rc, &size, &numConns);
if (!res && numConns == 0)
// В системе не создано нет соединений - нечем работать
return 0;
if (res) {
// Ошибка!!! Пишем в лог
fRasGetErrorString(res, buff, sizeof(buff));
LogEvents(buff);
String MsgError = "При подключении произошла следующая ошибка:\n"+String(buff);
//Если пользователь выбирает - перезвонить - запускаем таймер и пытаемся снова if(Application->MessageBoxA(MsgError.c_str(),"Ошибка",MB_ICONQUESTION+MB_OKCANCEL)==IDOK){
fRasHangUp(rc.hrasconn);
ttTimer->Enabled = true;
return 0;
}
else Close();
return 0;
}
else {
// Получение текущего статуса.
RASCONNSTATUS status;
status.dwSize = sizeof(status);
res = fRasGetConnectStatus(rc.hrasconn, &status);
if (res) {
// Ошибка!!!!
fRasGetErrorString(res, buff, sizeof(buff));
LogEvents(buff);
String MsgError = "При подключении произошла следующая ошибка:\n"+String(buff);
if(Application->MessageBoxA(MsgError.c_str(),"Ошибка",MB_ICONQUESTION+MB_OKCANCEL)==IDOK){
fRasHangUp(rc.hrasconn);
ttTimer->Enabled = true;
return 0;
}
else Close();
return 0;
} else {
// Если есть соединение - показываем его статус.
if (status.rasconnstate == RASCS_Connected) {
LogEvents("Установлено соединение:");
LogEvents("Тип устройства:\t" +
String(rc.szDeviceType));
LogEvents("Название устройства: " +
String(rc.szDeviceName));
LogEvents("Соединение: " +
String(rc.szEntryName));
return rc.hrasconn;
} else {
// Соединение найдено но оно уже завершено
// status is RASCS_Disconnected.
//Если надо - кладем трубку и перезваниваем
LogEvents("Connection Error");
String MsgError = "При подключении произошла следующая ошибка:\n"+String(buff);
if(Application->MessageBoxA(MsgError.c_str(),"Ошибка",MB_ICONQUESTION+MB_OKCANCEL)==IDOK){
fRasHangUp(rc.hrasconn);
ttTimer->Enabled = true;
return 0;
}
else Close();
return 0;
}
}
}
return 0;
}А эта функция собственно все и начинает - у меня она привязана к таймеру, который активизируется при создании формы
void __fastcall TfmMainFtp::acConnectWithInetExecute(TObject *Sender)
{
ttTimer->Enabled = false;
hRas = CheckForConnections();
String PBEntry;
if (hRas) {
LogEvents("Используем существующее соединение...");
//LogEvents("Кликните на кнопку \"Отправить/принять\" или ожидайте автоматического старта соединения.");
// bbSend->Enabled = true;
ttTimerClick->Enabled = true;
AlreadyConnected = true;
// Ничего не нужно делать - соединение установлено.
return;
}
else {
// Выбираем список соединений.
PBEntry = GetPhoneBookEntry();
if (PBEntry == "") {
LogEvents("Нет соединений");
Close();
}
}
LogEvents("Соединение отсутствует. Дозваниваемся...");
// Проверяем - вдруг мы работаем под - 98винд.
CheckRnaApp();
// Установка параметров соединения.
RASDIALPARAMS params;
params.dwSize = sizeof(params);
strcpy(params.szEntryName, PBEntry.c_str());
//Юзаем сохраненный пароль
BOOL PwdDetected = FALSE;
//Заполняем структуру
DWORD Result = fRasGetEntryDialParams(NULL,¶ms,&PwdDetected);
//Дозваниваемся
DWORD res = fRasDial(0, 0, ¶ms, 1, RasCallback, &hRas);
//Чегото не то :)
if (res) {
char buff[256];
fRasGetErrorString(res, buff, sizeof(buff));
LogEvents(buff);
String MsgError = "При подключении произошла следующая ошибка:\n"+String(buff);
if(Application->MessageBoxA(MsgError.c_str(),"Ошибка",MB_ICONQUESTION+MB_OKCANCEL)==IDOK){
fRasHangUp(hRas);
ttTimer->Enabled = true;
return;
}
else Close();
}
}
здесь нам понадобилось еще две функции - для получения имени соединения по умолчанию и для проверки версии операционнки - вот они:
String TfmMainFtp::GetPhoneBookEntry()
{
LogEvents("Инициализация соединения");
String S;
TRegistry *Reg = new TRegistry;
Reg->RootKey = HKEY_LOCAL_MACHINE;
Reg->OpenKeyReadOnly("\\SOFTWARE\\Microsoft\\RAS AutoDial\\Default");
LogEvents("Получение соединения по умолчению");
//Если есть соединение по умолчанию
if(Reg->ValueExists("DefaultInternet")){
S = Reg->ReadString("DefaultInternet");
delete Reg;
return S;
}
else{
RASENTRYNAME* entries = new RASENTRYNAME;
entries->dwSize = sizeof(RASENTRYNAME);
DWORD numEntries;
DWORD size = entries->dwSize;
DWORD res = fRasEnumEntries(0, 0, entries, &size, &numEntries);
if (numEntries == 1) {
String entryName = entries->szEntryName;
delete[] entries;
delete Reg;
return entryName;
}
//если соединений больше одного и нет по умолчанию - берем первое
if (res == ERROR_BUFFER_TOO_SMALL) {
// памяти йок
delete[] entries;
entries = new RASENTRYNAME[numEntries];
entries[0].dwSize = sizeof(RASENTRYNAME);
res = fRasEnumEntries(0, 0, entries, &size, &numEntries);
if (res) {
char buff[256];
fRasGetErrorString(res, buff, sizeof(buff));
LogEvents(buff);
}
S = entries[0].szEntryName;
}
delete[] entries;
delete Reg;
return S;
}
}
void TfmMainFtp::CheckRnaApp()
{
if (Win32Platform == VER_PLATFORM_WIN32_NT)
// RNAAPP is only Win9x
return;
HWND hWnd = FindWindow("RnaEngClass", 0);
if (hWnd) {
// Found it, kill it.
PostMessage(hWnd, WM_CLOSE, 0, 0);
// Delay a bit for good measure.
Sleep(2000);
}
}
Ну естественно, по завершении работы надо закрыть соединение - если его открывали мы:
void __fastcall TfmMainFtp::CloseConnect()
{
LogEvents("Завершаем соединение...");
if (!hRas)
return;
// Hang up.
fRasHangUp(hRas);
// Очищаем состояние.
DWORD res = 0;
while (res != ERROR_INVALID_HANDLE) {
RASCONNSTATUS status;
status.dwSize = sizeof(status);
res = fRasGetConnectStatus(hRas, &status);
::Sleep(0);
}
AlreadyConnected = true;
hRas = 0;
LogEvents("Соединение завершено");
}
Вот и все.
- Войдите или зарегистрируйтесь, чтобы получить возможность отправлять комментарии
