2009/07/15


amachangの記事glooxなんて物があるのを知った。
Gloox で XMPP を書いてみた - IT戦記

けっこうシンプルに書ける

http://d.hatena.ne.jp/amachang/20090601/1243852022

gloox - A portable high-level Jabber/XMPP library for C++

gloox is a rock-solid, full-featured Jabber/XMPP client library, written in C++. It makes writing spec-compliant clients easy and allows for hassle-free integration of Jabber/XMPP functionality into existing applications. gloox is released under the GNU GPL. Commercial licensing and support are available.

http://camaya.net/
確かに簡単そう。って事でWindowsだけれど試した。ソース見ると、なんと既にWindows対応出来ている。すばらしい。ただVisual Studio用のプロジェクトファイルが付いているだけだったので、mingw32からビルドする際には少し小細工が必要。
以下、その手順。
# wget http://camaya.net/download/gloox-1.0-beta7.tar.bz2
# tar xjvf gloox-1.0-beta7.tar.bz2
# cd gloox-1.0-beta7
# vim config.h.win

comment out HAVE_WINDNS_H
// #define HAVE_WINDNS_H 1

# cd src
# gcc -I. -c *.cpp
# ar cr libgloox.a *.o
これでmingw32用のlibgloox.aが出来上がる。そしてソース。glooxを使っておしゃべりするプログラムなので鶏っぽく"glookoo"(ぐるっくー)と名づけた。
必要な物はreadlineとpthread。readlineは出回っている物には--enable-multibyte付きでコンパイルされた物が見当たらなかったので、rubyのmingw32バイナリ等でも使われているココから使わせて頂く。
またpthreadはココからダウンロードして # make clean GCE-inlined
でビルドした物を使う。以下コード。
#include <gloox/client.h>
#include <gloox/connectionlistener.h>
#include <gloox/messagesessionhandler.h>
#include <gloox/messageeventhandler.h>
#include <gloox/messagehandler.h>
#include <gloox/message.h>
#include <gloox/messagesession.h>
#include <pthread.h>
#include <readline/readline.h>

#ifdef _WIN32
#include <windows.h>
#endif

static char* str_to_utf8_alloc(const char* str) {
#ifdef _WIN32
    size_t in_len = strlen(str);
    wchar_t* wcsdata;
    char* mbsdata;
    size_t mbssize, wcssize;

    wcssize = MultiByteToWideChar(GetACP(), 0, str, in_len,  NULL, 0);
    wcsdata = (wchar_t*) malloc((wcssize + 1) * sizeof(wchar_t));
    wcssize = MultiByteToWideChar(GetACP(), 0, str, in_len, wcsdata, wcssize + 1);
    wcsdata[wcssize] = 0;

    mbssize = WideCharToMultiByte(CP_UTF8, 0, (LPCWSTR) wcsdata, -1, NULL, 0, NULL, NULL);
    mbsdata = (char*) malloc((mbssize + 1));
    mbssize = WideCharToMultiByte(CP_UTF8, 0, (LPCWSTR) wcsdata, -1, mbsdata, mbssize, NULL, NULL);
    mbsdata[mbssize] = 0;
    free(wcsdata);
    return mbsdata;
#else
    return strdup(str);
#endif
}

static char* utf8_to_str_alloc(const char* utf8) {
#ifdef _WIN32
    size_t in_len = strlen(utf8);
    wchar_t* wcsdata;
    char* mbsdata;
    size_t mbssize, wcssize;

    wcssize = MultiByteToWideChar(CP_UTF8, 0, utf8, in_len,  NULL, 0);
    wcsdata = (wchar_t*) malloc((wcssize + 1) * sizeof(wchar_t));
    wcssize = MultiByteToWideChar(CP_UTF8, 0, utf8, in_len, wcsdata, wcssize + 1);
    wcsdata[wcssize] = 0;

    mbssize = WideCharToMultiByte(GetACP(), 0, (LPCWSTR) wcsdata, -1, NULL, 0, NULL, NULL);
    mbsdata = (char*) malloc((mbssize + 1));
    mbssize = WideCharToMultiByte(GetACP(), 0, (LPCWSTR) wcsdata, -1, mbsdata, mbssize, NULL, NULL);
    mbsdata[mbssize] = 0;
    free(wcsdata);
    return mbsdata;
#else
    return strdup(utf8);
#endif
}

class Glookoo : public gloox::ConnectionListener, gloox::MessageHandler
{
public:
    Glookoo(const char* server, const char* jid, const char* passwd, const char* user)
            : server_(server), jid_(jid), passwd_(passwd), user_(user) {
        client_ = NULL;
        session_ = NULL;
    }

    virtual ~Glookoo() {
        delete client_;
    }

    virtual void onConnect() {
        displayMessage("=== connected ===");
        session_ = new gloox::MessageSession(client_, gloox::JID(user_));
        session_->registerMessageHandler(this);
    }

    virtual void handleMessage(const gloox::Message& msg, gloox::MessageSession *session) {
        char* message = utf8_to_str_alloc(msg.body().c_str());
        displayMessage(message);
        free(message);
    }

    virtual void onDisconnect(gloox::ConnectionError reason) {
        displayMessage("=== disconnected ===");
        delete session_;
        session_ = NULL;
    }

    virtual bool onTLSConnect(const gloox::CertInfo &info) {
        return true;
    }

    void run() {
        client_ = new gloox::Client(gloox::JID(jid_), passwd_);
        client_->registerConnectionListener(this);
        client_->setServer(server_);
        client_->connect();
    }

    void sendMessage(const char* message) {
        if (session_) {
            char* send_message = str_to_utf8_alloc(message);
            session_->send(send_message, "");
            free(send_message);
        }
    }
    
    void displayMessage(const char* message) {
        int point = rl_point;
        rl_beg_of_line(0, 0);
        rl_redisplay();
        printf("%s\n", message);
        rl_point = point;
        rl_refresh_line(0, 0);
    }

private:
    gloox::Client* client_;
    gloox::MessageSession* session_;
    const char* server_;
    const char* jid_;
    const char* passwd_;
    const char* user_;
};

Glookoo* glookoo = NULL;

void* input_func(void* arg) {
    pthread_detach(pthread_self());
    while (true) {
        rl_callback_read_char();
    }
    return 0;
}

void* gloox_func(void* arg) {
    glookoo->run();
}

void input_handler(void) {
    char* message = rl_copy_text(0, rl_end);
    if (glookoo) glookoo->sendMessage(message);
    free(message);
}

int  opterr = 1;
int  optind = 1;
int  optopt;
char *optarg;
 
int getopt(int argc, char** argv, char* opts) {
    static int sp = 1;
    register int c;
    register char *cp;

    if(sp == 1) {
        if(optind >= argc ||
                argv[optind][0] != '-' || argv[optind][1] == '\0')
            return(EOF);
        else if(strcmp(argv[optind], "--") == 0) {
            optind++;
            return(EOF);
        }
    }
    optopt = c = argv[optind][sp];
    if(c == ':' || (cp=strchr(opts, c)) == NULL) {
        if(argv[optind][++sp] == '\0') {
            optind++;
            sp = 1;
        }
        return('?');
    }
    if(*++cp == ':') {
        if(argv[optind][sp+1] != '\0')
            optarg = &argv[optind++][sp+1];
        else if(++optind >= argc) {
            sp = 1;
            return('?');
        } else
            optarg = argv[optind++];
        sp = 1;
    } else {
        if(argv[optind][++sp] == '\0') {
            sp = 1;
            optind++;
        }
        optarg = NULL;
    }
    return(c);
}

int main(int argc, char* argv[]) {
    pthread_t pt_input, pt_gloox;
    char* server = "talk.google.com";
    char* jid = NULL;
    char* passwd = NULL;
    char* user = "wassr-bot@wassr.jp";
    int c;

    opterr = 0;
    while ((c = getopt(argc, argv, "j:p:s:u:") != -1)) {
        switch (optopt) {
        case 'j': jid = optarg; break;
        case 'p': passwd = optarg; break;
        case 's': server = optarg; break;
        case 'u': user = optarg; break;
        case '?': break;
        default:
            argc = 0;
            break;
        }
        optarg = NULL;
    }
    
    if (!jid || !passwd) {
        fprintf (stderr, "Usage: glookoo [-s server] [-j jid] [-p passwd] [-u user]\n");
        exit (1);
    }
    
    rl_callback_handler_install("#> ", (void(*)(char*))input_handler);

    glookoo = new Glookoo(server, jid, passwd, user);
    pthread_create(&pt_input, NULL, &input_func, glookoo);
    pthread_create(&pt_gloox, NULL, &gloox_func, glookoo);
    pthread_join(pt_gloox, NULL);
    pthread_join(pt_input, NULL);
    rl_deprep_terminal();
    return 0;
}
相変わらずコメントもなく適当ですね。
mingw32でのビルド手順は以下の通り。
gcc -Ic:/mingw/include/pthread -o glookoo.exe -I. glookoo.cxx libgloox.a c:/mingw/lib/readline.lib -lstdc++ -lws2_32 -lcrypt32 -lsecur32 -lpthreadGCE2
使い方はこんな感じ
Usage: glookoo [-s server] [-j jid] [-p passwd] [-u user]
デフォルトでサーバはGoogle Talk、ユーザはワッサーのボットになっています。
glookoo
マルチスレッドで入力とGlooxを処理していて入力途中の文字列を消されないようにしてあります。
今回はバイナリも置いておきます。
glookoo-0.0.1.tar.gz
よろしければどうぞ。ソースは後でgithubにでも上げておきます。
Posted at by



2009/07/07


kazuhoさんがやってくれました。
ずいぶん前からjsonをC++でパース(SAXじゃなくてDOM)するのに小さいライブラリないかなーと思ってました。個人的にはjson-cというのを使ってたのですが、幾らか気に入らない所があったりビルドが少し手間だったりしていました。STLしか使わなくてvectorとかmapで表現されるツリー構造な物が欲しいなぁって思ってたんです。
とあるIRCで昨日、kazuhoさんと「ほしいですよねー」という話から始まって、githubにあるjsonxxとかも物色しながら「いいのないねー」とか言ってたらkazuhoさんが「もすこし綺麗に書けそう」って言い出して朝から本格的に書き始めてついさっき出来上がりました。速いw
名前はpicojson
とても小さく、実装コードだと300数十ステップ程です。しかもヘッダファイルだけなので管理が楽です。

試しにwassrのpublicタイムラインをパースしてみました。
コードはこんな感じ。
curlのコードではなく、jsonのパース部分を見てください。
#include <curl/curl.h>
#include "../picojson.h"

typedef struct {
  char* data;   // response data from server
  size_t size;  // response size of data
} MEMFILE;

MEMFILE*
memfopen() {
  MEMFILE* mf = (MEMFILE*) malloc(sizeof(MEMFILE));
  mf->data = NULL;
  mf->size = 0;
  return mf;
}

void
memfclose(MEMFILE* mf) {
  if (mf->data) free(mf->data);
  free(mf);
}

size_t
memfwrite(char* ptr, size_t size, size_t nmemb, void* stream) {
  MEMFILE* mf = (MEMFILE*) stream;
  int block = size * nmemb;
  if (!mf->data)
    mf->data = (char*) malloc(block);
  else
    mf->data = (char*) realloc(mf->data, mf->size + block);
  if (mf->data) {
    memcpy(mf->data + mf->size, ptr, block);
    mf->size += block;
  }
  return block;
}

char*
memfstrdup(MEMFILE* mf) {
  char* buf = (char*)malloc(mf->size + 1);
  memcpy(buf, mf->data, mf->size);
  buf[mf->size] = 0;
  return buf;
}

using namespace std;
using namespace picojson;

int
main(int argc, char* argv[]) {
  char error[256];

  MEMFILE* mf = memfopen();
  CURL* curl = curl_easy_init();
  curl_easy_setopt(curl, CURLOPT_URL, "http://api.wassr.jp/statuses/public_timeline.json");
  curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, &error);
  curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, memfwrite);
  curl_easy_setopt(curl, CURLOPT_WRITEDATA, mf);
  if (curl_easy_perform(curl) != CURLE_OK) {
    cerr << error << endl;
  } else {
    value v;
    string err;
    parse(v, mf->data, mf->data + mf->size, &err);
    if (err.empty()) {
      array arr = v.get<array>();
      array::iterator it;
      for (it = arr.begin(); it != arr.end(); it++) {
        object obj = it->get<object>();
        cout << obj["user_login_id"].to_str() << ": " << obj["text"].to_str() << endl;
      }
    } else {
      cerr << err << endl;
    }
  }
  curl_easy_cleanup(curl);
  memfclose(mf);

  return 0;
}
こんなに短いコードでアプリが作れる!
STLに慣れた人ならイメージ沸くかと思います。すばらしい!
こういうのが欲しかったんです。
ただまだ出来上がったばっかりですしバグはあるかもしれません。また高機能にするつもりもないでしょうから使用目的を選ぶのが先決かと思います。
ライセンスはBSDとの事なので、バイナリ配布も可能です。

share - Revision 34226: /lang/cplusplus/picojson/trunk

picojson

http://svn.coderepos.org/share/lang/cplusplus/picojson/trunk/
ありがたや、ありがたや。

追記
kazuhoさんも記事を書いてますんでそちらも...
Kazuho@Cybozu Labs: 今更 C++ で JSON パーサ「picojson」を書いたわけ
http://developer.cybozu.co.jp/kazuho/2009/07/c-json-picojson.html
Posted at by



2009/06/14


先日書いた、「コマンドラインからGoogle Readerが使えるPeepを試した」でLinuxでは快適になった。ただWindowsでは動かない。まずWindowsのpython2.6にはcursesのバイナリモジュールが含まれていなかった。これは「Python2.6にはcursesのバイナリが含まれていないので作る」で解決したのだけど、元になっているpdcursesがWindows上ではまともに動かなくて、例えばマルチバイト文字を含んだ複数行をscrl(スクロール)すると、正しくマルチバイト文字の幅を取れていないのか下段の行が上段の行にゴミとなって重なったりした。
Peep自身を触っても良かったのだが修正非量も多い。常に残念だったのでCで書く事にした。APIの呼び出しまわりはPeepを参考にさせて頂き、libxml2やcurl、pdcurses(linuxではncurses)で書き直す事にした。ただWindows上でcursesの動作が変なのは変わらない話なので、scrlを使わない事にした。どうやったかというとスクロールの度にclear()を呼んで全行書き直し。試してみた所、それ程操作感も悪く無かった。また色属性についてもACS_REVERSEを使うと幅計算が間違っているのか1行の幅で反転してくれなかったので、A_BOLDを使う事にした。現状、WindowsでもLinuxでも動作するようになっているけど、C++のstlをふんだんに使っててメモリ効率を何も考えてないソースになっています。
mattn's cpeep at master - GitHub

terminal front-end for Google Reader writen in C

http://github.com/mattn/cpeep/tree/master
cpeep1
cpeep2
まだまだ、全く作りかけなので閲覧しか出来ません。
j,kスクロールと、oで閲覧、qで閉じる、vでブラウザ(現状Windowsは通常使うブラウザ、それ意外はfirefox限定)です。
ちなみにPeepはHTMLをテキストに変換するのにw3mを内部で呼び出していたけど、cpeepでは自前でウンチャラカンチャラやってます。
まだまだ、これからです。

ちなみに今気付いたのですが、PeepのAuthorさんからPeepのcommit bitを付与して頂いている様です。ありがとうございます。何か協力出来る事があればcommitさせて頂きます。

追記
なんかの間違いだった様です。すみません。
Posted at by