Thursday, May 19, 2005

Mobile Computing SIG Presentation Code Samples

First, let me thank all of you who attended last night's Mobile Computing SIG meeting last night at Microsoft in Mountain View. I thoroughly enjoyed doing the Windows Mobile application demo showing how to build wireless connected applications with the Microsoft .NET Compact Framework.

A number of you asked for the sample code I presented. I am happy to provide it here as two zip files.

The first file, cddb.zip, contains the Visual Studio .NET 2003 solution file as well as the Windows Mobile Pocket PC client source code. The second file, cddb_web.zip, contains the source code for the web application that the client application talks to. I packaged everything as two separate zip files because the web application code lives under the IIS inetpub\wwwroot directory, and the other files don't.

To use this sample code, you need to be running ASP.NET and have a cddb virtual directory under a directory called sig in your IIS default web application. Then extract the cddb_web.zip files into that directory. Or, you can create a new C# web application in Visual Studio .NET located at localhost/sig/cddb, let Visual Studio .NET create the virtual directory for you, and then copy the WebForm1.aspx, etc. files from the zip file over the ones created by Visual Studio .NET.

You can extract the cddb.zip contents pretty much anywhere you like. If you use Visual Studio to create a new web application as described above, you won't need the .sln and .suo files included. You will however in that casd need to add an existing project to the solution, the client.csproj client application.

Feel free to email me or post to this entry if you have any questions or comments.

Thursday, May 12, 2005

Programatically Turning the Pocket PC WLAN Adapter On and Off

Here is another frequently requested Pocket PC programming trick. With the wide adoption of WiFi / 802.11x wireless for PDAs, Pocket PC developers very frequently want to programatically turn the wireless LAN adapter on their device on or off. There are various reasons for this, not least of which is the desire to exercise greater control over the power consumption of the wireless radio.

The trick to doing this is our friend the DeviceIoControl API. Basically what you need to do is this:

To power off the WLAN radio: Unbind the wireless adapter, then power it off.
To power on the WLAN radio: Power on the adapter, then bind it.

Here is the code:

// wlanutils.cpp : Defines the entry point for the DLL application.
//

#include
#include
#include
#include
#include
#include "wlanutils.h"

///////////////////////////////////////////////////////
//Global definitions

//g_pszAdapterName --> Used for the multisz null terminated adapter string name for the
// IOCTL_NDIS_BIND_ADAPTER and IOCTL_NDIS_UNBIND_ADAPTER ioctls
// By the time this variable is needed, it is in the proper format.
TCHAR* g_pszAdapterName = NULL;
DWORD g_dwAdapterNameLen = 0;
//g_pszDriverName --> Contains the name of the active WLAN driver.
//This value corresponds to the SanDisk SDIO WiFi card.
//The driver name for your device may be different. Look in
//the device registry under HKLM\Drivers for yours.
TCHAR g_pszDriverName[] = TEXT("SDN1:");

//////////////////////////////////////////////////////////
//Public exported functions
BOOL APIENTRY DllMain( HANDLE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
)
{
return TRUE;
}

int GetRadioState()
{
HANDLE hDriver = NULL;
DWORD dwBytesOut = 0;
CEDEVICE_POWER_STATE ps;
int nPowerState = GET_POWER_STATE_ERROR;

hDriver = ::CreateFile(g_pszDriverName, GENERIC_READ|GENERIC_WRITE, FILE_SHARE_READ|FILE_SHARE_WRITE, NULL, OPEN_ALWAYS, 0, NULL);
if (INVALID_HANDLE_VALUE != hDriver)
{
BOOL bRes = ::DeviceIoControl(hDriver, IOCTL_POWER_GET, NULL, 0, &ps, sizeof(ps), &dwBytesOut, NULL);
if (bRes) {
nPowerState = ((ps == D4)?GET_POWER_STATE_OFF:GET_POWER_STATE_ON); //Note: This treats all states but off as on, i.e, stanby is considered on
}
::CloseHandle(hDriver);
}
return (nPowerState);
}

//bPowerState paramter is TRUE to turn radio on, FALSE to turn off
BOOL PowerRadio(BOOL bPowerState)
{
HANDLE hNdis = NULL;
HANDLE hDriver = NULL;
BOOL bRes = FALSE;
DWORD dwBytesOut = 0;
DWORD dwError = 0;
CEDEVICE_POWER_STATE ps;

GetAdapterName();

ps = (bPowerState?D0:D4);

if (!bPowerState)
{ //Power off the radio
hNdis = ::CreateFile(DD_NDIS_DEVICE_NAME, GENERIC_READ|GENERIC_WRITE, FILE_SHARE_READ|FILE_SHARE_WRITE, NULL, OPEN_ALWAYS, 0, NULL);
if (INVALID_HANDLE_VALUE != hNdis)
{
bRes = ::DeviceIoControl(hNdis, IOCTL_NDIS_UNBIND_ADAPTER, g_pszAdapterName, g_dwAdapterNameLen, NULL, 0, &dwBytesOut, NULL);
if (bRes)
{
hDriver = ::CreateFile(g_pszDriverName, GENERIC_READ|GENERIC_WRITE, FILE_SHARE_READ|FILE_SHARE_WRITE, NULL, OPEN_ALWAYS, 0, NULL);
if (INVALID_HANDLE_VALUE != hDriver)
{
bRes = ::DeviceIoControl(hDriver, IOCTL_POWER_SET, NULL, 0, &ps, sizeof(ps), &dwBytesOut, NULL);
}
else bRes = FALSE;
::CloseHandle(hDriver);

}
::CloseHandle(hNdis);
}
}
else
{ //Power on the radio
hDriver = ::CreateFile(g_pszDriverName, GENERIC_READ|GENERIC_WRITE, FILE_SHARE_READ|FILE_SHARE_WRITE, NULL, OPEN_ALWAYS, 0, NULL);
if (INVALID_HANDLE_VALUE != hDriver)
{
bRes = ::DeviceIoControl(hDriver, IOCTL_POWER_SET, NULL, 0, &ps, sizeof(ps), &dwBytesOut, NULL);
if (bRes)
{
hNdis = ::CreateFile(DD_NDIS_DEVICE_NAME, GENERIC_READ|GENERIC_WRITE, FILE_SHARE_READ|FILE_SHARE_WRITE, NULL, OPEN_ALWAYS, 0, NULL);
if (INVALID_HANDLE_VALUE != hNdis)
{
bRes = ::DeviceIoControl(hNdis, IOCTL_NDIS_BIND_ADAPTER, g_pszAdapterName, g_dwAdapterNameLen, NULL, 0, &dwBytesOut, NULL);
::CloseHandle(hNdis);
}
else bRes = FALSE;
}
::CloseHandle(hDriver);
}
}
return (bRes);
}

///////////////////////////////////////////
//Internal functions
void GetAdapterName()
{
DWORD dwError = 0;
DWORD dwSize = 0;
::GetAdaptersInfo(NULL, &dwSize);
IP_ADAPTER_INFO* pInfo = (IP_ADAPTER_INFO*)new char[dwSize];
TCHAR pszAdapterName[MAX_ADAPTER_NAME_LENGTH+4];

if ((dwError = ::GetAdaptersInfo(pInfo, &dwSize)) == NO_ERROR)
{
IP_ADAPTER_INFO* pCurrentAdapter = pInfo;
while (pCurrentAdapter)
{
if (pCurrentAdapter->Type == MIB_IF_TYPE_ETHERNET)
{
MultiByteToWideChar( CP_ACP, 0, pCurrentAdapter->AdapterName, -1, pszAdapterName, sizeof(pszAdapterName) / sizeof(pszAdapterName[0]));
//We assume there is only one ethernet adapter per device
break;
}
pCurrentAdapter = pCurrentAdapter->Next;
}
}
//Add the terminating zeros required by the IOCTL_NDIS_BIND_ADAPTER and IOCTL_NDIS_UNBIND_ADAPTER ioctls
DWORD dwLen = _tcslen(pszAdapterName)*sizeof(TCHAR);
g_dwAdapterNameLen = (_tcslen(pszAdapterName)+3)*sizeof(TCHAR);
//Free previous use of the adapter name string
if (g_pszAdapterName != NULL) {
::LocalFree(g_pszAdapterName);
g_pszAdapterName = NULL;
}
g_pszAdapterName = (TCHAR*)::LocalAlloc(LPTR, g_dwAdapterNameLen);
memmove(g_pszAdapterName, pszAdapterName, dwLen);
delete [] pInfo;
}

Sunday, May 01, 2005

Kiosk Style, Single Application Pocket PC Code

A number of people have asked lately how to "lock down" a Pocket PC so that it appears to the user as a single purpose, single application device. This behavior is sometimes called kiosk mode. Without being able to completely rewrite the device ROM and build a custom shell, it is still possible to program a commercial Pocket PC device so that it exhibits this behavior.

The basic idea is to store the original location / dimensions of the task bar / start menu (represented by the HHTaskBar window class) and the SIP in the RECT members m_rcTaskBar and m_rcSIP. Then move these windows off screen. The original dimensions are stored so that the windows can be restored if needed.



//Hide the task bar and SIP

m_hWndSIP = ::FindWindow(TEXT("MS_SIPBUTTON"), TEXT("MS_SIPBUTTON"));

m_hWndTaskBar = ::FindWindow(TEXT("HHTaskBar"), NULL);

if (::IsWindow(m_hWndTaskBar)){

RECT rcMain;

::GetClientRect(m_hWndTaskBar, &m_rcTaskBar);

::GetClientRect(m_hWnd, &rcMain);

::MoveWindow(m_hWndTaskBar, 0, -100, (m_rcTaskBar.right-m_rcTaskBar.left),(m_rcTaskBar.bottom-m_rcTaskBar.top), TRUE);

::MoveWindow(m_hWnd, 0, 0, (rcMain.right-rcMain.left), (rcMain.bottom-rcMain.top), TRUE);

}

if (::IsWindow(m_hWndSIP)){

::GetClientRect(m_hWndSIP, &m_rcSIP);

::MoveWindow(m_hWndSIP, 0, -100, (m_rcSIP.right-m_rcSIP.left),(m_rcSIP.bottom-m_rcSIP.top), TRUE);

}

Finally, in the .inf file used to build the CAB file installer for your application, add a shortcut to the application in the startup folder. To wit, in the inf file for building your cab:

[DefaultInstall]
...
CEShortcuts = SC.MyKiosk
...

[DestinationDirs]
SC.MyKiosk = 0, %CE4%
...

[SC.MyKiosk]
"MyKiosk",0,"MyKiosk.exe",%CE4%