Since Windows Vista, a new API called Windows Contacts (WC) was born. It replaces the Windows Address BookAPI.
You can get your personal properties by using the Windows Address Book Editor. It can be opened using “wab” in command line.
At first you see your personal “Contacts” folder. This folder contains your personal contact file. Its extension is “.contact”. A double click on it opens the Address editor as seen below:
“Web.exe” can also receive any personal contact file to import it to your personal contacts.
The contact file contains a formatted text in xml syntax. Data is arranged in a tree that is used to retrieve values. When I am writing of a path, I iterate through the tree. Some of the elements contains a collection of the same element type. The reason comes from the fact, that several other contacts can be added to the personal contact. The WC API uses the array brackets to access different contact values.
The following sample path shows you how to obtain the first user’s name.(Notice that the array starts with one not zero.)
NameCollection/Name[1]/GivenName
XML (manually formatted):
This parts can be cut into three parts:
There are also Value tags:
ContactIDCollection/ContactID[1]/Value
XML (manually formatted):
This path returns the GUID of contact number one.
Many parts of a path are already defined as constants. Have a look at them in MSDN.
So how can we read from the personal contact file? Since new API uses COM, we will use the following interfaces
Actually there is no implementation of these interfaces for Delphi. To create your own type library (TLB) do the following steps (in Vista of course):
…and add :
Additionally find this method…
…and also add :
In this way midl.exe will also create interfaces in the typelibrary for IContactProperties and IContactPropertyCollection.
I could not figure out how to do it in another way. Comments are apreciated.
…and replace them by:
It is necessary because we need to check for a positive return value (S_FALSE). Delphi does not raise an EOleSysError exception for positive values.
Did you know?
You can also (un)register a TLB file using regtlib.exe from C:\Windows\System32\URTTEMP
Type “regtlib Icontact.tlb” to register the library and “regtlib /u IContact.tlb” to unregister it.
You can find an already generated type library and and Delphi source code at the end of this article.
Using the Windows Contact API is really easy as soon as the Delphi type library unit exists. It implements class factories for IContact and IContactManager. Use the IContact class factory if you want to add your own contacts to the user’s personal database. If you just want to read data you have to use IContactManager.
Retrieving the current user’s profile is simple:
ContactManager := CoContactManager.Create;
ContactManager.Initialize(‘Your AppName’,‘AppVersion’);
These lines creates an empty contact manager. Notice that the method Initialize is necessary for further calls.
Now we have to actually open the current user’s contact database.
The new interface Contact contains all the user’s personal data. We can obtain it through a path value as shown in the beginning of this article.
Let us obtain the user’s name. This task is done by retrieving the property interface IContactProperties using the Contact instance.
The new instance ContactProp allows us to access a property through a path.
try
ContactProp.GetString(PWideChar(Path), 0, nil, 0, Size);
except
GetMem(Name, Size*sizeof(WideChar));
ContactProp.GetString(PWideChar(Path), 0, wName, Size, Size);
Name := WideString(wName);
FreeMem(wName);
end;
The variable Name will contain the user’s name. In this way we make sure that the right amount of memory is allocated. Notice that the GetString actually returns the count of chars and not the count of bytes, so we have to double the size. We always get an exception in GetString because Delphi wraps (safecall convetion) the method and automatically raises an exception if the function returns a negative value (E_XXX code). We don’t distinguish between a memory error which we need to check for the correct size and a real error. So let us implement it in the right way:
In this way all “real” errors are catched and reraised.
All the other properties can be obtained in similar ways. However what properties are available at all? The interface IContactPropertyCollection allows us to obtain all available property names and types. IContactProperties implements a method GetPropertyCollection which returns a pointer to a IContactPropertyCollection that contains the requested properties. It is also possible to filter some of them.
begin
Dummy := nil;
ContactProp.GetPropertyCollection(Props, 0, nil, 0, Dummy, Integer(true));
The new property collection contains an enumeration method to iterating all available properties. Be aware that you must call Next before you start getting data.
while H <> S_FALSE do
begin
…
H := Props.Next;
end;
In this way we can obtain name, type , version, modification time and ID of a property. Just use the available methods of IContactPropertyCollection.
We already coded how to get a string using COM methods. So this code isn’t new to you. It retrieves the name of the property.
GetMem(PW, (Size+1)*sizeof(WideChar));
Props.GetPropertyName(PW, Size, Size);
Name := WideString(PW);
end;
end;
end;
To make life easier I created a unit that implements the shown code. You can download ContactMisc.pas in the download section of this article. The unit implements the following functions:
CGD_UNKNOWN_PROPERTY = $00000000;
CGD_STRING_PROPERTY = $00000001;
CGD_DATE_PROPERTY = $00000002;
CGD_BINARY_PROPERTY = $00000004;
CGD_ARRAY_NODE = $00000008;
type TWideStringArray = array of WideString;
{@Name contains contact information}
TContactProperty = record
//ID contains the contact’s guid as a string
ID,
//Name contains the contanct’s name
Name : WideString;
//Type contains contact type
Typ,
Version : Cardinal;
Time : TFileTime;
end;
TContactPropertyArray = array of TContactProperty;
{@Name returns a contact value in binary form.
@param(Contact defines an interface to a contact which is used to retrieve data)
@param(Path defines a path to the value to be retrieved.
Most calls to @Name needs the string "/Value" at the end for success.
For more about possible path values visit:
http://msdn2.microsoft.com/en-us/library/ms735869(VS.85).aspx
)
@param(Mime returns the mime type of binary data)
@return(Returns a memory stream containing the data)
@raises(EPathNotFound will be raised if parameter Path could not be found.)
}
function GetContactBinaryValue(const Contact : IContact;const Path : WideString;
out Mime : WideString) : TMemoryStream;
{@Name returns an array of available contact properties.
@param(Contact defines an interface to a contact which is used to retrieve data)
}
function GetUserContactProperties(const Contact : IContact) : TContactPropertyArray;
{@Name returns all available contacts from a contact manager.
@param(Manager defines an interface to a contact manager interface)
@return(Returns a list of IContact interfaces)
}
function GetContactList(const Manager : IContactManager) : TInterfaceList;
{@Name returns the value of a contact’s property
@param(Path defines a path to the value to be retrieved.
Most calls to @Name needs the string "/Value" at the end for success.
For more about possible path values visit:
http://msdn2.microsoft.com/en-us/library/ms735869(VS.85).aspx
)
@param(Contact defines an interface to a contact which is used to retrieve data.)
@raises(EPathNotFound will be raised if parameter Path could not be found.)
}
function GetContactValue(const Contact : IContact;const Path : WideString) : WideString;
Some hints in the end
retrieve the user’s ID?
The ID is stored in the registry. Look at …
HKEY_CURRENT_USER\Software\Microsoft\WAB\Me
…and you’ll find the a string that is necessary for the Load method of IContactManager.
Download:
Download the package here. It contains:
One Response
Obtain the user’s profile picture (2nd update). by JEDI Windows API
03|Nov|2009 1[...] Next time we will discuss how to use the Windows Contact COM API. Goto. [...]
Leave a reply