Read Write Call History

0.00 avg. rating (0% score) - 0 votes
Objective: Retrieve and modify call history from a Windows Mobile device.

Read-only access

The CallLog API ( provides read-only access to call history via its PhoneOpenCallLog and PhoneGetCallLogEntry function. For a C# implementation via P/Invoke, the OpenNetCF has a nice wrapper via its OpenNETCF.Phone namespace. Refer to OpenNETCF 1.4 for full source code.

Read-write access

The Call Log database is in CEDB/EDB format. If you want to modify it you’ll have to do it manually using CEDB or EDB API’s. For Windows Mobile 5 and higher, use EDB to open the volume pim.vol in the clog.db database.

To use EDB, the following declaration

#define EDB

must be added on top on the source code (or stdafx.h for apps that use pre-compiled headers).

This code demonstrates how to modify the call log using EDB API. The necessary steps are as follows:

1. Call history is stored in the EDB database pim.vol. Mount the database file first.

CeMountDBVolEx(&m_ceguidDB, L”\pim.vol“, NULL, OPEN_EXISTING);

2. Open the call history table clog.db. Return INVALID_HANDLE_VALUE if error. CEDB_AUTOINCREMENT makes sure that each call to CeReadRecordPropsEx will move the cursor forward to the next record so that no manual seeking in the database is required

HANDLE m_hDBCallLog = CeOpenDatabaseEx2( &m_ceguidDB, &m_ceoidCallLog, TEXT(“clog.db”), 0, CEDB_AUTOINCREMENT, NULL);

3. Seek to the beginning/end of the table. Return non-zero if OK, 0 if error.

CEOID ceOIDTemp = CeSeekDatabase(m_hDBCallLog, CEDB_SEEK_BEGINNING, 0, NULL);

4.1. Read the records repeatedly (forward cursor) until there are no records left. This is the most tricky part as we need to guess the database format, e.g. how many columns each record has and what each column is for. Refer to [3] for a description of possible format. We need to define every fields that the record contains:

//Indentify the column indexes (in the database) of the properties to be retrieved
CEPROPID propReserved1 MAKELONG(CEVT_I2, 1); //Always 1
CEPROPID propStartTime = MAKELONG(CEVT_FILETIME, 2); //call start time
CEPROPID propEndTime = MAKELONG(CEVT_FILETIME, 3); //call end time
CEPROPID propCallFlags = MAKELONG(CEVT_I4, 4); //Identify call property (connected, roaming, incoming, outgoing, missed, etc.), every bit has a certain meaning
CEPROPID propSomeText = MAKELONG(CEVT_LPWSTR, 5); //Some texts to display
CEPROPID propCallNumber = MAKELONG(CEVT_LPWSTR, 6); //called/incoming number
CEPROPID propCallName = MAKELONG(CEVT_LPWSTR, 7); //name from address book
CEPROPID propReserved2 = MAKELONG(CEVT_I4, 8); //always 0
CEPROPID propID = MAKELONG(CEVT_AUTO_I4, 9); //Sequence number, auto-increase
DWORD dwBuf = 0; //returned size of buffer to hold record properties
WORD wProps = 5; //length of property array, e.g. number of properties to receive CEPROPVAL *pPropVals = NULL; //properties of records are returned in this array
CEPROPID propsToRead[5] = {propStartTime, propEndTime, propCallNumber, propCallName, propCallFlags}; //array of property to retrieve

After that, call CeReadRecordPropsEx to read the record. This function returns non-zero if OK, 0 if failed:

CEOID readRecord = CeReadRecordPropsEx(m_hDBCallLog, CEDB_ALLOWREALLOC, &wProps, propsToRead, reinterpret_cast(&pPropVals), &dwBuf, NULL);

To retrieve the read properties,

  1. check pPropVals[i].wFlags to make sure that the field has been read properly
  2. Check LOWORD(pPropVals[i].propid) for the field data type
  3. According to the field data type, retrieve data from pPropVals[i].val

Deleting a record is straightforward once you have its OID:

CeDeleteRecord(m_hDBCallLog, ceOIDTemp);

Creating a new record is possible via CeWriteRecordProps. This function return the ID of the created record if successful, and zero if failed:

CEPROPVAL propsToWrite[5]; //array of properties to write for the current record
WORD length = sizeof(propsToWrite)/sizeof(CEPROPVAL);
CEOID newRec = CeWriteRecordProps(m_hDBCallLog, 0, length, propsToWrite);

Finally, the volume needs to be unmounted:


Migrating to C# via P/Invoke:

Most functions are available by dllimporting from coredll.dll. Unsafe code in C# needs to be used as CeReadRecordPropsEx works with pointer to byte buffer, which cannot be simulated via the .NET class IntPtr.

[DllImport(“coredll.dll”, SetLastError=true)]
static unsafe extern int CeReadRecordPropsEx(IntPtr hDbase, int dwFlags, ref int lpcPropID, IntPtr rgPropID, byte** lplpBuffer, ref int lpcbBuffer, IntPtr hHeap);

As in C++, we have to first mount, open the database and seek to the beginning

CeMountDBVolEx(ref m_ceguidDB, “\pim.vol“, IntPtr.Zero, OPEN_EXISTING);
int m_ceoidCallLog = 0;
IntPtr m_hDBCallLog = CeOpenDatabaseEx2(ref m_ceguidDB, ref m_ceoidCallLog, “clog.db”, IntPtr.Zero, CEDB_AUTOINCREMENT, IntPtr.Zero);
int ceOIDTemp = CeSeekDatabase(m_hDBCallLog, CEDB_SEEK_BEGINNING, 0, IntPtr.Zero);

To identify the fields of each records to read, we need to define an array of CEPROPID. Luckily, CEPROPID is just an integer which has to be calculated by providing the column index and the data type to the MAKELONG macro. In C#, however, we can simplify this by pre-calculating the values:

//array containing the property to be retrieved
const int colStartTime = 131136;
const int colEndTime = 196672;
const int colCallNumber = 393247;
const int colCallName = 458783;
const int colCallFlags = 262147;
int[] propsToRead = new int[5] { colStartTime, colEndTime, colCallNumber, colCallName, colCallFlags };
We can proceed to read the record as follows:
int wProps = 5; //length of property array
int dwBuf = 0; //size of read buffer is returned here
byte* retVal; //pointer to returned buffer
//perform actual reading of the record
fixed (int* pArray = propsToRead)
int readRecordID = CeReadRecordPropsEx(m_hDBCallLog, CEDB_ALLOWREALLOC, ref wProps, new IntPtr((void*)pArray), &retVal, ref dwBuf, IntPtr.Zero);

The record is read properly (the return value readRecordID is valid and retVal seemingly contains data) but retrieving the read data is difficult since the returned value (retVal) is an array of unions which cannot be declared via managed code. To simulate union in .NET, we have tried to explicitly declare the start address of each member in the structure to be the same, but this result in a TypeLoadException

unsafe struct CEBLOB {
int dwCount;
byte* lpb;
Int16 iVal;
Int16 uiVal;
long lVal;
Int32 ulVal;
FILETIME filetime;
string lpwstr;
CEBLOB blob;
bool boolVal;
Int64 dblVal;

There is no solution for this as of now.

  1. – Retrieving and Storing Call History
  2. – Porting applications from CEDB to EDB
  3. – Structure of clog.db table
  4. – EDB samples for WM5
0.00 avg. rating (0% score) - 0 votes


A tough developer who likes to work on just about anything, from software development to electronics, and share his knowledge with the rest of the world.

2 thoughts on “Read Write Call History

  • December 7, 2012 at 3:55 pm

    I am looking for International calling service provider that provide full online access of my account including Top Up, Registering Phones, Call History, Current Balance etc..

  • January 16, 2013 at 4:17 pm

    I want to use pin less calling with registered numbers but i also want to control my bill by viewing and tracking call history online from all registered numbers .

Leave a Reply

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

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>