C/C++でSkype.frameworkをいじってみた
http://forum.skype.com/index.php?showtopic=32933 を参考にして、以前から興味のあったSkypeのAPIをいじってみました。Skype.frameworkや必要なドキュメントは上のリンクから辿ってダウンロードすることができます。
Skypeの興味深いところは、マルチプラットフォームに展開しているにもかかわらず、コアな部分を除いて、大部分を各プラットフォームに最適化して作ってあるところです。APIも、Windows版ではWin32 Messageを使い、Mac版ではApple Eventsを使って実装されているようです。
Skype APIを利用するには、誰かが作った各種言語のバインディングを利用するのも手です。しかし今回は、シンプルにSkype本家から配布されているSkype.frameworkをC/C++で叩いて、シンプルなコマンドラインインターフェースを作ることにしました。スクリプトや他のツールとの連携は、割り切ってパイプでプロセス間通信します。
この方式のメリットは
サンプルコード
コードは以下の通りです。Cでエラー処理をする根気がなかったので、C++にしました。サンプルコードは自由にお使い下さい。
#include <CoreFoundation/CoreFoundation.h> #include <Carbon/Carbon.h> #include <Skype/Skype.h> #include <iostream> #include <stdexcept> #include <string> #include <pthread.h> using namespace std; static char s_isAttachedToSkype = -1; static void* CarbonMain(void* data); static void LaunchCarbonThread(); static void Send(const char* com_); static void SkypeNotificationReceived(CFStringRef aNotificationString); static void SkypeAttachResponse(unsigned int aAttachResponseCode); static void SkypeBecameAvailable(CFPropertyListRef aNotification); static void SkypeBecameUnavailable(CFPropertyListRef aNotification); //----------------------------------------------------------------------------- static void* CarbonMain(void* data) { RunApplicationEventLoop(); return NULL; } //----------------------------------------------------------------------------- static void LaunchCarbonThread() { pthread_attr_t attr = {}; bool err = true; if (!pthread_attr_init(&attr)) { if (!pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED)) { pthread_t posixThreadID = 0; if (!pthread_create(&posixThreadID, &attr, &CarbonMain, NULL)) err = false; } pthread_attr_destroy(&attr); } if (err) throw runtime_error("Failed to create Carbon thread."); } //----------------------------------------------------------------------------- static void Send(const char* com_) { cerr << "<= " << com_ << endl; CFStringRef com = CFStringCreateWithBytes( NULL, (const UInt8*)com_, strlen(com_), kCFStringEncodingUTF8, false); SendSkypeCommand(com); CFRelease(com); } //----------------------------------------------------------------------------- static void SkypeNotificationReceived(CFStringRef aNotificationString) { if (const char* aNote = CFStringGetCStringPtr(aNotificationString, kCFStringEncodingMacRoman)) cerr << "=> " << aNote << endl; } //----------------------------------------------------------------------------- // 0 - failed, 1 - success static void SkypeAttachResponse(unsigned int aAttachResponseCode) { s_isAttachedToSkype = (1 == aAttachResponseCode) ? 1 : 0; } //----------------------------------------------------------------------------- static void SkypeBecameAvailable(CFPropertyListRef aNotification) { cerr << "== Skype became available." << endl; } //----------------------------------------------------------------------------- static void SkypeBecameUnavailable(CFPropertyListRef aNotification) { cerr << "== Skype became unavailable." << endl; } //----------------------------------------------------------------------------- int main(int argc, const char * argv[]) { SkypeDelegate skypeDelegate = { CFSTR("ttsky"), &SkypeNotificationReceived, &SkypeAttachResponse, &SkypeBecameAvailable, &SkypeBecameUnavailable }; try { SetSkypeDelegate(&skypeDelegate); ConnectToSkype(); LaunchCarbonThread(); for (int i = 0; i < 60*200; ++i) { usleep(50000); if (s_isAttachedToSkype >= 0) break; } if (!s_isAttachedToSkype) throw runtime_error("Could not attached to Skype."); for (;;) { char buff[1024] = {}; cin.getline(buff, sizeof(buff)); if (!cin.good() || !strcmp(buff, "BYE")) break; Send(buff); } } catch (const exception& ex) { cerr << ex.what() << endl; } catch (...) { cerr << "Unknown error." << endl; } DisconnectFromSkype(); return 0; }
ビルドの方法は、Xcodeを使うか、コマンドライン上で
% g++ -Wall -Os -F. -framework Skype -framework Carbon ttsky.cpp -o ttsky
などとして下さい*1。実際にSkypeとやり取りするには、http://developer.skype.com/accessories にある Public API Reference を参考にして下さい。このツールは「BYE」と入れると終了します。
難点
- Carbonプログラミングについて詳しくないので、とりあえず別スレッドを立ててイベントループを回していますが、SendSkypeCommand()がスレッドセーフかどうかは未確認です。
- 上に関連して、APIからの返り値を受け取って何かしようと思ったとき、入力と出力の同期をとる手段が、今のところありません。
- 文字コードについて、入力時にはkCFStringEncodingUTF8でエンコードして、出力時にはkCFStringEncodingMacRomanでデコードしています。試行錯誤の結果こうなっていますが、特に、出力時にMacRomanを指定しないとUTF8の日本語がうまく動かなかった理由は未だ謎です。