Разработка приложений для Internet© Александр Фролов, Григорий ФроловТом 31, М.: Диалог-МИФИ, 1997, 286 стр. Функции WinInetКроме классов WinInet в состав библиотеки MFC входят три глобальные функции - AfxParseURL, AfxGetInternetHandleType и AfxThrowInternetException. Они не принадлежат классам MFC и могут вызываться из любого места приложения. Наибольший интерес представляет функция AfxParseURL. Она позволяет выделить из строки URL составные части. Вы можете использовать эту функцию для обработки адресов URL. Для обработки ошибок WinInet в состав библиотеки классов MFC включен класс CInternetException. Соответствующее исключение вызывается методами классов WinInet, чтобы сообщить приложению о возникших ошибках и нестандартных ситуациях. Приложение может самостоятельно вызвать исключение CInternetException, воспользовавшись глобальной функцией AfxThrowInternetException. Функция AfxGetInternetHandleType позволяет определить тип идентификатора Internet. Для определения типа идентификатора Internet глобальная функция InternetQueryOption вызывает функцию InternetQueryOption библиотеки WinInet. Также как и классы WinInet все глобальные функции описаны в файле AFXINET.H. Если вы желаете изучить их более подробно, то можете найти исходные тексты в файле INET.CPP. Адреса URLРесурсы, доступные через сеть Internet, определяются посредством универсальных адресов ресурсов или просто адресов (URL - Universal Resource Locators или Uniform Resource Locators). Адрес URL определяет имя сервера на котором расположен ресурс, имя объекта и каталог где этот ресурс находится, протокол для взаимодействия с сервером и номер порта TCP/IP на котором происходит соединение. Общий формат адресов URL представлен ниже: service://server:port/dir/dir/object.ext Поле service определяет протокол для работы с сервером. Сразу после названия протокола следует символ двоеточия и два обратных слеша. Затем идет поле server. Оно содержит название сервера, соответствующего данному адресу. После имени сервера может располагаться номер порта TCP/IP. Это числовое значение перед которым надо указать символ двоеточия. Данное поле необязательное. Если вы не укажете номер порта TCP/IP, то будет использоваться порт принятый по умолчанию для данного протокола. Непосредственно за номером порта TCP/IP или сразу после имени сервера (если номер порта TCP/IP не задан) следует путь каталога и имя объекта на который ссылается адрес. В качестве имени объекта может фигурировать имя файла, имя расширения CGI или ISAPI. Функция AfxParseURLФункция AfxParseURL разбирает текстовую строку, содержащую универсальный указатель ресурсов URL и выделяет из него тип сервиса, объект и номер порта TCP/IP. Приведем прототип функции AfxParseURL: BOOL AFXAPI AfxParseURL( LPCTSTR pstrURL, DWORD& dwServiceType, CString& strServer, CString& strObject, INTERNET_PORT& nPort ); Указатель на строку, содержащую URL, передается функции AfxParseURL через параметр pstrURL. Функция AfxParseURL разбирает данную строку и возвращает результат через параметры dwServiceType, strServer, strObject и nPort. Тип сервиса, соответствующий указанному URL, записывается в переменную по ссылке dwServiceType. В качестве типа сервиса может фигурировать одна из констант, перечисленных в следующей таблице (полный список смотрите в документации Microsoft Visual C++):
В строку strServer записывается первое поле URL, определяющее тип сервиса (тип протокола). Объект на который ссылается URL записывается в строку strObject. И, наконец, номер порта TCP/IP записывается в переменную nPort. Функция AfxParseURL возвращает ненулевое значение в случае успешного завершения и нуль в случае ошибки. Приложение ParseВ этом разделе мы представим вашему вниманию приложение Parse. Оно использует функцию AfxParseURL для разбора на составные части адресов URL, которые пользователь может вводить в диалоговой панели приложения (рис. 1.3). Приложение Parse не выполняет соединение с Internet и вы можете его запустить на компьютере, не имеющем ни модема, ни сетевой карты, и не подключенном к сетям Internet и Intranet. Конечно, пользы от такого приложения не много, но мы предлагаем вам с ним поработать, чтобы лучше разобраться со структурой адресов URL. Рис. 1.3. Приложение ParseURL Чтобы упростить приложение, мы выбрали для него интерфейс на основе диалоговой панели. Запустите MFC AppWizard, выбрав из меню File строку New. На экране появится одноименная диалоговая панель. Выберите в ней из списка New строку Project Workspace и нажмите кнопку OK. Откроется диалоговая панель New Project Workspace. В качестве типа создаваемого приложения укажите MFC AppWizard (exe) и введите в поле Name имя проекта - Parse. Затем нажмите кнопку Create. На экране появится первая диалоговая панель MFC AppWizard - MFC AppWizard - Step 1. В ней вы должны выбрать тип пользовательского интерфейса приложения - Dialog based, то есть приложение с главной диалоговой панелью. В принципе, теперь вы можете нажать кнопку Finish и оставить все остальные характеристики приложения по умолчанию. Но чтобы упростить приложение и сделать его исходный текст более доступным для понимания, мы предлагаем вам сначала перейти к следующей панели MFC AppWizard Step 2 of 4 и отключить в ней переключатель About box. При этом MFC AppWizard не будет подключать к приложению информационную диалоговую панель About. За счет этого можно несколько упростить исходные тексты приложения. После этого, нажмите кнопку Finish, просмотрите информацию о приложении, которая появится в диалоговой панели New Project Information, и нажмите на кнопку OK. MFC AppWizard создаст проект. Мы уже детально рассматривали шаблон приложения с пользовательским интерфейсом на основе диалоговой панели в 28 томе серии “Библиотека системного программиста”. Поэтому мы сразу приступим к доработке шаблона приложения и не будем останавливаться на исходных текстах, созданных MFC AppWizard. Сначала загрузите в редактор ресурсов шаблон главной диалоговой панели IDD_PARSEURL_DIALOG приложения Parse. Разместите на этой диалоговой панели пять полей редактирования IDC_EDIT_URL_ADDRESS, IDC_EDIT_SERVICE_TYPE, IDC_EDIT_SERVER_NAME, IDC_EDIT_OBJECT_NAME и IDC_EDIT_PORT_NUMBER. Все поля редактирования кроме IDC_EDIT_URL_ADDRESS сделайте доступными только для чтения. Для этого в панели свойств этих полей Edit Propeties на странице Styles установите переключатель Read-only. Около каждого поля редактирования сделайте соответствующие текстовые подписи URL Address, Service type, Server name, Object name и Port number. По умолчанию MFC AppWizard создает в диалоговой панели IDD_PARSEURL_DIALOG две кнопки - кнопку OK с идентификатором IDOK и кнопку Cancel с идентификатором IDCANCEL. Кнопка Cancel нам не понадобится вы можете ее удалить. Вместо нее добавьте кнопку Convert, присвоив ей идентификатор IDC_BUTTON_CONVERT. В листинге 1.1 мы привели фрагмент файла ресурсов приложения Parse, в котором определяется шаблон диалоговой панели IDD_PARSEURL_DIALOG. Ориентируйтесь на него, когда будете создавать эту диалоговую панель. Листинг 1.1. Фрагмент файла Parse.rc, шаблон диалоговой панели IDD_PARSEURL_DIALOG ////////////////////////////////////////////////////////////// // // Dialog // IDD_PARSEURL_DIALOG DIALOGEX 0, 0, 241, 210 STYLE DS_MODALFRAME | WS_POPUP | WS_VISIBLE | WS_CAPTION | WS_SYSMENU EXSTYLE WS_EX_APPWINDOW CAPTION "ParseURL" FONT 8, "MS Sans Serif" BEGIN DEFPUSHBUTTON "OK",IDOK,184,183,50,14 EDITTEXT IDC_EDIT_URL_ADDRESS,7,27,227,15, ES_AUTOHSCROLL LTEXT "URL Address",IDC_STATIC,7,15,43,8 EDITTEXT IDC_EDIT_SERVICE_TYPE,7,73,227,15,ES_AUTOHSCROLL | ES_READONLY LTEXT "Service type",IDC_STATIC,7,61,40,8 EDITTEXT IDC_EDIT_SERVER_NAME,7,110,227,15,ES_AUTOHSCROLL | ES_READONLY LTEXT "Server name",IDC_STATIC,7,97,41,8 EDITTEXT IDC_EDIT_OBJECT_NAME,7,147,227,15,ES_AUTOHSCROLL | ES_READONLY LTEXT "Object name",IDC_STATIC,7,135,41,8 EDITTEXT IDC_EDIT_PORT_NUMBER,7,182,52,15, ES_AUTOHSCROLL | ES_READONLY LTEXT "Port number",IDC_STATIC,7,170,39,8 PUSHBUTTON "Convert",IDC_BUTTON_CONVERT,115,183,50,14 END Все идентификаторы, которые создаются автоматически во время редактирования ресурсов приложения, в том числе диалоговой панели IDD_PARSEURL_DIALOG, записываются в файле resource.h. Исходный текст этого файла мы привели в листинге 1.2. Листинг 1.2. Файл resource.h //{{NO_DEPENDENCIES}} // Microsoft Developer Studio generated include file. // Used by ParseURL.rc // #define IDM_ABOUTBOX 0x0010 #define IDD_ABOUTBOX 100 #define IDS_ABOUTBOX 101 #define IDD_PARSEURL_DIALOG 102 #define IDR_MAINFRAME 128 #define IDC_EDIT_URL_ADDRESS 1000 #define IDC_EDIT_SERVICE_TYPE 1001 #define IDC_EDIT_SERVER_NAME 1002 #define IDC_EDIT_OBJECT_NAME 1003 #define IDC_EDIT_PORT_NUMBER 1004 #define IDC_BUTTON_CONVERT 1005 // Next default values for new objects // #ifdef APSTUDIO_INVOKED #ifndef APSTUDIO_READONLY_SYMBOLS #define _APS_NEXT_RESOURCE_VALUE 129 #define _APS_NEXT_COMMAND_VALUE 32771 #define _APS_NEXT_CONTROL_VALUE 1006 #define _APS_NEXT_SYMED_VALUE 101 #endif #endif После того, как вы завершите подготовку диалоговой панели IDD_PARSEURL_DIALOG, запустите MFC ClassWizard. С его помощью мы привяжем к полям редактирования диалоговой панели новые элементы данных класса CParseURLDlg. В диалоговой панели MFC ClassWizard перейдите на страницу Member Variables. Выберите в поле Class name имя класса CParseURLDlg и добавьте к нему с помощью кнопки Add Variable пять переменных, привязав их к полям ввода. К полю IDC_EDIT_URL_ADDRESS привяжите переменную m_UrlAddress, к IDC_EDIT_SERVICE_TYPE - m_ServiceType, IDC_EDIT_SERVER_NAME - m_ServerName и к IDC_EDIT_PORT_NUMBER - m_PortNumber. В качестве типа переменных выберите для всех них класс CString. Тогда используя механизм DDX вы легко сможете обмениваться данными между полями редактирования и соответствующими строками. В исходных текстах приложения MFC ClassWizard модифицирует конструктор и метод DoDataExchange класса CParseURLDlg. В конструкторе класса CParseURLDlg добавляется программный код, выполняющий инициализацию строк, привязанных к полям редактирования диалоговой панели. Как видно из листинга 1.3, соответствующие команды добавляются внутри блока AFX_DATA_INIT. Листинг 1.3. Фрагмент файла Parse.cpp, конструктор класса CParseURLDlg CParseURLDlg::CParseURLDlg(CWnd* pParent /*=NULL*/) : CDialog(CParseURLDlg::IDD, pParent) { //{{AFX_DATA_INIT(CParseURLDlg) m_ObjectName = _T(""); m_PortNumber = _T(""); m_ServerName = _T(""); m_ServiceType = _T(""); m_UrlAddress = _T(""); //}}AFX_DATA_INIT m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME); } К методу DoDataExchange класса CParseURLDlg MFC ClassWizard добавляет методы DDX_Text, фактически осуществляющие обмен данными между полями редактирования диалоговой панели и соответствующими текстовыми строками. Мы привели фрагмент файла Parse.cpp с определением метода DoDataExchange в листинге 1.4. Листинг 1.4. Фрагмент файла Parse.cpp, метод DoDataExchange класса CParseURLDlg void CParseURLDlg::DoDataExchange(CDataExchange* pDX) void CParseURLDlg::DoDataExchange(CDataExchange* pDX) { CDialog::DoDataExchange(pDX); //{{AFX_DATA_MAP(CParseURLDlg) DDX_Text(pDX, IDC_EDIT_OBJECT_NAME, m_ObjectName); DDX_Text(pDX, IDC_EDIT_PORT_NUMBER, m_PortNumber); DDX_Text(pDX, IDC_EDIT_SERVER_NAME, m_ServerName); DDX_Text(pDX, IDC_EDIT_SERVICE_TYPE, m_ServiceType); DDX_Text(pDX, IDC_EDIT_URL_ADDRESS, m_UrlAddress); //}}AFX_DATA_MAP } Мы разместили на диалоговой панели IDD_PARSEURL_DIALOG кнопку Convert. Еще раз воспользуйтесь MFC ClassWizard и добавьте к классу CParseURLDlg обработчик сообщений от этой кнопки. Он будет вызываться когда пользователь будет нажимать на кнопку. MFC ClassWizard предложит вам назвать соответствующий метод именем OnButtonConvert. Согласитесь с этим предложением и в таблице сообщений класса CParseURLDlg появится новая макрокоманда: BEGIN_MESSAGE_MAP(CParseURLDlg, CDialog) //{{AFX_MSG_MAP(CParseURLDlg) . . . ON_BN_CLICKED(IDC_BUTTON_CONVERT, OnButtonConvert) //}}AFX_MSG_MAP END_MESSAGE_MAP() Кроме того, в определении класса CParseURLDlg будет добавлено описание метода OnButtonConvert. Мы привели определение класса CParseURLDlg в листинге 1.5. Листинг 1.5. Фрагмент файла Parse.h, определение класса CParseURLDlg class CParseURLDlg : public CDialog { // Construction public: CParseURLDlg(CWnd* pParent = NULL); // Dialog Data //{{AFX_DATA(CParseURLDlg) enum { IDD = IDD_PARSEURL_DIALOG }; CString m_ObjectName; CString m_PortNumber; CString m_ServerName; CString m_ServiceType; CString m_UrlAddress; //}}AFX_DATA // ClassWizard generated virtual function overrides //{{AFX_VIRTUAL(CParseURLDlg) protected: virtual void DoDataExchange(CDataExchange* pDX); //}}AFX_VIRTUAL // Implementation protected: HICON m_hIcon; // Generated message map functions //{{AFX_MSG(CParseURLDlg) virtual BOOL OnInitDialog(); afx_msg void OnSysCommand(UINT nID, LPARAM lParam); afx_msg void OnPaint(); afx_msg HCURSOR OnQueryDragIcon(); afx_msg void OnButtonConvert(); //}}AFX_MSG DECLARE_MESSAGE_MAP() }; Определение метода OnButtonConvert размещается в файле Parse.cpp вместе с остальными методами класса CParseURLDlg. MFC ClassWizard создает только шаблон метода, который не выполняет никаких действий. Вы должны самостоятельно доработать метод OnButtonConvert, загрузив его в текстовый редактор Microsoft Visual C++. В листинге 1.6 мы привели фрагменты файла Parse.cpp с определением метода OnButtonConvert класса CParseURLDlg. Листинг 1.6. Фрагмент файла Parse.cpp, метод OnButtonConvert класса CParseURLDlg void CParseURLDlg::OnButtonConvert() { // Получаем адрес, введенный в диалоговой панели UpdateData(TRUE); CString sServer; // Имя сервера CString sObject; // Имя объекта (с каталогами) INTERNET_PORT nPort; // Номер порта TCP/IP DWORD dwServiceType; // Тип сервиса или протокола // Разбираем адрес URL, записанный в строке m_UrlAddress if (!AfxParseURL(m_UrlAddress, dwServiceType, m_ServerName, m_ObjectName, nPort)) { AfxMessageBox("AfxParseURL Error"); m_ObjectName = ""; m_PortNumber = ""; m_ServerName = ""; m_ServiceType = ""; } else { switch(dwServiceType) { case AFX_INET_SERVICE_FTP: m_ServiceType = "AFX_INET_SERVICE_FTP"; break; case AFX_INET_SERVICE_HTTP: m_ServiceType = "AFX_INET_SERVICE_HTTP"; break; case AFX_INET_SERVICE_GOPHER: m_ServiceType = "AFX_INET_SERVICE_GOPHER"; break; case AFX_INET_SERVICE_FILE: m_ServiceType = "AFX_INET_SERVICE_FILE"; break; default: m_ServiceType.Format("%d", dwServiceType); } m_PortNumber.Format("%d", nPort); } // Выводим полученные значения в диалоговой панелли UpdateData(FALSE); } Для наглядности во время запуска приложения мы выполняем инициализацию поля ввода адреса URL, записывая в него строку http://host:80/bin/programm/main.htm. Этот адрес содержит все возможные элементы - тип протокола http, имя сервера host, номер порта 80, а также путь и имя объекта /bin/programm/main.htm, на который и указывает данный адрес URL. Соответствующий программный код, инициализирующий строку адреса надо добавить к методу OnInitDialog класса CParseURLDlg. Мы привели исходный текст этого метода в листинге 1.7. Листинг 1.7. Фрагмент файла Parse.cpp, метод OnInitDialog класса CParseURLDlg BOOL CParseURLDlg::OnInitDialog() { CDialog::OnInitDialog(); // . . . SetIcon(m_hIcon, TRUE); // Set big icon SetIcon(m_hIcon, FALSE); // Set small icon // Инициализируем поле адреса URL m_UrlAddress = "http://host:80/bin/programm/main.htm"; // Отображаем строку m_UrlAddress в диалоговой панели UpdateData(FALSE); return TRUE; } Теперь в исходный текст приложения внесены все необходимые изменения. Вы можете построить проект и запустить приложение. На экране появится диалоговая панель ParseURL, внешний вид которой мы уже приводили ранее на рисунке 4.4. Изначально в поле URL Address отображается адрес http://host:80/bin/programm/main.htm, который мы взяли в качестве примера. Вы можете изменить его по своему усмотрению - поменять тип протокола обмена, имя сервера, путь и имя объекта и номер порта. Вы даже можете попытаться ввести неправильный адрес, не соответствующий формату адресов URL. Нажмите кнопку Convert. Приложение разберет введенный вами адрес URL на составные части и выведет их в других полях диалоговой панели. Так в поле Service type будет показан тип сервиса (протокола обмена). Это может быть либо текстовая строка, либо число. В поле Server name вы увидите имя сервера, а в поле Object name - имя объекта, включая путь. Если в адрес URL включен номер порта, то он будет показан в поле Port number. Если вы введете в поле адреса URL неправильную строку, то на экране появится сообщение об ошибке, а поля Service type, Server name, Object name и Port number будут очищены. Когда вы закончите экспериментировать с разбором различных адресов URL завершите приложение. Для этого достаточно нажать кнопку OK. Как работает приложение ParseURLСтруктура приложения ParseURL ничем не отличается от других приложений с пользовательским интерфейсом на основе диалоговой панели, сформированных средствами MFC AppWizard. Поэтому мы не будем останавливаться на описании устройства приложения, и рассмотрим только моменты, непосредственно связанные с разбором адресов URL и отображением полученной информации в диалоговой панели. Сразу после запуска, приложение ParseURL создает диалоговую панель IDD_PARSEURL_DIALOG и отображает ее на экране. Управление этой диалоговой панелью осуществляет класс CParseURLDlg, определенный в нашем приложении. Конструктор класса CParseURLDlg инициализирует строки m_ObjectName, m_PortNumber, m_ServiceType, m_UrlAddress. Несколько позже, когда будет вызван метод OnInitDialog, мы запишем в строку m_UrlAddress пример адреса URL. Чтобы вывести этот адрес на экран, метод OnInitDialog вызывает метод UpdateData с параметром FALSE: UpdateData(FALSE); Когда пользователь нажимает кнопку Convert, командное сообщение от нее попадает в таблицу сообщений класса CParseURLDlg и для его обработки вызывается метод OnButtonConvert: ON_BN_CLICKED(IDC_BUTTON_CONVERT, OnButtonConvert) Метод OnButtonConvert опять вызывает метод UpdateData, но на этот раз в качестве параметра ему передается значение TRUE. Этот метод считывает данные из органов управления диалоговой панели и записывает их в соответствующие переменные. Вызов этого метода необходим, так как пользователь мог изменить адрес URL в поле URL Address диалоговой панели приложения: UpdateData(TRUE); Теперь в строке m_UrlAddress записан адрес URL, который необходимо разобрать на составные части. Для этого вызываем глобальную функцию AfxParseURL, передавая ей строку m_UrlAddress для разбора и переменные dwServiceType, m_ServerName, m_ObjectName, nPort. В них будет записан результат разбора адреса: if (!AfxParseURL(m_UrlAddress, dwServiceType, m_ServerName, m_ObjectName, nPort)) { } Обратите внимание, что имя сервера и имя объекта записываются непосредственно в переменные m_ServerName и m_ObjectName, привязанные к полям диалоговой панели. А вот тип сервиса (тип протокола) и номер порта записываются во временные переменные nPort и dwServiceType, так как их тип не соответствует строковому: INTERNET_PORT nPort; DWORD dwServiceType; Если в строке m_UrlAddress записан неправильный адрес, то функция AfxParseURL возвращает нулевое значение. В этом случае мы выводим сообщение об ошибке и сбрасываем строки m_ObjectName, m_PortNumber, m_ServerName, m_ServiceType: AfxMessageBox("AfxParseURL Error"); m_ObjectName = ""; m_PortNumber = ""; m_ServerName = ""; m_ServiceType = ""; Если адрес успешно разобран, мы проверяем тип сервиса dwServiceType и записываем в строку m_ServiceType соответствующий текст. Чтобы не загромождать приложение мы таким образом распознаем только четыре основных типа сервиса: switch(dwServiceType) { case AFX_INET_SERVICE_FTP: m_ServiceType = "AFX_INET_SERVICE_FTP"; break; case AFX_INET_SERVICE_HTTP: m_ServiceType = "AFX_INET_SERVICE_HTTP"; break; case AFX_INET_SERVICE_GOPHER: m_ServiceType = "AFX_INET_SERVICE_GOPHER"; break; case AFX_INET_SERVICE_FILE: m_ServiceType = "AFX_INET_SERVICE_FILE"; break; default: // Формируем строку из значения переменной dwServiceType m_ServiceType.Format("%d", dwServiceType); } Если вы указали другой тип сервиса мы записываем в строку m_PortNumber соответствующее числовое значение. Для этого мы используем метод Format класса CString: m_PortNumber.Format("%d", nPort); ¨ Метод Format класса CString очень удобен для записи в строку форматированных значений каких-либо переменных. По своему назначению и формату вызова данный метод напоминает хорошо известную вам функцию sprintf. Только в функции sprintf строка, в которую записываются форматированные данные, определяется первым параметром, а метод Format вызывается для объекта представляющего эту строку. После того, как все переменные, привязанные к полям редактирования диалоговой панели, заполнены, вызываем метод UpdateData с параметром FALSE. Он отображает их содержимое в диалоговой панели: UpdateData(FALSE); |