One of the greatest aspects of Lazarus, the free Delphi-like RAD environment of the FreePascal project, is its ability to cross-compile applications targetting Windows Mobile devices. I was taking a look at various CellID-based geolocation software lately and I thought I could probably write something of my own. However, it seems that most information pertaining to obtaining the CellID on WM devices is written for the .NET CF and there is - of course - nothing about Lazarus. So, I wrote my own simple unit to query the Cell ID, LAC and MNC codes of my Windows Mobile phone using nothing but Lazarus.

The theory

Windows Mobile has a rich set of Dynamic Link Libraries to interface all aspects of the device and the operating system itself. One of them is the Radio Interface Layer architecture, or RIL. This is an interface to the cellular radio module of your Windows Mobile device, if one is available. It can do all sort of things, from notifying you on incoming calls to getting useful information on the cellular network tower your device is connected to. We are going to use this last feature.

The information we need from the cell tower are:

  • Cell ID. The unique identification of the cellular tower your device is connected to. "Unique" is a bit misleading, because this value is only unique to the specific cellular operator you are using.
  • LAC. The Location Area Code specifies the area code of the current location.
  • MNC. The Mobile Network Code is the identifier of the cellular network you are connected to.

The combination of these three values uniquely identifies the cell tower you are connected to. Once you have this information, you can do all sort of interesting stuff, like getting (approximate) geolocation information using, for example, OpenCellID or hacking your way through the Google Maps CellID geolocation API. One particularly interesting application is tracking your furniture during a move like the guy in the article did, without having to buy an iPhone and pay for the service.

The practice

That's all interesting theory, but how can we do this without having to buy Visual Studio just to develop a simple Windows Mobile application? Well, with Lazarus of course! The idea we'll pursue is load the RIL.DLL library, get a RIL handle, ask the device for cell tower info, encode useful information and return it as a string. This is easier said than done, because all you can get is a C header to the RIL.DLL library (aptly called ril.h ...). I don't especially digg C - I'm a mostly PHP and Pascal guy - so the biggest pain was understanding how to "translate" the header. After an hour or so, my ucellid.pas unit was ready:

ucellid.pas
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
unit ucellid;

{$mode objfpc}{$H+}

interface

uses
Classes, SysUtils;

function GetCellTowerInfo: string;

implementation

uses
DynLibs;

const
MAXLENGTH_BCCH = 48;
MAXLENGTH_NMR = 16;

type
TRILCELLTOWERINFO = record
cbSize,
dwParams,
dwMobileCountryCode,
dwMobileNetworkCode,
dwLocationAreaCode,
dwCellID,
dwBaseStationID,
dwBroadcastControlChannel,
dwRxLevel,
dwRxLevelFull,
dwRxLevelSub,
dwRxQuality,
dwRxQualityFull,
dwRxQualitySub,
dwIdleTimeSlot,
dwTimingAdvance,
dwGPRSCellID,
dwGPRSBaseStationID,
dwNumBCCH : DWord;
rgbBCCH: Array[1..MAXLENGTH_BCCH] of Byte;
rgbNMR: Array[1..MAXLENGTH_NMR] of Byte;
end;
PRILCELLTOWERINFO = ^TRILCELLTOWERINFO;

TRilResultCallback = procedure( dwCode: DWord; hrCmdID: pointer;
lpData: PRILCELLTOWERINFO; cbData, dwParam: DWord );

TRilNotifyCallback = procedure( dwCode: DWord;
lpData: PRILCELLTOWERINFO; cbData, dwParam: DWord );

PHandle = ^THandle;

var
flagWait : Boolean;
strCellInfo : String;

procedure RilResultCallback ( dwCode: DWord; hrCmdID: pointer;
lpData: PRILCELLTOWERINFO; cbData, dwParam: DWord );
var
RILCellTowerInfo : TRILCELLTOWERINFO;
begin
RILCellTowerInfo := lpData^;
strCellInfo := IntToStr(RILCellTowerInfo.dwCellID) + '-' +
IntToStr(RILCellTowerInfo.dwLocationAreaCode) + '-' +
IntToStr(RILCellTowerInfo.dwMobileNetworkCode);

flagWait := false;
end;

function GetCellTowerInfo: string;
var
hDLL: THandle;
RIL_Initialize : function( dwIndex: DWord; pfnResult: TRILRESULTCALLBACK;
pfnNotify: TRILNOTIFYCALLBACK; dwNotificationClasses, dwParam: DWord;
var HRIL: PHandle): HRESULT; stdcall;
RIL_Deinitialize : function( HRIL: Pointer): HRESULT; stdcall;
RIL_GetCellTowerInfo : function( HRIL: Pointer): HRESULT; stdcall;
hRil: PHandle;
hRes: HRESULT;
begin
// Load RIL.DLL library
hDLL := LoadLibrary('RIL.DLL');
if hDLL < 32 then
begin
// Could not load RIL.DLL
Result := '';
Exit;
end;
// Get a hook on the functions required
Pointer(RIL_Initialize) := GetProcAddress(hDLL, 'RIL_Initialize');
Pointer(RIL_Deinitialize) := GetProcAddress(hDLL, 'RIL_Deinitialize');
Pointer(RIL_GetCellTowerInfo) := GetProcAddress(hDLL, 'RIL_GetCellTowerInfo');
// Get a new RIL handle and ask for cell tower information (async call)
hRil := nil;
hRes := 0;
Result := '';
hRes := RIL_Initialize(1, @RilResultCallback, nil, 0, 0, hRil);
if not(hRes = S_OK) then
begin
// Failed to init RIL
Result := '';
end
else
begin
// RIL initialised successfully, get cell info
flagWait := True; // This flag is reset in RilResultCallback, when data arrives
hRes := RIL_GetCellTowerInfo(hRil);
while flagWait do Sleep(100); // Simplistic wait handling
// finished - release the RIL handle
RIL_Deinitialize(hRil);
// Return the cell info as CellID-LAC-MCC
Result := strCellInfo;
end;
// Finally, unload RIL.DLL
FreeLibrary(hDLL);
end;

end.

In order to use it on your project, it's enough to include this unit in the implementation section of the form / unit you want to use the CellID in and call the published GetCellTowerInfo function. It returns a string in the format CellID-LAC-MNC, for example 1234-567-890. This is a ficticious example which reads as CellID = 1234, LAC = 567 and MNC = 890.

The code for ucellid.pas is licensed under the WTFPL Public License. It pretty much means that you can do whatever you want with it, without asking my permission. Enjoy!

No comments