Для того чтобы управлять 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 приводит к включению/віключению вибро. Мая шоке %).

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

This post has 2 Comments

2
  1. Так и есть, ведь вибро – это индикатор с состоянием вкл/выкл.

    На счет моста – несовсем, случай вырожденный, для других платформ он не определен. Достаточно было бы простого наследования.

  2. Так же ж по поводу моста, планировалось же “на вырост”, чтобы еще под Linux дописать аналог.

Leave a Reply

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

К.

Кросс-платформенная разработка — Windows Mobile и Windows (.NET Compact Framework, C#)

В этот раз статья от Андрея Коновалова о том, как сделать мультиплатформенный проект в Visual Studio с использованием .NET Compact Framework.
Не так много разработчиков осознают, что разрабатывая приложения для платформы Windows Mobile с использованием Compact Framework, у них существуют шансы собрать это же приложение под десктоп версию Windows! Я и сам об этом долгое время только задумывался, предполагая, что подобная возможность есть, но не рассматривал её как нечто, хоть сколько-нибудь реальное.

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

Во-первых, во-вторых, в-третьих…

Во-первых, необходимо изначально создавать приложение для Windows Mobile (т.е. это основная платформа). Это действительно важно. Причин несколько, но основная заключается в том, что Compact Framework на то и компактный, что там существенно меньше классов и свойств у классов. Т.е. совместимость с десктопом есть, но односторонняя, т.е. только в сторону десктопа.

Во-вторых, нужно понимать, что отличия в приложении всё-таки будут и их придётся программировать отдельно. Например, стандартное меню, находящееся внизу на Windows Mobile автоматически перемещается наверх, и там Cancel и More выглядят не очень привлекательно. Далее, на десктопе в принципе нет InputPanel. Т.е. по сути нужно быть готовым к инструкциям компилятору #if #else #endif.

В-третьих, надо также готовиться к тому, что будут некоторые ограничения, касающиеся дизайна форм. А именно, нельзя открывать форму визуальным редактором при десктопе, выбранном в качестве текущего таргета — сразу же в *.Designer.cs налетит множество свойств, не поддерживаемых мобильным фреймворком — придётся откатывать.

В-четвёртых, придётся вручную править *.csproj файлы и видеть в Solution Explorer-е жёлтые треугольники — это нормально 🙂

В пятых, не все сборки и неймспесы на 100% работают на десктопе. Например, я совершенно не уверен в том, что SQL Server Compact собирается на десктопе. Не проверял, поэтому не обещаю. Точно знаю, что с SQLite всё хорошо (хотя и придётся попотеть немного).

В-шестых, нужно разобраться, как можно отлаживать приложение на десктопе, ведь все знают, что при сборке мобильного приложения необходимо выбрать некое устройство, физическое или эмулятор!

Попробуем разобраться с основными тонкостями.

Desktop Target

Начнём с того, что у нас должен быть некоторый проект, созданный для Compact Framework. Создадим новый таргет через Build->Configuration Manager:

Windows Mobile and Windows - Configuration ManagerПосле этого добавим символ условной компиляции в свойствах проекта:

Windows Mobile and Windows - Project SettingsНачало положено. Посмотрим теперь, что делать, чтобы обеспечить подключение правильных сборок в нужном таргете. По умолчанию в нашем csproj файле нет никаких разделений по таргетам:

 <ItemGroup>
  <Reference Include="mscorlib" />
  <Reference Include="System" />
  <Reference Include="System.Data" />
  <Reference Include="System.Drawing" />
  <Reference Include="System.Windows.Forms" />
  <Reference Include="System.Xml" />
 </ItemGroup>

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

<ItemGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
  <Reference Include="mscorlib">
   <Private>False</Private>
  </Reference>
  <Reference Include="Microsoft.WindowsMobile, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35">
   <SpecificVersion>False</SpecificVersion>
   <HintPath>..\..\..\..\..\Program Files\Windows Mobile 6 SDK\Managed Libraries\Microsoft.WindowsMobile.dll</HintPath>
  </Reference>
  <Reference Include="Microsoft.WindowsMobile.Status, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35">
   <SpecificVersion>False</SpecificVersion>
   <HintPath>..\..\..\..\..\Program Files\Windows Mobile 6 SDK\Managed Libraries\Microsoft.WindowsMobile.Status.dll</HintPath>
  </Reference>
  <Reference Include="Microsoft.WindowsCE.Forms">
   <Private>True</Private>
  </Reference>
  <Reference Include="System" />
  <Reference Include="System.Data">
   <Private>False</Private>
  </Reference>
  <Reference Include="System.Windows.Forms" />
  <Reference Include="System.Drawing" />
  <Reference Include="System.Data.SQLite, Version=1.0.60.0, Culture=neutral, PublicKeyToken=1fdb50b1b62b4c84, processorArchitecture=MSIL">
   <Private>True</Private>
   <HintPath>..\..\..\..\..\Program Files\SQLite.NET\bin\CompactFramework\SQLite.Interop.060.DLL</HintPath>
  </Reference>
 </ItemGroup>

 <ItemGroup Condition=" '$(Configuration)|$(Platform)' == 'Desktop|AnyCPU' ">
  <Reference Include="mscorlib">
   <Private>False</Private>
   <HintPath>C:\Windows\Microsoft.NET\Framework\v2.0.50727\mscorlib.dll</HintPath>
  </Reference>
  <Reference Include="System, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089, processorArchitecture=x86">
   <HintPath>C:\Windows\Microsoft.NET\Framework\v2.0.50727\System.dll</HintPath>
  </Reference>
  <Reference Include="System.Windows.Forms">
   <HintPath>C:\Windows\Microsoft.NET\Framework\v2.0.50727\System.Windows.Forms.dll</HintPath>
  </Reference>
  <Reference Include="System.Drawing">
   <HintPath>C:\Windows\Microsoft.NET\Framework\v2.0.50727\System.Drawing.dll</HintPath>
  </Reference>
  <Reference Include="System.Data, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089, processorArchitecture=x86" />
  <Reference Include="System.Data.SQLite, Version=1.0.60.0, Culture=neutral, PublicKeyToken=db937bc2d44ff139, processorArchitecture=x86">
   <Private>True</Private>
   <HintPath>..\..\..\..\..\Program Files\SQLite.NET\bin\System.Data.SQLite.DLL</HintPath>
  </Reference>
 </ItemGroup>

Как видно, мы разделяем блоки ItemGroup по таргетам и указываем разные пути до сборок. Признаюсь, что именно здесь я возился дольше всего в моём проекте. Правильные версии выдирались прямо из сообщений об ошибках компиляции 😉 Т.е. компилятор ругался, что не находятся такие-то нужные сборки и я подставлял правильные значения. И именно здесь я несколько раз порывался бросить всё, т.к. гуглятся подобные ошибки очень плохо.

В результате хитрых манипуляций с csproj файлом получается такая ерунда в Solution Explorer:
Windows Mobile and Windows - Warnings
Это нормально, т.к. Visual Studio не совсем пригодна к таким извращениям (хотя и позволяет их в итоге).

#if #endif

Когда сборки подключаются правильные, мы имеем возможность для каждой платформы использовать тот максимум, который каждая из платформ поддерживает. Напомню, однако, что автоматическая кодогенерация дизайнера форм нам тут всё портит. Поэтому необходимо, по возможности, сначала всё надизайнить, а потом уже править только руками — ведь при перегенерации *.Designer.cs, студия не сохраняет наши правки и добавления #if endif участков.

Немного хитрый момент. Мой проект поддерживает как QVGA, так и VGA разрешение, однако, в Designer.cs у форм свойство ClientSize всегда соответствует QVGA разрешению. На десктопе же иметь окно размером 240х268 как-то неправильно, особенно, когда есть VGA-скин. Поэтому в конструкторе после InitializeComponent() я помещаю участок условной компиляции:

Size vertSize = new Size(480, 560);
Size horisSize = new Size(640, 480);
[…]
InitializeComponent();
#if Desktop
this.ClientSize = vertSize;
this.FormBorderStyle = FormBorderStyle.Fixed3D;
this.MaximizeBox = false;
this.MouseWheel += new MouseEventHandler(MainController_MouseWheel);
#endif

Как видно, у меня объявлено две переменные типа Size. Зачем это нужно? Просто-напросто, у меня по F9 происходит переключение ClientSize, для эмуляции смены ориентации экрана. Полезно, когда необходимо протестировать OnResize. Да и в конце концов, есть же нетбуки с 800х480, для них ландшафтная ориентация — единственно возможная, чтобы всё поместилось на экране 🙂

Также видно, что MouseWheel тоже обрабатывается только на десктопе.

System.Diagnostics.Conditional

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

[Conditional("Desktop")]
public void SomeDesktopMethod()
{
}

В данном способе хорошо то, что вызовы данного метода могут существовать в коде, но при наличии данного атрибута, вызовы будут просто проигнорированы на других таргетах! Альтернатива с использованием #if #endif предполагает, что везде, где нужен вызов, необходимо проставить проверку, чтобы компилировать или не компилировать вызов на нужной платформе.

Отладка на десктопе

Существует два способа отладки. Первый (неудобный) заключается в том, мы идём в bin\Desktop\, запускаем exe-файл и потом в студии говорим Debug — Attatch to process. Этот способ сначала кажется единственно возможным. Но! Есть мега-хак, неофициальный и неподдерживаемый способ (который, тем не менее, работает как в VS2005, так и в VS2008). Способ следующий:

  • В студии открыть Tools/Options, далее в дереве выбрать Device Tools/Devices.
  • В выпадающем меню выбрать платформу, для которой необходимо организовать отладку на десктопе. Это будет необходимо повторить для всех платформ, где необходимо.
  • Далее нужно выбрать любой из эмуляторов и нажать [Save As…]. Удобно назвать копию “My Computer”.
  • Теперь надо закрыть студию и открыть %USERPROFILE%\Local Settings\Application Data\Microsoft\CoreCon\1.0\conman_ds_platform.xsl файл в текстовом редакторе.
  • В файле необходимо найти элемент, соответствующий свежесозданному “дивайсу”
  • Далее добавляем следующую строку — true сразу после тега .
  • Сохраняем conman_ds_platform.xsl и перезапускаем студию

Ну вот, теперь нам доступен отладчик и все вкусности десктопной отладки.

Заключение

Чтож, в статье перечислены основные подводные камни, с которыми я столкнулся в процессе сборки своего проекта под Windows. Далее всё было делом техники — ловил исключения и разбирался в их причине. Среди них были не найденные пути, которые, очевидно, на десктопе отличаются и т.д., но это всё уже было ничто, по сравнению с начальными проблемами.

PS. Почти всё, что описано выше, я выстрадал в результате долгих поисков, и вот, в самом конце, когда я искал способ отладки на десктопе, я натолкнулся на шикарную статью Дениела Моса о кросс-платформенной компиляции для Compact Framework 🙂 Моя статья ни в коем случае не является переводом, однако, не могу не дать на неё ссылку.

Оригинал статьи на Хабре.

W.

Windows Marketplace for Mobile доступен для WM 6.0 и WM 6.1

Windows Marketplace for MobileКак известно, запущенный ранее Windows Marketplace for Mobile был направлен на работу с Windows Mobile 6.5, но теперь пользователи Windows Mobile 6.0 и 6.1 также смогут пользоваться этим сервисом.

Если у вас смартфон на базе Windows Mobile 6 и выше, то зайдя с него на адрес mp.windowsphone.com вы можете установить небольшой клиент, который позволит вам производить навигацию по магазину.

На данный момент доступно около 800 приложений. Пользователям с русскими аккаунтами доступны только бесплатные приложения.

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