Integratie met Skype
In dit artikel beschrijf ik de eerste stappen die je moet zetten om een Delphi-toepassing te laten integreren met Skype. De volgende keer zal ik meer ingaan op de multimedia-aspecten, maar nu staat (slechts) de integratie centraal.
Skype
Skype is een dienst die telefonie over een breedband internetverbinding mogelijk maakt. Je moet hiervoor de Skype software downloaden (van http://www.skype.com) en op je machine installeren.
Gratis telefonie is alleen mogelijk met andere Skype gebruikers. Daarnaast is het mogelijk om een SkypeOut beltegoed te kopen om daarmee naar externe telefoonnummers te bellen. Tot slot is er ook een SkypeIn (momenteel in beta), waarmee je een "vast" nummer kunt aanvragen om daarop gebeld te worden – ook hiervoor moet je echter betalen.
Behalve de Skype software is het voor ontwikkelaars ook mogelijk om de Skype API te gebruiken, en die bijvoorbeeld te integreren in eigen software (een snelle en goedkope manier om vanuit je Delphi-toepassing de helpdesk te laten bellen bijvoorbeeld). Daarvoor moet de gebruiker uiteraard wel Skype zelf op zijn machine hebben staan.
Skype API
In dit artikel zal ik de een begin maken met het gebruik van de Skype API (in het kader van integratie). In een vervolgartikel zal ik dan ingaan op de verschillende mogelijkheden van Skype en een aantal voorbeelden bouwen met Delphi.
De API is geen laag om de Skype telefonie/communicatie library, maar een laag om de Skype client zelf. Wie de Skype client op zijn machine heeft, die heeft dus automatisch alle benodigde APIs ook beschikbaar.
Behalve de platte Skype API, is het ook mogelijk om via een COM-interface met Skype te praten (voor oude VB6 ontwikkelaars bijvoorbeeld). De Skype COM-server is te vinden in de SYSTEM32-directory als SKYPAAPI.DLL. Het gebruik van de COM-server is in principe eenvoudiger (vanuit de ontwikkelaar gezien), maar betekent wel dat je dan een extra installatie op de client moet doen – of moet afdwingen dat de eindgebruiker zelf de COM-server geïnstalleerd heeft.
Ik zal me in dit artikel beperken tot de niet-COM manier, en daartoe moeten we met de Skype Client communiceren via Windows messages.
De API is geen laag om de Skype telefonie/communicatie library, maar een laag om de Skype client zelf. Wie de Skype client op zijn machine heeft, die heeft dus automatisch alle benodigde APIs ook beschikbaar
Skype Client Detectie
De Skype-API is met name bedoeld om binnenkomende telefoontjes buiten de Skype-client zelf af te handelen - je kunt hierbij denken aan een antwoordapparaat - maar de Skype-client zelf moet dus aanwezig zijn en zal, indien nodig, gestart moeten worden.
Het detecteren van de aanwezigheid van Skype kan gebeuren door in de registry te zoeken naar de key HKEY_LOCAL_MACHINE\SOFTWARE\Skype\Phone die een String-value SkypePath heeft waarvan de waarde het volledige pad naar de SKYPE.EXE is (bij mij is dat C:\Program Files\Skype\Phone\Skype.exe).
In Delphi doen we dat als volgt:
function SkypeClient: String;
var
Reg: TRegistry;
begin
Result := '';
Reg := TRegistry.Create(KEY_READ);
try
Reg.RootKey := HKEY_LOCAL_MACHINE;
if Reg.OpenKey('SOFTWARE\Skype\Phone', False) then
Result := Reg.ReadString('SkypePath')
finally
Reg.Free
end
end;
Als het resultaat leeg is, dan is de Skype-client niet geïnstalleerd, en de Skype-API dus ook niet beschikbaar.
Protocol versies
Als de Skype-client gevonden is, dan is de volgende stap het bepalen van de compatibiliteit.
Er zijn op dit moment drie verschillende versies van de Skype-API beschikbaar, onder de naam PROTOCOL 1, 2 en 3. Tegen de tijd dat dit artikel te lezen is, zal PROTOCOL 4 wellicht ook al beschikbaar zijn.
Als ontwikkelaar kun je gebruik maken van het nieuwste protocol, maar dan zul je wel moeten vragen of de Skype-client dit protocol ondersteunt. We zullen aan de Skype-client moeten vertellen welke protocolversie we ondersteunen (bijvoorbeeld "PROTOCOL 3"), en de Skype-client zal daarop antwoorden door zijn versie terug te geven. Nieuwere Protocol versies bouwen voort op eerdere versies, dus wie protocol 3 ondersteunt, ondersteunt ook protocol 1 en 2. Op onze melding "PROTOCOL 3" zal de Skype-client kunnen antwoorden met "PROTOCOL 1", "PROTOCOL 2" of "PROTOCOL 3". Het is uiteraard aan te raden om vervolgens de protocolversie te gebruiken waar de Skype-client mee terugkwam.
De Skype-client zal nooit met een hoger PROTOCOL nummer komen dan onze aanroep, omdat onze software die niet ondersteunt. En truc om het daadwerkelijke PROTOCOL nummer van de geïnstalleerde Skype-client te vinden is dus om PROTOCOL met een hoog nummer aan te roepen, zoals PROTOCOL 9999, waarop de Skype-client dan met het hoogst mogelijke PROTOCOL nummer zal antwoorden.
Windows Message Communicatie
De communicatie tussen onze Delphi-toepassing en de Skype-client gaat via speciale Windows Messages. We moeten dus eerst zorgen dat onze zelfgeschreven Delphi-toepassing deze messages kan versturen en ontvangen. Daarvoor gebruiken we de RegisterWindowMessage Win32 API, die als argument de string van de Windows Message meekrijgt. De twee Windows Messages die we moeten registreren zijn SkypeControlAPIAttach en SkypeControlAPIDiscover.
Dit doen we in Delphi als volgt:
procedure TForm1.btnHookClick(Sender: TObject);
begin
SkypeControlAPIAttach :=
RegisterWindowMessage('SkypeControlAPIAttach');
SkypeControlAPIDiscover :=
RegisterWindowMessage('SkypeControlAPIDiscover');
end;
Hierbij zijn SkypeControlAPIAttach en SkypeControlAPIDiscover van het type DWord.
Na het registreren van deze twee messages moeten we een WindowHook installeren om de binnenkomende messages te kunnen ontvangen en bewerken. Dit kan met de Application.HookMainWindow method, en kunnen we ongedaan maken met de Application.UnhookMainWindow method. De btnHookClick kan hierdoor als volgt aangepast worden:
procedure TSkypeForm.btnHookClick(Sender: TObject);
begin
if (Sender as TButton).Caption = 'Hook' then
begin
SkypeControlAPIAttach :=
RegisterWindowMessage('SkypeControlAPIAttach');
SkypeControlAPIDiscover :=
RegisterWindowMessage('SkypeControlAPIDiscover');
Application.HookMainWindow(WindowHook);
SendMessage(HWND_BROADCAST, SkypeControlAPIDiscover,
Application.Handle, 0);
(Sender as TButton).Caption := 'Unhook'
end
else
begin
Application.UnHookMainWindow(WindowHook);
(Sender as TButton).Caption := 'Hook'
end
end;
Als extra stukje code heb ik ook vast een SendMessage van het type HWND_BROADCAST erin gezet, waarin we de SkypeControlAPIDiscover message via een broadcast-message versturen, met als argument onze application-handle. Als de Skype-client draait, zal hij hier antwoord op geven, en dat antwoord komt dan binnen in onze WindowHook functie.
WindowHook
De function WindowHook die kan kijken of er messages binnenkomen van het type SkypeControlAPIAttach is als volgt gedefinieerd:
function TSkypeForm.WindowHook(var Msg: TMessage):
Boolean;
begin
Result := False;
if Msg.Msg = SkypeControlAPIAttach then
begin
Log.Lines.Add('HOOK: SkypeControlAPIAttach');
Msg.Result := 1;
Result := true
end
else
inherited
end;
Let op dat we de Msg.Result altijd op 1 moeten zetten als we een bericht voor ons ontvangen hebben, anders denkt de Skype-client dat de verbinding al verbroken is.
De SkypeControlAPIAttach is een message die we krijgen als de Skype-client hoort dat er een toepassing is die gebruik wil maken van de Skype-API. De eindgebruiker krijgt hierbij het volgende te zien:

De default keuze bestaat uit het toestaan van het gebruik van de Skype-client, maar je kunt dit opnieuw vragen en laten bevestigen door de eindgebruiker in de toekomst. Alternatieven zijn het onconditioneel toestaan of niet toestaan. De Skype-client onthoudt dit, en koppelt dit aan de informatie over “Project1.exe”. Als dezelfde Project1.exe in de toekomst nog een keer langskomt, dan is hij in ieder geval bekend. Als Project1.exe in gewijzigde vorm langskomt, dan zal de Skype-client dit aangeven in de trant van “let op, je gaf in het verleden eens toestemming, maar inmiddels is Project1.exe gewijzigd.”. Prettig om te weten.
We bellen!
Terwijl de Skype-client wacht op input van de gebruiker, komt er bij onze Delphi-toepassing een message van type SkypeControlAPIAttach binnen, waarbij de status is weergegeven in het Msg.LParam deel. Als dat 0 is, dan is de permissie gegeven. Is het 1, dan moeten we nog even wachten (dan zal de dialoog zichtbaar zijn bijvoorbeeld). Als Msg.LParam 2 is, dan is de toegang geweigerd.
Als we uiteindelijk een SkypeControlAPIAttach message met Msg.LParam gelijk aan 0 binnenkrijgen, dan zit in de Msg.WParam de Windows Handle van de Skype-client waar we vanaf nu direct Windows Messages naartoe mogen sturen en terug van kunnen verwachten. De communicatie is dan gereed. Dit levert de volgende versie op van de WindowHook functie:
function TSkypeForm.WindowHook(var Msg: TMessage):
Boolean;
begin
Result := False;
if Msg.Msg = SkypeControlAPIAttach then
begin
if Msg.LParam = 0 then
begin
SkypeAPIWindowHandle := Msg.WParam;
Log.Lines.Add('HOOK: Attached’);
end
else
Log.Lines.Add('HOOK: Not Attached: ' +
IntToStr(Msg.LParam));
Msg.Result := 1;
Result := true
end
else
if Msg.WParam = SkypeAPIWindowHandle then
begin
// handel de SkypeAPIWindowHandle af
Msg.Result := 1;
Result := true
end
else
inherited
end;
SendMessageToSkype
Zodra we attached zijn, kunnen we vragen welk protocol er ondersteund wordt. Daartoe moeten we een SendMessage doen naar de SkypeAPIWindowHandle. Hiervoor heb ik een speciale SendMessageToSkype method geschreven met de volgende code:
procedure TSkypeForm.SendMessageToSkype(Str: String);
var
CopyData: CopyDataStruct;
begin
if Str <> '' then
begin
Log.Lines.Add('SEND: ' + Str);
CopyData.dwData := 0;
CopyData.lpData := PChar(Str);
CopyData.cbData := Length(Str)+1;
SendMessage(SkypeAPIWindowHandle, WM_COPYDATA,
Application.Handle, LPARAM(@CopyData))
end
end;
En deze kunnen we als volgt aanroepen vanuit de WindowHook method (vlak na het assignment naar de SkypeAPIWindowHandle):
SendMessageToSkype('PROTOCOL 3')
Het ontvangen van berichten van de Skype-client kunnen we ook doen in de WindowHook methode, als we checken of de Msg.WParam gelijk is aan de SkypeAPIWindowHandle (de verzender van het bericht). Als dat het geval is en het message type is een WM_COPYDATA, dan krijgen we antwoord van de Skype-client, bijvoorbeeld een antwoord op onze message “PROTOCOL 3”.
Dit levert uiteindelijk de volgende implementatie van de WindowHook op:
function TSkypeForm.WindowHook(var Msg: TMessage):
Boolean;
begin
Result := False;
if Msg.Msg = SkypeControlAPIAttach then
begin
Log.Lines.Add('HOOK: SkypeControlAPIAttach');
if Msg.LParam = 0 then
begin
SkypeAPIWindowHandle := Msg.WParam;
Log.Lines.Add('HOOK: Attached,
SkypeAPIWindowHandle=' +
IntToStr(SkypeAPIWindowHandle));
SendMessageToSkype('PROTOCOL 3')
end
else
Log.Lines.Add('HOOK: Not Attached: ' +
IntToStr(Msg.LParam));
Msg.Result := 1;
Result := true
end
else
if Msg.WParam = SkypeAPIWindowHandle then
begin
if Msg.Msg = WM_COPYDATA then
Log.Lines.Add('RECV: ' +
PChar(PCopyDataStruct(Msg.LParam).lpData));
Msg.Result := 1;
Result := true
end
else
inherited
end;
We bellen!
Als dit allemaal achter de rug is, dan kunnen we een SendMessage met een CALL commando sturen, waarbij we als argument de Skype ID van de ontvanger moeten opgeven. Ik heb zelf een aantal Skype ID’s, waaronder “eBob42”. Dus als test om mezelf te bellen kan ik gewoon een SendMessageToSkype(‘CALL eBob42’); doen.

Deze functionaliteit is nu makkelijk in te bouwen in een eigen Delphi-toepassing, bijvoorbeeld in de About box.
Wie met de Skype-API aan de slag wil en er echte integraties mee wil bouwen, doet er goed aan de Skype-API documentatie op de Skype-website door te nemen, en de verschillende voorwaarden te lezen. Mijn eigen experimenten hebben niet tot commerciële toepassingen geleid, maar dienen voornamelijk om anderen te laten zien hoe de integratie met Skype mogelijk is. Daarnaast zijn mijn vrienden en familieleden (waaronder m’n kinderen) fanatieke gebruikers van Skype en van de uitbreidingen die we erop hebben gebouwd (waaronder een soort antwoord/opbelapparaat).
Binnenkort zullen meer demo’s van Skype met Delphi op mijn website verschijnen. Wie vragen heeft of meer informatie wil kan me altijd mailen, of een Skype-telefoontje plegen (wie weet krijg je wel m’n antwoordapparaat aan de lijn)…