В этот раз статья от Андрея Коновалова о том, как сделать мультиплатформенный проект в 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 🙂 Моя статья ни в коем случае не является переводом, однако, не могу не дать на неё ссылку.

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

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

This post has 1 Comment

1
  1. SqlCE имеет две версии дллок – для девайсов и для десктопа. Соответственно версии одинаковы, а вот пути к дллкам в референсах разные.

Leave a Reply

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

О.

Отправка USSD-запроса в Windows Mobile

В этот раз будем учиться делать странное, а именно отправлять USSD-запрос оператору мобильной связи с помощью TAPI в Windows Mobile.

USSD (Unstructured Supplementary Service Data) — стандартный сервис в сетях GSM, позволяющий организовать интерактивное взаимодействие между абонентом сети и сервисным приложением в режиме передачи коротких сообщений. – Wikipedia

На самом деле USSD-запросы это то, чем каждый из нас пользуется довольно часто, например, при пополнении счета или при запросе состояния счета у оператора мобильной связи.

Примеры USSD-запросов:

  • *101#
  • *100*111122223333444455#

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

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

Для начала нам надо инициализировать TAPI.

#include <windows.h>
#include <tapi.h>
#include <extapi.h>
#include <tsp.h>

#define EXT_API_LOW_VERSION     0x00010000
#define EXT_API_HIGH_VERSION    0x00010000

long InitializeTAPI(HLINEAPP & lineApp, DWORD & lowAPIVersion, DWORD & deviceCount)
{
	LINEINITIALIZEEXPARAMS	lineParams;
	ZeroMemory(&lineParams,sizeof(LINEINITIALIZEEXPARAMS));

	lineParams.dwTotalSize	= sizeof(LINEINITIALIZEEXPARAMS);
	lineParams.dwOptions	= LINEINITIALIZEEXOPTION_USEHIDDENWINDOW; 

	lowAPIVersion = TAPI_CURRENT_VERSION;

	return lineInitializeEx(&lineApp,
		(HINSTANCE)GetModuleHandle(NULL),
		LineCallback,
		L"wxUSSDRequest",
		&deviceCount,
		&lowAPIVersion,
		&lineParams);
}

LineCallback – это callback-функция, которая будет вызываться при наступлении событий TAPI.

void FAR PASCAL LineCallback(DWORD hDevice,
							 DWORD dwMsg,
							 DWORD dwCallbackInstance,
							 DWORD dwParam1,
							 DWORD dwParam2,
							 DWORD dwParam3)
{
	switch(dwMsg)
	{
	case LINE_DEVSPECIFIC:
		{
			if(dwParam1 == LINE_USSD)
			{
				DWORD	dwMessageId	= dwParam2;
			}

			break;
		}
	case LINE_REPLY:
		{
			if(dwParam2 == 0) return;
			wprintf(L"Error: ");	
			switch(dwParam2)
			{
			case LINEERR_INVALLINEHANDLE:		wprintf(L"LINEERR_INVALLINEHANDLE");	break;
			case LINEERR_NOMEM:					wprintf(L"LINEERR_NOMEM");				break;
			case LINEERR_OPERATIONUNAVAIL:		wprintf(L"LINEERR_OPERATIONUNAVAIL");	break;
			case LINEERR_OPERATIONFAILED:		wprintf(L"LINEERR_OPERATIONFAILED");	break;
			case LINEERR_RESOURCEUNAVAIL:		wprintf(L"LINEERR_RESOURCEUNAVAIL");	break;
			case LINEERR_INVALPOINTER:			wprintf(L"LINEERR_INVALPOINTER");		break;
			case LINEERR_INVALPARAM:			wprintf(L"LINEERR_INVALPARAM");			break;
			case LINEERR_UNINITIALIZED:			wprintf(L"LINEERR_UNINITIALIZED");		break;
			default:							wprintf(L"Error: %x",dwParam2);			break;
			}
			wprintf(L"\r\n");
			break;
		}
	}
}

Затем нужно получить идентификатор телефонной линии. Сделать это можно с помощью функции lineGetDevCaps.

long GetCellularLineID(HLINEAPP lineApp,
					   DWORD lowAPIVersion,
					   DWORD deviceCount,
					   DWORD & apiVersion)
{
	DWORD				dwReturn		= 0xFFFFFFFF;
	long				lResult			= 0;
	LINEEXTENSIONID		sLineExt		= {0};
	LPLINEDEVCAPS		lpLineDevCaps	= NULL;	
	BOOL				bContinue		= TRUE;

	for(DWORD dwLine = 0; dwLine < deviceCount && bContinue; ++dwLine)
	{
		lResult		= lineNegotiateAPIVersion(lineApp,
			dwLine,
			lowAPIVersion,
			TAPI_CURRENT_VERSION,
			&apiVersion,
			&sLineExt);

		if(0 == lResult)
		{
			lpLineDevCaps	= (LPLINEDEVCAPS)LocalAlloc(LPTR,sizeof(LINEDEVCAPS));
			lResult			= LINEERR_STRUCTURETOOSMALL;

			lpLineDevCaps->dwTotalSize	= sizeof(LINEDEVCAPS);
			lpLineDevCaps->dwNeededSize	= sizeof(LINEDEVCAPS);

			while(LINEERR_STRUCTURETOOSMALL == lResult)
			{
				lResult	= lineGetDevCaps(lineApp,dwLine,TAPI_CURRENT_VERSION,0,lpLineDevCaps);

				if(LINEERR_STRUCTURETOOSMALL == lResult || lpLineDevCaps->dwTotalSize < lpLineDevCaps->dwNeededSize)
				{
					lpLineDevCaps	= (LPLINEDEVCAPS)LocalReAlloc(lpLineDevCaps,lpLineDevCaps->dwNeededSize,LMEM_MOVEABLE);
					lResult			= LINEERR_STRUCTURETOOSMALL;

					lpLineDevCaps->dwTotalSize	= lpLineDevCaps->dwNeededSize;
				}
			}

			if(0 == lResult)
			{
				TCHAR szName[512];

				memcpy((PVOID)szName,(PVOID)((BYTE*)lpLineDevCaps + lpLineDevCaps ->dwLineNameOffset), 
					lpLineDevCaps->dwLineNameSize);

				szName[lpLineDevCaps->dwLineNameSize]	= 0;

				if(_tcscmp(szName,CELLTSP_LINENAME_STRING) == 0)
				{
					dwReturn	= dwLine;
					bContinue	= FALSE;
				}
			}

			LocalFree((HLOCAL)lpLineDevCaps);
		}
	}

	return dwReturn;
}

Теперь нужно открыть телефонную линию для последующей работы с ней. Делается это с помощью функции lineOpen.

HLINE OpenTAPILine(HLINEAPP lineApp, DWORD cellularID, DWORD apiVersion)
{
	DWORD	dwMediaMode = LINEMEDIAMODE_INTERACTIVEVOICE;
	HLINE	hLine		= NULL;
	DWORD	extVersion	= 0;
	long	lReturn		= lineOpen(lineApp, 
		cellularID, 
		&hLine,
		TAPI_CURRENT_VERSION, 0,
		(DWORD)NULL,
		LINECALLPRIVILEGE_OWNER,
		dwMediaMode, 0);

	lReturn = ::lineNegotiateExtVersion(lineApp,
		cellularID,
		apiVersion, 
		EXT_API_LOW_VERSION, 
		EXT_API_HIGH_VERSION, 
		&extVersion);

	return hLine;
}

После завершения работы с TAPI необходимо обязательно закрыть все хэндлы.

void ShutdownTAPI(HLINE cellularLine, HLINEAPP lineApp)
{
	if(cellularLine)
	{
		lineClose(cellularLine);
	}

	if(lineApp)
	{
		lineShutdown(lineApp);
	}
}

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

int wmain()
{
	HLINEAPP lineApp = NULL;
	DWORD lowAPIVersion = 0;
	DWORD apiVersion = 0;
	DWORD deviceCount = 0;
	DWORD cellularID = 0;
	HLINE cellularLine = NULL;
	TCHAR command[] = _T("*101#");
	int errorCode;

	do
	{
		if(InitializeTAPI(lineApp, lowAPIVersion, deviceCount) != 0) 
		{
			errorCode = 1;
			break;
		}
		cellularID = GetCellularLineID(lineApp, lowAPIVersion, deviceCount, apiVersion);
		if(cellularID == 0xFFFFFFFF)
		{
			errorCode = 2;
			break;
		}
		cellularLine = OpenTAPILine(lineApp, cellularID, apiVersion);
		if(cellularLine == NULL)
		{
			errorCode = 3;
			break;
		}
		// Супер-строчка, которая отправляет USSD-запрос !!! Уиииии!
		if(lineSendUSSD(cellularLine, (const BYTE* const)command, sizeof(command), 0) < 0)
		{
			errorCode = 4;
			break;
		}
		return 0;
	}
	while(false);
	ShutdownTAPI(cellularLine, lineApp);
	return errorCode;
}
&#91;/sourcecode&#93;
И вот, вызов <a href="http://msdn.microsoft.com/en-us/library/ms862581.aspx" title="Отправка USSD запроса">lineSendUSSD</a> - это то к чему мы так стремились. Функция принимает в качестве параметров хэндл телефонной линии, строку запроса и длину запроса. В случае успешного завершения работы возвращается нулевой результат. В случае ошибки возвращается отрицательное значение.

И в качестве дополнения ко всему сказанному выше, класс-обертка для wxWinCE:

<strong>wxUSSDRequest.h</strong>

#ifndef _WX_USSD_REQUEST_H
#define _WX_USSD_REQUEST_H

#include <wx/wx.h>
#include <tapi.h>
#include <extapi.h>
#include <tsp.h>

class wxUSSDRequest
{
	long InitializeTAPI(DWORD & lowAPIVersion,
		DWORD & deviceCount);
	long GetCellularLineID(DWORD lowAPIVersion,
		DWORD deviceCount,
		DWORD & apiVersion);
	HLINE OpenTAPILine(DWORD cellularID, DWORD apiVersion);

	void ShutdownTAPI();

	static void FAR PASCAL LineCallback(DWORD hDevice,
		DWORD dwMsg,
		DWORD dwCallbackInstance,
		DWORD dwParam1,
		DWORD dwParam2,
		DWORD dwParam3);
public:
	wxUSSDRequest();
	~wxUSSDRequest();

	bool SendUSSDCommand(const TCHAR * command, DWORD length);
	int Init();
	bool IsOK();
private:
	HLINEAPP m_LineApp;
	HLINE m_CellularLine;
	bool m_IsOK;
};

#endif

wxUSSDRequest.cpp

#include "wxUSSDRequest.h"

#define EXT_API_LOW_VERSION     0x00010000
#define EXT_API_HIGH_VERSION    0x00010000

wxUSSDRequest::wxUSSDRequest()
: m_LineApp(NULL), m_CellularLine(NULL), m_IsOK(false)
{
	m_IsOK = (Init() == 0);
}

wxUSSDRequest::~wxUSSDRequest()
{
	ShutdownTAPI();
}

int wxUSSDRequest::Init()
{
	int result(0);
	do
	{
		DWORD lowAPIVersion(0);
		DWORD deviceCount(0);
		if(InitializeTAPI(lowAPIVersion, deviceCount) != 0) 
		{
			result = 1;
			break;
		}
		DWORD apiVersion(0);
		DWORD cellularID = GetCellularLineID(lowAPIVersion, deviceCount, apiVersion);
		if(cellularID == 0xFFFFFFFF) 
		{
			result = 2;
			break;
		}
		m_CellularLine = OpenTAPILine(cellularID, apiVersion);
		if(m_CellularLine == NULL) 
		{
			result = 3;
			break;
		}
	}
	while(false);
	return result;
}

bool wxUSSDRequest::IsOK()
{
	return m_IsOK;
}

bool wxUSSDRequest::SendUSSDCommand(const TCHAR * command, DWORD length)
{
	if(lineSendUSSD(m_CellularLine, (const BYTE* const)command, length, 0) < 0)
	{
		return false;
	}
	return true;
}

long wxUSSDRequest::InitializeTAPI(DWORD & lowAPIVersion, DWORD & deviceCount)
{
	LINEINITIALIZEEXPARAMS	lineParams;
	ZeroMemory(&lineParams,sizeof(LINEINITIALIZEEXPARAMS));

	lineParams.dwTotalSize	= sizeof(LINEINITIALIZEEXPARAMS);
	lineParams.dwOptions	= LINEINITIALIZEEXOPTION_USEHIDDENWINDOW; 

	lowAPIVersion = TAPI_CURRENT_VERSION;

	return lineInitializeEx(&m_LineApp,
		(HINSTANCE)wxTheApp->GetInstance(),
		LineCallback,
		wxT("wxUSSDRequest"),
		&deviceCount,
		&lowAPIVersion,
		&lineParams);
}

void wxUSSDRequest::ShutdownTAPI()
{
	if(m_CellularLine)
	{
		lineClose(m_CellularLine);
	}

	if(m_LineApp)
	{
		lineShutdown(m_LineApp);
	}

	m_LineApp		= NULL;
	m_CellularLine	= NULL;
}

long wxUSSDRequest::GetCellularLineID(DWORD lowAPIVersion,
									  DWORD deviceCount,
									  DWORD & apiVersion)
{
	DWORD				dwReturn		= 0xFFFFFFFF;
	long				lResult			= 0;
	LINEEXTENSIONID		sLineExt		= {0};
	LPLINEDEVCAPS		lpLineDevCaps	= NULL;	
	BOOL				bContinue		= TRUE;

	for(DWORD dwLine = 0; dwLine < deviceCount && bContinue; ++dwLine)
	{
		lResult		= lineNegotiateAPIVersion(m_LineApp,
			dwLine,
			lowAPIVersion,
			TAPI_CURRENT_VERSION,
			&apiVersion,
			&sLineExt);

		if(0 == lResult)
		{
			lpLineDevCaps	= (LPLINEDEVCAPS)LocalAlloc(LPTR,sizeof(LINEDEVCAPS));
			lResult			= LINEERR_STRUCTURETOOSMALL;

			lpLineDevCaps->dwTotalSize	= sizeof(LINEDEVCAPS);
			lpLineDevCaps->dwNeededSize	= sizeof(LINEDEVCAPS);

			while(LINEERR_STRUCTURETOOSMALL == lResult)
			{
				lResult	= lineGetDevCaps(m_LineApp,dwLine,TAPI_CURRENT_VERSION,0,lpLineDevCaps);

				if(LINEERR_STRUCTURETOOSMALL == lResult || lpLineDevCaps->dwTotalSize < lpLineDevCaps->dwNeededSize)
				{
					lpLineDevCaps	= (LPLINEDEVCAPS)LocalReAlloc(lpLineDevCaps,lpLineDevCaps->dwNeededSize,LMEM_MOVEABLE);
					lResult			= LINEERR_STRUCTURETOOSMALL;

					lpLineDevCaps->dwTotalSize	= lpLineDevCaps->dwNeededSize;
				}
			}

			if(0 == lResult)
			{
				TCHAR szName[512];

				memcpy((PVOID)szName,(PVOID)((BYTE*)lpLineDevCaps + lpLineDevCaps ->dwLineNameOffset), 
					lpLineDevCaps->dwLineNameSize);

				szName[lpLineDevCaps->dwLineNameSize]	= 0;

				if(_tcscmp(szName,CELLTSP_LINENAME_STRING) == 0)
				{
					dwReturn	= dwLine;
					bContinue	= FALSE;
				}
			}

			LocalFree((HLOCAL)lpLineDevCaps);
		}
	}

	return dwReturn;
}

HLINE wxUSSDRequest::OpenTAPILine(DWORD cellularID, DWORD apiVersion)
{
	DWORD	dwMediaMode = LINEMEDIAMODE_INTERACTIVEVOICE;
	HLINE	hLine		= NULL;
	DWORD	extVersion	= 0;
	long	lReturn		= lineOpen(m_LineApp, 
		cellularID, 
		&hLine,
		TAPI_CURRENT_VERSION, 0,
		(DWORD)this,
		LINECALLPRIVILEGE_OWNER,
		dwMediaMode, 0);

	lReturn				= ::lineNegotiateExtVersion(m_LineApp,
		cellularID,
		apiVersion, 
		EXT_API_LOW_VERSION, 
		EXT_API_HIGH_VERSION, 
		&extVersion);

	return hLine;
}

void FAR PASCAL wxUSSDRequest::LineCallback(DWORD hDevice,
											DWORD dwMsg,
											DWORD dwCallbackInstance,
											DWORD dwParam1,
											DWORD dwParam2,
											DWORD dwParam3)
{
	switch(dwMsg)
	{
	case LINE_DEVSPECIFIC:
		{
			if(dwParam1 == LINE_USSD)
			{
				DWORD	dwMessageId	= dwParam2;
			}

			break;
		}
	case LINE_REPLY:
		{
			wxString strError;

			switch(dwParam2)
			{
			case 0:								strError	= wxEmptyString;						break;
			case LINEERR_INVALLINEHANDLE:		strError	= wxT("LINEERR_INVALLINEHANDLE");		break;
			case LINEERR_NOMEM:					strError	= wxT("LINEERR_NOMEM");					break;
			case LINEERR_OPERATIONUNAVAIL:		strError	= wxT("LINEERR_OPERATIONUNAVAIL");		break;
			case LINEERR_OPERATIONFAILED:		strError	= wxT("LINEERR_OPERATIONFAILED");		break;
			case LINEERR_RESOURCEUNAVAIL:		strError	= wxT("LINEERR_RESOURCEUNAVAIL");		break;
			case LINEERR_INVALPOINTER:			strError	= wxT("LINEERR_INVALPOINTER");			break;
			case LINEERR_INVALPARAM:			strError	= wxT("LINEERR_INVALPARAM");			break;
			case LINEERR_UNINITIALIZED:			strError	= wxT("LINEERR_UNINITIALIZED");			break;
			default:							strError.Format(_("Error: %x"),dwParam2);			break;
			}

			if(!strError.IsEmpty())
			{
				wxLogError(strError);
			}
			break;
		}
	}
}

Пример использования

wxUSSDRequest * m_USSDRequest;
...
wxUSSDRequest * GetUSSDRequest() const { return m_USSDRequest ; }
...
void wxUSSDRequestMainFrame::OnSENDClick( wxCommandEvent& event )
{
	do
	{
		if(!wxGetApp().GetUSSDRequest() || !wxGetApp().GetUSSDRequest()->IsOK()) break;
		wxString command = m_USSDTextCtrl->GetValue().GetData();
		if(!wxGetApp().GetUSSDRequest()->SendUSSDCommand(command, 
			command.Length() * sizeof(wxChar)))
		{
			wxLogError(_("Error sending USSD message '%s'"),
				m_USSDTextCtrl->GetValue().GetData());
		}
	}
	while(false);
}

Скачать исходный код примера.

W.

Windows Mobile Widgets – Новый тип приложений для Windows Mobile

Windows Mobile Widgets - MSN WidgetСегодня в блоге разработчиков Windows Mobile появилась информация о новом типе приложений, который будет доступен в Windows Mobile 6.5.

На данный момент для разработки приложений для Windows Mobile можно было использовать либо системное API (Native) либо управляемый код (Managed). В новой версии Windows Mobile ,удет доступна разработка приложений с использованием Web-технологий (HTML, CSS, AJAX, JavaScript).

Т.к. эти приложения будут использовать возможности новой версии Internet Explorer, то разработчики будут иметь доступ к использованию Flash и ActiveX компонентов, установленных на устройстве, таких, например, как MediaPlayer.

Заявлено также соответствие стандарту разработки мобильных widget-приложений от W3C, а также прозрачности для PNG-bзображений и нормальная работа с AJAX (support for the Window.XMLHttpRequest object work as expected).

Более подробно о новом типе приложений разработчики обещают рассказать на TechDays в апреле.