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

Для того чтобы скачать файл в C++ приложении с wxWinCE надо совсем немного кода. В простейшем случае для реализации однопоточной загрузки файла мы можем использовать класс wxURL, скормив ему адрес загружаемого ресурса.

void MobileDownloaderMainFrame::OnDOWNLOADClick( wxCommandEvent& event )
{
	do
	{
		wxString address = wxGetTextFromUser(_("Input URL"), 
			wxGetTextFromUserPromptStr, 
			wxT("http://wxwidgets.info"));
		if(address.IsEmpty()) break;
		wxURL url(address);
		if(!url.IsOk()) break;
		wxInputStream * stream = url.GetInputStream();
		if(!stream) break;
		wxString result;
		wxStringOutputStream out(&result);
		out.Write(*stream);
		delete stream;
		m_SingleThreadedResultTextCtrl->SetValue(result);
	}
	while(false);
}

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

Чтобы таких ситуаций не случалось можно использовать библиотеку wxDownloadFile, которая позволяет загружать каждый файл в отдельном потоке и получать уведомления о состоянии загрузки.

Наш следующий пример демонстрирует как можно реализовать многопоточную загрузку файлов с минимальными усилиями.

Для начала создадим класс, который будет содержать информацию о загрузке:

#ifndef _DOWNLOAD_INFO_H
#define _DOWNLOAD_INFO_H

#include <wx/wx.h>
#include <DownloadFile.h>
#include <DownloadEvent.h>

class DownloadInfo
{
private:
	wxInt64 m_DownloadedSize;
	wxString m_URL;
	wxDownloadEvent::DownloadSTATUS m_Status;
	wxDownloadFile * m_Downloader;
public:
	DownloadInfo(const wxString & url)
		: m_DownloadedSize(0), m_URL(url), 
		m_Status(wxDownloadEvent::DOWNLOAD_NONE), m_Downloader(NULL) {}

	const wxString & GetURL() {return m_URL;}

	wxInt64 GetDownloadedSize() {return m_DownloadedSize;}
	void SetDownloadedSize(wxInt64 value) {m_DownloadedSize = value;}

	wxDownloadEvent::DownloadSTATUS GetStatus() {return m_Status;}
	void SetStatus(wxDownloadEvent::DownloadSTATUS value) {m_Status = value;}

	wxDownloadFile * GetDownloader() {return m_Downloader;}
	void SetDownloader(wxDownloadFile * value) {m_Downloader = value;}
};

WX_DECLARE_STRING_HASH_MAP(DownloadInfo *, DownloadsHash);

#endif

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

Для отображения информации о загрузках мы будем использовать компонент wxSimpleHtmlListBox. Компонент будет содержать указатель на хеш-таблицу с загрузками.

Чтобы в списке загрузок отображалась актуальная информация, нам необходимо переопределить метод OnGetItem().

#include "DownloadInfo.h"
...
class wxDownloadListBox: public wxSimpleHtmlListBox
{
...
    DownloadsHash * GetDownloads() const { return m_Downloads ; }
    void SetDownloads(DownloadsHash * value) { m_Downloads = value ; }

    virtual wxString OnGetItem(size_t n) const;

    void AddNew() {SetItemCount(GetItemCount()+1);}

    DownloadsHash * m_Downloads;
};

wxString wxDownloadListBox::OnGetItem(size_t n) const
{
	wxString value;
	do 
	{
		DownloadsHash::iterator i = m_Downloads->begin();
		for(size_t j = 0; (j < n) && (i != m_Downloads->end()); j++)
		{
			i++;
		}
		if(i == m_Downloads->end()) break;
		value = wxString::Format(wxT("<b>%s</b>"), i->first.GetData());
		DownloadInfo * download = i->second;
		if(!download) break;
		switch(download->GetStatus())
		{
		case wxDownloadEvent::DOWNLOAD_INPROGRESS:
			value += wxString::Format(
				_(" - <i>downloaded %d bytes</i>"), 
				download->GetDownloadedSize());
			break;
		case wxDownloadEvent::DOWNLOAD_COMPLETE:
			value += _(" (finished)");
			break;
		case wxDownloadEvent::DOWNLOAD_FAIL:
			value += _(" (failed)");
			break;
		}
	}
	while (false);
	return value;
}

Метод AddNew() указывает компоненту, что у него добавился новый элемент.

Теперь рассмотрим код, создающий новую загрузку и добавляющий ее в список:

void MobileDownloaderMainFrame::OnADDClick( wxCommandEvent& event )
{
	do 
	{
		DownloadParametersDialog * dlg = new DownloadParametersDialog(this);
		int result = dlg->ShowModal();
		wxString url = dlg->m_URLTextCtrl->GetValue();
		wxString fileName = dlg->m_FileNamePicker->GetPath();
		dlg->Destroy();
		if(result != wxID_OK) break;
		DownloadInfo * info = m_Downloads[url];
		bool needAddNew(true);
		if(info != NULL)
		{
			info->GetDownloader()->CancelDownload();
			needAddNew = false;
		}
		else
		{
			info = new DownloadInfo(url);
		}
		wxDownloadFile * download = new wxDownloadFile(this, url, fileName, true);
		
		info->SetDownloader(download);
		m_Downloads[url] = info;

		download->Run();
		if(needAddNew)m_DownloadsListBox->AddNew();

		m_DownloadsListBox->RefreshLines(
			m_DownloadsListBox->GetFirstVisibleLine(),
			m_DownloadsListBox->GetLastVisibleLine());
	} 
	while (false);
}

Собственно, что у нас здесь происходит:

  • отображается диалог создания новой загрузки. В нем необходимо ввести URL и путь к результирующему файлу
  • В случае если диалог отработал успешно, мы проверяем наличие загрузки с указанным URL в хеш-таблице
  • Если загрузка присутствует, то она останавливается
  • Если загрузки с указанным URL в таблице нет, то создается новый объект DownloadInfo
  • Затем создается объект wxDownloadFile и запускается
  • После этого происходит обновление списка загрузок

Как уже было сказано ранее, класс wxDownloadFile позволяет получать уведомления о состоянии загрузки. Для этого используется обработчик события EVT_DOWNLOAD():

BEGIN_EVENT_TABLE( MobileDownloaderMainFrame, wxFrame )
...
EVT_DOWNLOAD(MobileDownloaderMainFrame::OnDownloadStatus)
END_EVENT_TABLE()

void MobileDownloaderMainFrame::OnDownloadStatus(wxDownloadEvent & event)
{
	do
	{
		wxString url = event.GetDownLoadURL();
		DownloadInfo * downloadInfo = m_Downloads[url];
		wxDownloadFile * download = downloadInfo->GetDownloader();
		if(!download) break;
		downloadInfo->SetStatus((wxDownloadEvent::DownloadSTATUS)event.GetDownLoadStatus());
		switch(event.GetDownLoadStatus())
		{
		case wxDownloadEvent::DOWNLOAD_FAIL:
			wxLogDebug(wxT("DOWNLOAD_FAIL"));
			downloadInfo->SetDownloader(NULL);
			break;
		case wxDownloadEvent::DOWNLOAD_COMPLETE:
			wxLogDebug(wxT("DOWNLOAD_COMPLETE"));
			downloadInfo->SetDownloader(NULL);
			break;
		case wxDownloadEvent::DOWNLOAD_INPROGRESS:
			wxLogDebug(wxT("DOWNLOAD_INPROGRESS"));
			downloadInfo->SetDownloadedSize(event.GetDownLoadedBytesCount());
			break;
		default:
			break;
		}
		m_DownloadsListBox->RefreshLines(
			m_DownloadsListBox->GetFirstVisibleLine(),
			m_DownloadsListBox->GetLastVisibleLine());
	}
	while(false);
}

В обработчике события мы получаем состояние загрузки из объекта wxDownloadEvent и, в зависимости от этого значения, выполняем обновление объекта DownloadInfo в хеш-таблице загрузок.

О чем еще важно помнить? Приложение остается висеть в памяти пока не будут завершены все его потоки. wxDownloadFile создает поток на каждую загрузку и если мы попытаемся закрыть приложение во время скачивания файлов, то приложение будет выгружено только по завершению всех потоков. Чтобы такой ситуации у нас не было, нам необходимо принудительно завершить все потоки при закрытии главной формы приложения. Для этого мы будем использовать обработчик EVT_CLOSE():

void MobileDownloaderMainFrame::OnCloseWindow( wxCloseEvent& event )
{
	for(DownloadsHash::iterator i = m_Downloads.begin(); i != m_Downloads.end(); i++)
	{
		DownloadInfo * download = i->second;
		if(!download) continue;
		if(download->GetDownloader()) download->GetDownloader()->CancelDownload();
		wxDELETE(download);
		i->second = NULL;
	}
    event.Skip();
}

Вот теперь все.
wxMobileDownloader - Пример использования wxDownloadFile
Скачать исходный код приложения и проекты для Win32 и Windows Mobile.

Previous ArticleNext Article
Технический директор IT-Dimension, компании-разработчика кросс-платформенного программного обеспечения

Leave a Reply

Your email address will not be published. Required fields are marked *

Р.

Русская версия Windows Mobile 6.5 в открытом доступе

В РУнете в открытом доступе появилась русифицированная бета-версия новой операционной системы Windows Mobile 6.5. Она предлагается для скачивания на сайтах 4PDA.ru и клубе пользователей техники RoverComputers — ProRover.ru.

Файл прошивки можно получить в обмен на соблюдение нескольких условий:

  • предоставление сведений о себе
  • предоставление сведений о вашем мобильном устройстве
  • подтверждение формальных условий распространения прошивки

Windows Mobile 6.5 - Screenshots

Р.

Работаем с LED-индикаторами устройства под управлением Windows Mobile

Для того чтобы управлять LED-индикаторами устройства, в Windows Mobile предусмотрено специальное API:

BOOL WINAPI NLedSetDevice(UINT nDeviceId, void* pInput);

Первый параметр, nDeviceID указывает на то, какие данные передаются в параметре pInput. Для того чтобы установить состояние LED-индикатора, параметр nDeviceID должен иметь значение NLED_SETTINGS_INFO_ID, а в качестве параметра pInput необходимо передать указатель на структуру NLED_SETTINGS_INFO, содержащую информацию о новом состоянии LED-индикатора.

struct NLED_SETTINGS_INFO {
UINT LedNum;
INT OffOnBlink;
LONG TotalCycleTime;
LONG OnTime;
LONG OffTime;
INT MetaCycleOn;
INT MetaCycleOff;
};
  • LedNum – содержит индекс LED-индикатора (zero-based).
  • OffOnBlink – содержит новое состояние индикатора (0 – выключен, 1 – включен, 2 – мигающий
  • TotalCycleTime – длительность цикла мерцания в миллисекундах
  • OnTime – длительность включенного состояния индикатора при мерцании
  • OffTime – длительность выключенного состояния индикатора при мерцании
  • MetaCycleOn – количество ON-циклов
  • MetaCycleOff – количество OFF-циклов

Вобще описание назначения последних двух полей структуры вводит меня в недоумение. В MSDN по этому поводу всего две скупые фразы “Number of on blink cycles.” и “Number of off blink cycles.” без какого-либо дальнейшего пояснения.

Для того чтобы получить количество LED-индикаторов в системе можно воспользоваться функцией NLedGetDeviceInfo:

BOOL WINAPI NLedGetDeviceInfo(UINT nInfoId, void* pOutput);

В качестве первого параметра необходимо передать значение NLED_COUNT_INFO_ID, в качестве второго параметра – указатель на структуру NLED_COUNT_INFO, которая, в случае успешного завершения работы функции, будет содержать количество индикаторов.

Ну вот, с теоретической частью закончили, теперь перейдем к рассмотрению простенького примера. А в качестве примера у нас будет небольшая библиотека-обертка над описанным выше API.

wxMobileLED.h

#ifndef _WX_MOBILE_LED_H
#define _WX_MOBILE_LED_H

class wxMobileLEDImpl;

class wxMobileLED
{
	wxMobileLEDImpl * m_Impl;
public:
	wxMobileLED();
	~wxMobileLED();
	size_t GetCount();
	bool SetLED(size_t index, bool value, bool blink = false);
};

#endif

wxMobileLED.cpp

#include "wxMobileLED.h"
#if defined(__WXWINCE__)
#include "wxMobileLEDImplWinCE.h"
#else
#endif

wxMobileLED::wxMobileLED()
{
	m_Impl = 
#if defined(__WXWINCE__)
		new wxMobileLEDImplWinCE;
#else
		NULL;
#endif
}

wxMobileLED::~wxMobileLED()
{
	if(m_Impl) delete m_Impl;
}

size_t wxMobileLED::GetCount()
{
	if(m_Impl)
	{
		return m_Impl->GetCount();
	}
	return 0;
}

bool wxMobileLED::SetLED(size_t index, bool value, bool blink)
{
	if(m_Impl)
	{
		return m_Impl->SetLED(index, value, blink);
	}
	return false;
}

wxMobileLEDImpl.h

#ifndef _WX_MOBILE_LED_IMPL_H
#define _WX_MOBILE_LED_IMPL_H

class wxMobileLEDImpl
{
public:
	virtual size_t GetCount() = 0;
	virtual bool SetLED(size_t index, bool value, bool blink = false) = 0;
};

#endif

wxMobileLEDImplWinCE.h

#ifndef _WX_MOBILE_LED_IMPL_WINCE_H
#define _WX_MOBILE_LED_IMPL_WINCE_H

#include "wxMobileLEDImpl.h"

class wxMobileLEDImplWinCE : public wxMobileLEDImpl
{
public:
	virtual size_t GetCount();
	virtual bool SetLED(size_t index, bool value, bool blink = 0);
};

#endif

wxMobileLEDImplWinCE.cpp

#include "wxMobileLEDImplWinCE.h"
#include <windows.h>
#include <NLed.h>

size_t wxMobileLEDImplWinCE::GetCount()
{
#if defined(__WXWINCE__) && (UNDER_CE >= 0x500)
	NLED_COUNT_INFO count = {0};
	NLedGetDeviceInfo(NLED_COUNT_INFO_ID,&count); 
	return count.cLeds;
#else
	return 0;
#endif
}

bool wxMobileLEDImplWinCE::SetLED(size_t index, bool value, bool blink)
{
#if defined(__WXWINCE__) && (UNDER_CE >= 0x500)
	do
	{
		if(index >= GetCount()) break;
		NLED_SETTINGS_INFO info;
		ZeroMemory(&info, sizeof(NLED_SETTINGS_INFO));
		info.LedNum = index;
		info.OffOnBlink = value ? (blink ? 2 : 1) : 0;
		return (NLedSetDevice(NLED_SETTINGS_INFO_ID, &info) == TRUE);
	}
	while(false);
#endif
	return false;
}

Пример использования библиотеки:


wxMobileLED m_MobileLED;

...
void wxMobileLEDTestMainFrame::FillLEDList()
{
	size_t ledCount = m_MobileLED.GetCount();
	for(size_t i = 0; i < ledCount; i++)
	{
		int item = m_LEDList->Append(wxString::Format(wxT("LED %i"), i+1));
	}
}

void wxMobileLEDTestMainFrame::UpdateLEDs()
{
	for(size_t i = 0; i < m_LEDList->GetCount(); i++)
	{
		bool value = m_LEDList->IsChecked(i);
		if(!m_MobileLED.SetLED(i, value, m_BlinkCheckBox->GetValue()))
		{
			wxLogTrace(wxTraceMask(), _("Unable to turn LED #%i %s"), i, 
				value ? _("ON") : _("OFF"));
		}
	}
}

Исходный код примера для wxWinCE, а также исполняемый файл для Windows Mobile 6 можно взять здесь.

PS: Хотя описанное выше API предназначено для управления состоянием LED-индикаторов и на эмуляторе действительно управляет ими, но на реальном устройстве (ETEN Glofiish X800 под управлением Windows Mobile 6) включение/віключение индикатора с индексом 1 приводит к включению/віключению вибро. Мая шоке %).