2009/10/29


C言語ってlambdaが書けないのでGUIのコールバックなんかを作る場合には関数を用意しなければならないのですが...
Lambda abstractions in C++ vs. Scheme

1. Simple lambda-expressions

#define Lambda(args,ret_type,body) \
class MakeName(__Lambda___) { \
public: ret_type operator() args { body; } }
http://okmij.org/ftp/c++-digest/Lambda-CPP-more.html
なるほどねー。これなら Lambda((int a, int b), int, return a+b) foo;

std::cout << foo(1, 2) << std::endl; // 3
こんな書き方も出来る訳か。ただGUIなんかのコールバックだと関数ポインタが必要だし、operator()のアドレスなんて取る必要もないからstaticで宣言した関数ポインタで十分かな。

fltkだとこんな感じにmainの外に関数宣言しなくても良くなるね。
/* g++ -I Develop/fltk c.cxx -L Develop/fltk/lib -lXi -lfltk2 -lXft -lXinerama -lXcursor */
#include <fltk/Window.h>
#include <fltk/Button.h>
#include <fltk/ask.h>
#include <fltk/run.h>
#include <stdlib.h>

int main(void) {
#define lambda(args,ret_type,block) \
    class { public: static ret_type func args {block;} }

    lambda((fltk::Widget* wid, void* data), void, fltk::alert("don't click!")) button_clicked;

    fltk::Window w(0, 0, 300, 100, "lambda window");
    w.begin();
    fltk::Button b(10, 10, 280, 80, "click me!");
    b.callback(&button_clicked.func, 0);
    w.end();
    w.show();

    return fltk::run();
}

fltk-lambda-callback

mainしか宣言したく無い人にはいいかも。
先日知ったんだけど、最近のVisual Studioに付いてるコンパイラだと auto func [&](int a, int b) -> int { return a+b; };

std::cout << func(1, 2) << std::endl;
こんな書き方が出来るらしい
Posted at by



2009/10/23


githubが高速化に成功した様です。
How We Made GitHub Fast - GitHub
github

Now that things have settled down from the move to Rackspace, I wanted to take some time to go over the architectural changes that we’ve made in order to bring you a speedier, more scalable GitHub.

...

For our data serialization and RPC protocol we are using BERT and BERT-RPC.

http://github.com/blog/530-how-we-made-github-fast
データのシリアライズおよびRPC(リモートプロシージャコール)としてBERT-RPCを選んだ様です。
BERT and BERT-RPC 1.0 Specification

BERT and BERT-RPC are an attempt to specify a flexible binary serialization and RPC protocol that are compatible with the philosophies of dynamic languages such as Ruby, Python, PERL, JavaScript, Erlang, Lua, etc. BERT aims to be as simple as possible while maintaining support for the advanced data types we have come to know and love. BERT-RPC is designed to work seamlessly within a dynamic/agile development workflow. The BERT-RPC philosophy is to eliminate extraneous type checking, IDL specification, and code generation. This frees the developer to actually get things done.

http://bert-rpc.org/
BERT-RPCはBERT(Binary ERlang Term)という名前の通り、ERlangのterm_to_binary/1で生成されるフレキシブルなバイナリデータ交換の仕組みで、これをRPCに利用した物がBERT-RPCです。

BERT

BERT (Binary ERlang Term) is a flexible binary data interchange format based on (and compatible with) Erlang's binary serialization format (as used by erlang:term_to_binary/1).

http://bert-rpc.org/
erlangで使える、integer,float,atom,tupple,bytelist,list,binaryをシリアライズして転送する事が出来ます。仕様書はerlangの拡張仕様ページに掲載されています。
External Term Format

The external term format is mainly used in the distribution mechanism of Erlang.

http://erlang.org/doc/apps/erts/erl_ext_dist.html
詳しくはgithubのブログエントリでも説明されています。
最近ではMessagePackやProtocolBuffer、Thriftなどバイナリフォーマットを使ったRPCが流行ですが、このErlangのバイナリフォーマットは無駄の無いシリアライズ形式になっているかと思います。
実際には型識別と、それに後続するデータで構成され、型によってはデータ部の前にデータ長が付加されます。
Erlangのシリアライズでは、Erlangのprotocolバージョン131から開始されますが、その前に全体長を付与した物がRPCで使われています。RPCの本体はtuppleと、その中のATOMで構成されています。
実装を見たい方は以下のrubyによる実装を見る事が出来ます。
mojombo's bert at master - GitHub

BERT (Binary ERlang Term) serialization library for Ruby.

http://github.com/mojombo/bert

mojombo's bertrpc at master - GitHub

BERTRPC is a Ruby BERT-RPC client library.

http://github.com/mojombo/bertrpc
またerlangによるシリアライズ実装、およびBERT-RPCを使ったRPCサーバの実装は以下から参照出来ます。
mojombo's erlectricity at master - GitHub

Erlectricity exposes Ruby to Erlang and vice versa

http://github.com/mojombo/erlectricity

mojombo's ernie at master - GitHub

Ernie is an Erlang/Ruby BERT-RPC Server.

http://github.com/mojombo/ernie
例えば上記githubブログへのリンク先で説明されている様に # calc.rb
require 'ernie'

mod(:calc) do
  fun(:add) do |a, b|
    a + b
  end
end
というサーバのコードに対しては require 'bertrpc'

svc = BERTRPC::Service.new('localhost', 9999)
svc.call.calc.add(1, 2)
# => 3
というクライアントのコードが書けるのですが、内部ではErlangでシリアライズされ、先頭にデータ長が付与された以下のイメージでデータ送信が行われます。
{call,calc,add,[1,2]}
そしてバイト列としては以下の様になります。
0000000: 0000 0021 8368 0464 0004 6361 6c6c 6400  ...!.h.d..calld.
0000010: 0463 616c 6364 0003 6164 646c 0000 0002  .calcd..addl....
0000020: 6101 6102 6a                             a.a.j
先頭4バイトにデータ長(0x21:33バイト)、バージョン(0x83:131)、tupple(0x68)、tuppleアイテム数(4)、atom ext(0x64)...と続きます。

今日はこのBERT-RPCで動く足し算サーバををC言語から呼び出すサンプルを書いてみました。
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
#include <stdio.h>
#include <string.h>
#include <fcntl.h>

// send BERT-RPC command
//
//   request => {call,calc,add,[1,2]}
//   result  => {reply,3}
int
main(int argc, char *argv[]) {
  int sock;
  struct sockaddr_in serv_addr;
  short nport = htons(9999);
  struct hostent *host_ent;
  char *host = (char*)strdup("127.0.0.1");
  char *ptr;
  FILE *sockfp;

  ptr = strchr(host, ':');
  if (ptr) {
    *ptr++ = 0;
    if (atoi(ptr) == 0) {
      struct servent *serv_ent;
      serv_ent = getservbyname(ptr, "tcp");
      if (serv_ent)
        nport = serv_ent->s_port;
    } else {
      nport = htons(atoi(ptr));
    }
  }
  ptr = host;
  if ((host_ent = gethostbyname(ptr)) == NULL) {
    sock = -1;
    return -1;
  }
  memset((char *)&serv_addr, 0, sizeof(serv_addr));
  serv_addr.sin_family = AF_INET;
  memcpy(&serv_addr.sin_addr, host_ent->h_addr, host_ent->h_length);
  serv_addr.sin_port = nport;

  sock = socket(AF_INET, SOCK_STREAM, 0);
  if (sock < 0) {
    return;
  }

  if (connect(sock, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) == -1) {
    close(sock);
    sock = -1;
    return;
  }

  char  n1;
  short n2;
  int   n4;

  // total length
  n4 = htonl(33);               // 4byte
  write(sock, &n4, sizeof(n4)); // number of bytes

  n1 = 131;
  write(sock, &n1, sizeof(n1)); // version 131

  // {call,module,function,arguments}
  // ex: {call,calc,add,[1,2]}
  n1 = 104;
  write(sock, &n1, sizeof(n1)); // tupple
  n1 = 4;
  write(sock, &n1, sizeof(n1)); // number of tupple items

    n1 = 100;
    write(sock, &n1, sizeof(n1)); // atom
    n2 = htons(4);
    write(sock, &n2, sizeof(n2)); // number of atom items

      // atom1
      write(sock, "call", 4);          // call

      // atom2
      n1 = 100;
      write(sock, &n1, sizeof(n1));    // atom ext
      n2 = htons(4);                   // 2byte
      write(sock, &n2, sizeof(n2));    // atom length
      write(sock, "calc", 4);          // atom name

      // atom3
      n1 = 100;
      write(sock, &n1, sizeof(n1));    // atom ext
      n2 = htons(3);                   // 2byte
      write(sock, &n2, sizeof(n2));    // number of atom items
      write(sock, "add", 3);           // atom name

      // atom4
      n1 = 108;
      write(sock, &n1, sizeof(n1));    // list extension
      n4 = htonl(2);                   // 4byte
      write(sock, &n4, sizeof(n4));    // number of list elements

        n1 = 97;
        write(sock, &n1, sizeof(n1));  // small int
        n1 = 1;
        write(sock, &n1, sizeof(n1));  // int 1

        n1 = 97;
        write(sock, &n1, sizeof(n1));  // small int
        n1 = 2;
        write(sock, &n1, sizeof(n1));  // int 2

      n1 = 106;
      write(sock, &n1, 1); // nil ext(term of list extension)

  char buf[256];
  ptr = buf;
  int x = 0, n, l;
  l = recv(sock, buf, sizeof(buf), 0);
  printf("recv size %d\n", l);
  for (n = 0; n < l; n++) {
      x = (x + 1) % 80;
      printf("%02X ", (unsigned char)buf[n]);
      if (x == 0) putchar('\n');
  }
  putchar('\n');

  // [ 13bytes ] []
  // 00 00 00 0D 83 68 02 64 00 05 72 65 70 6C 79 61 03

  // length
  n4 = (unsigned int) (
          (*(ptr+0) << 24) +
          (*(ptr+1) << 16) +
          (*(ptr+2) <<  8) +
          (*(ptr+3) <<  0));
  printf("total length = %d\n", n4);
  ptr+=4;

  // version
  n1 = *ptr;
  printf("version = %d\n", (unsigned char)n1);
  ptr++;

  // small tupple extension
  n1 = *ptr;
  if (n1 == 104) printf("tupple extension\n");
  ptr++;

  // number of tupple items
  n1 = *ptr;
  printf("number of tupple items %d\n", n1);
  ptr++;

  // atom ext
  n1 = *ptr;
  if (n1 == 100) printf("atom extension\n");
  ptr++;

  // atom length
  n2 = (unsigned short) (
          (*(ptr+0) <<  8) +
          (*(ptr+1) <<  0));
  printf("atom length = %d\n", n2);
  ptr+=2;

  // atom name
  char atom[200] = {0};
  memcpy(atom, ptr, n2);
  printf("atom name = %s\n", atom);
  ptr+=n2;

  // small int
  n1 = *ptr;
  if (n1 == 97)printf("small int\n");
  ptr++;
  n1 = *ptr;
  printf("int %d\n", n1);

  close(sock);
  sock = -1;
  return 0;
}
  
  

分かりやすい様にコメントに種別を書いていますが、実用するには構造化したり抽象化する必要があります。ただし言語によっては型が見合わない場合もあるので、擬似するコードを書かなければならない可能性もあります。まだrubyとjavascriptにしか実装がない様なので、どなたかチャレンジしてみてはいかがでしょうか。
Posted at by



2009/10/09


tokuhiromさんがnanowwwという、C++から簡単にHTTPが扱えるクライアントライブラリを書いてくれたので、botを書いてみた。
tokuhirom's nanowww at master - GitHub

C++ lightweight, fast, portable HTTP client library

http://github.com/tokuhirom/nanowww
picojsonは以前ご紹介した通り、kazuhoさんが書いたC++からSTLと親和性の高いJSONパーサです。
今回はtokuhiromさんが、C++で、もちろんSTLと親和性の高いHTTPクライアントライブラリを書いてくれました。
これを使えば、例えばhttp://example.com/fooというURLにPOSTで"name=hasegawa"を送信するとした場合
  nanowww::Client client;
  nanowww::Request req("POST", "http://example.com/foo", "name=hasegawa");
  nanowww::Response res;
  req.set_header("Content-Type", "application/x-www-form-urlencoded");
  if (client.send_request(req, &res))
      std::cout << res.content() << std::endl;
  else
      std::cout << client.errstr() << std::endl;
これだけのコードでHTTP通信出来てしまいます。
C++らしくていいですね。CURLよりはインタフェースが優れていると思います。ただ現状はPROXYに対応していなかったり、Windowsで使えなかったりしますので、用途を絞られますが、今後に期待したいと思います。
さて今日はこれを使って、IRCに常駐し、指定のチャネルに入り、指定の単語が現れると「それXXX関係ないだろ!jk」と発言する、「関係ないbot」を書きました。実際にIRCプロトコルを喋っている訳でなく、webchat.freenode.netのAjax通信を、nanowwwとpicojsonによる擬似Ajax通信を行って実現しています。
以下、少々長いですが全ソースコード。
#include "picojson.h"
#include "nanowww.h"

using namespace std;
using namespace picojson;
using namespace nanowww;

#ifdef _WIN32
static char* utf8_to_str_alloc(const char* utf8) {
    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;
}
#endif

value get_json(string url, map<string, string>& postdata) {
  value v;
  char error[256];
  string data;
  map<string, string>::iterator it;

  for (it = postdata.begin(); it != postdata.end(); it++) {
      if (data.size()) data += "&";
      data += it->first + "=";
      data += it->second;
  }

  Client client;
  Request req("POST", url.c_str(), data.c_str());
  Response res;
  req.set_header("Content-Type", "application/x-www-form-urlencoded");
  if (client.send_request(req, &res)) {
    const char* ptr = res.content().c_str();
    string err = parse(v, ptr, ptr + strlen(ptr));
    if (!err.empty()) cerr << err << endl;
  }
  return v;
}

int
main(int argc, char* argv[]) {
  map<string, string> postdata;
  value v;
  bool loop;

  if (argc != 4) {
      std::cerr << "usage: kankeinaibot [nick] [channel] [word]" << std::endl;
      return -1;
  }
  string nick = argv[1];
  string channel = argv[2];
  string word = argv[3];

  postdata.clear();
  postdata["nick"] = nick;
  v = get_json("http://webchat.freenode.net/e/n", postdata);
  string sid = v.get<array>()[1].get<string>();

  loop = true;
  postdata.clear();
  postdata["s"] = sid;
  while (loop) {
    v = get_json("http://webchat.freenode.net/e/s", postdata);
    array a = v.get<array>();
    for (array::iterator it = a.begin(); it != a.end(); it++) {
      if (!it->is<array>()) continue;
      array line = it->get<array>();
      if (line[0].to_str() == "c") {
        if (line[1].to_str() == "443") {
            postdata["nick"] += "_";
            v = get_json("http://webchat.freenode.net/e/n", postdata);
            sid = v.get<array>()[1].get<string>();
            break;
        }
        if (line[1].to_str() == "376") {
            loop = false;
            break;
        }
      }
    }
  }

  loop = true;
  postdata.clear();
  postdata["c"] = "JOIN #";
  postdata["c"] += channel;
  postdata["s"] = sid;
  v = get_json("http://webchat.freenode.net/e/p", postdata);
  while (loop) {
    v = get_json("http://webchat.freenode.net/e/s", postdata);
    array a = v.get<array>();
    for (array::iterator n = a.begin(); n != a.end(); n++) {
      array line = n->get<array>();
      if (line[0].to_str() == "c") {
        array aa = line[3].get<array>();
        if (aa.size() > 1 && aa[0].to_str() != nick) {
          cout << aa[0].serialize() << endl;
          cout << aa[1].serialize() << endl;
          if (!strncmp(aa[1].to_str().c_str(), word.c_str(), word.size())) {
              map<string, string> saydata;
              saydata["s"] = sid;
              std::stringstream s;
              s << "PRIVMSG #" << channel << " :" <<
                  "それ" << word << "関係ないだろ!jk";
              saydata["c"] = s.str();
              cout << saydata["c"] << endl;
              v = get_json("http://webchat.freenode.net/e/p", saydata);
          }
        }
        string s = line[3].serialize();
#ifdef _WIN32
        char* p = utf8_to_str_alloc(s.c_str());
        cout << p;
        cout << " ";
        free((void*)p);
        cout << endl;
#else
        cout << s << endl;
#endif
      }
    }
  }
  
  return 0;
}
CURLを使った場合のレスポンスwriterを書かなくてもいいので、かなりスッキリしましたね。

ソースはgithubにも置いてあるのでよろしければ試してみて下さい。
mattn's kankeinai-bot at master - GitHub

関係ないbot

http://github.com/mattn/kankeinai-bot

えっ?全然botじゃない?

bot関係ないだろ!jk

C++の設計と進化 C++の設計と進化
Bjarne Stroustrup, ビョーン ストラウストラップ, επιστημη, επιστημη, 岩谷 宏
ソフトバンククリエイティブ 単行本 / ¥3,290 (2005年01月19日)
 
発送可能時間:

Posted at by