空想犬猫記

※当日記では、犬も猫も空想も扱っておりません。(旧・エト記)

C/C++でSkype.frameworkをいじってみた

http://forum.skype.com/index.php?showtopic=32933 を参考にして、以前から興味のあったSkypeAPIをいじってみました。Skype.frameworkや必要なドキュメントは上のリンクから辿ってダウンロードすることができます。

Skypeの興味深いところは、マルチプラットフォームに展開しているにもかかわらず、コアな部分を除いて、大部分を各プラットフォームに最適化して作ってあるところです。APIも、Windows版ではWin32 Messageを使い、Mac版ではApple Eventsを使って実装されているようです。

Skype APIを利用するには、誰かが作った各種言語のバインディングを利用するのも手です。しかし今回は、シンプルにSkype本家から配布されているSkype.frameworkをC/C++で叩いて、シンプルなコマンドラインインターフェースを作ることにしました。スクリプトや他のツールとの連携は、割り切ってパイプでプロセス間通信します。

この方式のメリットは

  • パイプを使うので言語非依存。色んなシーンで活用いただけます。
  • モジュールが小さくシンプル。お行儀が良いです。
  • Skype APIの理解が深まる。ためになります。
  • などなど

サンプルコード

コードは以下の通りです。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の日本語がうまく動かなかった理由は未だ謎です。

まとめ

C/C++Skype.frameworkを使って、MacOSX上でSkype APIを利用するサンプルを紹介しました。次回は、これを使ってTwitterのタイムラインをSkypeに流すツールを紹介しようと思います。

*1:-Fは、フレームワークのサーチパスを指定するオプションです