Попробовал сегодня FBA The Creator. Это что-то похожее на IDE для создания игр для PDA и смартфонов под управлением Windows Mobile. Имеет очень удобное API для работы с изображениями различных форматов, звуковыми файлами и др. Язык разрботки напоминает гибрид Pascal’я и Basic’а (знаю, многие не любят ни тот ни другой, но на самом деле плеваться нечего ибо такая аццкая смесь вполне может помочь какому-нибудь новичку с минимальными усилиями слепить несложную игру) (UPD: После прочтения документации оказалось что это LUA, да уж, век живи, век учись).

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

Плюс ко всему FBA The Creator позволяет создать инсталлятор для игры без каких-либо сторонних утилит. Т.е. получается самодостаточный инструмент разработки, на выходе у которого можно получить готовый к употреблению продукт.

И все это совершенно бесплатно! И весит мало. Дистрибутив FBA The Creator весит всего около 2х мегабайт.

Из недостатков хотелось бы отметить вот что: отказалось работать под Vista’ой из-под обычной учетки, пришлось запускать из-под административного аккаунта. Также при тестировании примеров на эмуляторе видно что при запуске программы секунд на 30 в левом нижнем углу появляется небольшой баннер с информацией о том что игра создана с помощью FBA Creator и со ссылкой на сайт продукта. Не скажу что это критично, но не очень приятно.

Ну а вцелом очень удачная штука.

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

This post has 3 Comments

3
  1. Интересная вещь. Только я упускаю один момент… Для сборки проекта помимо самой проги еще что-нибудь нужно? Не могу понять, то ли я туплю, то ли под wine она все-таки кривовато работает.

  2. Не, вроде ничего не надо. LUA ж скриптовый язык. Я думаю там приложение получается в виде интерпретатора и просто скрипта в ресурсах. Можно поковырять, но не вижу смысла. Оно просто работает.

  3. Ясно… вернусь домой – буду ломать, оно мне так и не дало собрать ничего 🙁

Leave a Reply

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

А.

Апрельское обновление средств разработки для Windows Phone 7

Microsoft выпустила обновленную версию средств разработки приложений для Windows Phone 7.

Отныне средства разработки совместимы с Visual Studio 2010 RTM английской редакции. Если у вас уже установлена Visual Studio 2010 с другой локализацией, то все равно прийдется ставить английскую.

Нововведения

  • Сборки подписанные в рамках Authenticode не будут загружаться и исполнятся. Проекты со ссылками на такие сборки будут выдавать сообщения об ошибке при попытке развертывания приложения на эмуляторе. Будет исправлено в будущих выпусках.
  • Ввод с клавиатуры в приложениях XNA не будет работать (раньше работало). Будет исправлено в будущих выпусках.
  • Изменения при отправке уведомлений на телефон (Push Notifications). Немного изменился формат отправки сообщений.
  • Добавлены новые секции в манифест WMAppManifest.xml, позволяющие указать, какие функции устройства хочет использовать приложение.
  • Изменение я API:
    • Временно удален объект BingMapTask. Появится снова в будущих выпусках.
    • Класс ChooserEventArgs переименован в TaskEventArgs.
    • У объекта ApplicationBar свойство Visible переименовано в IsVisible.
    • Изменения в объекте AccelerometerSensor: свойства Default, All, и Information были удалены, а доступ к этом объекту теперь осуществляется через вызов конструктора.

Изменения в эмуляторе

  • Для улучшения производительности можно включить поддержку аппаратной виртуализации.
  • Эмулятор Windows Phone 7 использует ускорение GPU. Для видеокарт с поддержкой DirectX 10 и выше будет использоваться аппаратное ускорение, в противном случае будет использоваться программное ускорение.
  • Поддержка кодека VC-1.
  • Синхронизация времени с хост-машиной, но при этом не учитываются часовые пояса.

Ссылки

О.

Отправка 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);
}

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