pfront: A utility for Windows Mobile
Contents
Introduction
I am a huge fan of the popular itsutils collection of command-line tools. They allow you to control your docked Windows Mobile device from your PC's command prompt. This article covers my concept for an addition to this toolset which brings a background application window to the foreground. I find this to be more convenient than clicking through the Start-menu to Task Manager, locating my executable in the running application list, and selecting switch to from the drop-down menu.
pfront Usage
The pfront tool usage is similar to the other itsutil tools. From your Windows PC's command prompt: pfront.exe application_name.exe. If application_name.exe is the name of an application that is already running on your Windows Mobile device, its window will appear in the foreground. The itsutil command pps will give you a list of all the processes in the system if you don't know whether or not your application is running.
pfront Design
Like all itsutils, pfront is divided into two parts: the Windows executable (pfront.exe) and a Windows Mobile DLL (pfront_lib.dll).
pfront.exe
The pfront executable uses the RAPI2 interface to upload the pfront_lib.dll library to the mobile device and invokes its exported RAPI extension method. Connecting to the attached Windows Mobile device using RAPI2 device is a brief 9 lines of code:
CComPtr< IRAPIDesktop > rapi_desktop;
rapi_desktop.CoCreateInstance( CLSID_RAPI );
CComPtr< IRAPIEnumDevices > rapi_device_list;
rapi_desktop->EnumDevices( &rapi_device_list );
CComPtr< IRAPIDevice > rapi_device;
rapi_device_list->Next( &rapi_device );
CComPtr< IRAPISession > rapi_session;
rapi_device->CreateSession( &rapi_session );
rapi_session->CeRapiInit();
Once connected, we use CeCreateFile
and CeWriteFile
to copy pfront_lib.dll to the mobile device. In the code below, I use boost::shared_ptr<>
to encapsulate the HANDLE
returned by CeCreateFile
If you don't use boost, it won't hurt to use a naked HANDLE
as you normally would.
/// @brief Copy a file to the attached device if it does not already exist
/// @param session - open RAPI2 session
/// @param local_file - path to the local file
/// @param remote_file - path to the remote file
/// @return BOOL - TRUE on success
static BOOL CopyToDevice( IRAPISession* session,
const wchar_t* local_src_file,
const wchar_t* remote_dest_file )
{
BOOL success = FALSE;
// open the local file first
std::ifstream local( local_src_file, std::ios_base::binary );
if( local.is_open() )
{
// create the remote file if it doesn't already exist
// must have BOOST_MEM_FN_ENABLE_STDCALL defined
boost::shared_ptr< void > remote_file(
session->CeCreateFile( remote_dest_file,
GENERIC_WRITE,
0,
NULL,
CREATE_NEW,
FILE_ATTRIBUTE_NORMAL,
INVALID_HANDLE_VALUE ),
boost::bind( &IRAPISession::CeCloseHandle, session, _1 ) );
if( INVALID_HANDLE_VALUE != remote_file.get() )
{
char buffer[ 10 * 1024 ];
DWORD bytes_written;
// copy the local file to the dev
while( local.good() )
{
local.read( buffer, sizeof( buffer ) );
if( !session->CeWriteFile( remote_file.get(),
buffer,
local.gcount(),
&bytes_written,
NULL ) )
{
break;
}
// we succeed if we successfully copy the entire file
success = local.eof();
}
}
else
{
// check to see if we failed because the file is already on the dev
success = ( session->CeGetLastError() == ERROR_FILE_EXISTS );
}
}
return success;
};
Now that the RAPI extension DLL is uploaded to the mobile device, it is a simple matter of calling CeRapiInvoke
to invoke the exported RAPI method in the DLL and pass it the name of the executable specified by the user in the command line argument.
BYTE *unused_a = NULL;
DWORD unused_b = 0;
rapi_session->CeRapiInvoke( L"pfront_lib.dll",
L"PFRONT_BringToFront",
( ::wcslen( argv[ 1 ] ) + 1 ) * sizeof( wchar_t ),
reinterpret_cast< BYTE* >( argv[ 1 ] ),
&unused_b,
&unused_a,
NULL,
0 );
pfront_lib.dll
The RAPI extension DLL pfront_lib.dll exports only one function in the standard RAPI extension DLL form:
/// @brief bring the application window of the app with the given name to
/// the front of the window z-order.
///
/// @param DWORD cbInput - number of bytes in the pInput buffer.
/// @param BYTE* pInput - [in] wchar_t* - null-terminated name of the
/// executable with the window to bring to the front. e.g. "myapp.exe"
/// @param DWORD* pcbOutput - unused.
/// @param BYTE** ppOutput - unused.
/// @param IRAPIStream* pStream - unused. NULL, always.
/// @return int - S_OK on success.
PFRONT_LIB_API int PFRONT_BringToFront( DWORD cbInput,
BYTE* pInput,
DWORD* pcbOutput,
BYTE** ppOutput,
IRAPIStream* pStream );
The implementation of this method is fairly simple. We will use the ToolHelper API to enumerate the list of running processes in the system. Once we find a process that matches the name given to us by the user, we will use EnumWindows
to locate any HWND
window handles owned by that process.
PFRONT_LIB_API int PFRONT_BringToFront( DWORD /*cbInput*/,
BYTE* pInput,
DWORD* /*pcbOutput*/,
BYTE** /*ppOutput*/,
IRAPIStream* /*pStream*/ )
{
const wchar_t* process_name = reinterpret_cast< const wchar_t* >( pInput );
if( NULL == process_name )
return E_INVALIDARG;
int error = ERROR_FILE_NOT_FOUND;
HANDLE snapshot =
::CreateToolhelp32Snapshot( TH32CS_SNAPPROCESS | TH32CS_SNAPNOHEAPS, 0 );
if( INVALID_HANDLE_VALUE != snapshot )
{
PROCESSENTRY32 pe = { 0 };
pe.dwSize = sizeof( PROCESSENTRY32 );
if( ::Process32First( snapshot, &pe ) )
{
do
{
if( ::wcsicmp( pe.szExeFile, process_name ) == 0 )
{
// we've located a process that has the given executable
// name. Find its window and send it to the foreground.
::EnumWindows( BringToFront, pe.th32ProcessID );
error = S_OK;
break;
}
} while( ::Process32Next( snapshot, &pe ) );
}
::CloseToolhelp32Snapshot( snapshot );
}
::LocalFree( pInput );
return error;
}
If the process is found, then the BringToFront
callback is executed to determine if it owns the window. Here, we use the GetWindowThreadProcessId
function to determine if the owner of the window matches our process. If it does, SetForegroundWindow
will bring it to the front.
/// @brief If the process that owns the given window handle matches the PID,
/// bring that window to the front of the z-order.
/// @param[in] hwnd window handle
/// @param[in] lparam PID of the window owner process
/// @return TRUE, always
BOOL CALLBACK BringToFront( HWND hwnd, LPARAM lparam )
{
DWORD pid = 0;
::GetWindowThreadProcessId( hwnd, &pid );
if( pid == static_cast< DWORD >( lparam ) )
::SetForegroundWindow( hwnd );
return TRUE;
}
Conclusion
I find this tool helps my day-to-day application development and I hope that you enjoy this tool as well and that it finds a place in your toolkit with the other itsutils.