IT戦記

プログラミング、起業などについて書いているプログラマーのブログです😚

gdb で swig 内部の値を見る

きっといつか誰かのためになると信じて残しておきます。
swig では doh というライブラリを使っていて、 gdb にはほとんどのオブジェクトが void* に見えてしまう。(おそろしい子)
で、文字列の値を見たい場合は以下のようにすれば見れます。

(gdb) p (char*)DohData(string)

Twitter の半径数クリック以内の情報収集

ちょっと

現実頭皮的に自己満足的プログラムを書きたくなったので Twitterクローラーを書いてみた。
C++ にしては、割とすっきり書けて満足。

使ったライブラリ

ソース

#include <cassert>
#include <soci.h>
#include <soci-sqlite3.h>
#include <unistd.h>
#include <iostream>
#include <sstream>
#include <picojson.h>
#include <boost/scoped_ptr.hpp>
#include <boost/asio.hpp>
#include <boost/cast.hpp>
#include <boost/lexical_cast.hpp>
#include <boost/date_time/posix_time/posix_time.hpp>

using namespace std;
using namespace SOCI;
using namespace boost;
using namespace boost::posix_time;
using namespace boost::gregorian;
namespace j = picojson;
typedef asio::ip::tcp::iostream tcpstream;

void interval(unsigned int t) {
    cout << "Interval " << t <<"[s]" << endl;
    sleep(t);
}

template<class T>
j::value request(const std::string &api_path, const T &id) {

    interval(30);

    // Request Friends API
    cout << "Request http://twitter.com" << api_path << "/" << id << ".json" << endl;
    tcpstream s("twitter.com", "http");
    s << "GET " << api_path << "/" << id << ".json HTTP/1.0\r\nHost: twitter.com\r\n\r\n" << flush;

    if (s) {
        // Skip Header
        string line;
        while (getline(s, line)) if (line == "\r") break;

        if (s) {
            // Parse JSON
            j::value v;
            string err = j::parse(v, s);

            return v; // Expect RVO
        }
    }

    j::value null;
    return null;
}


template<class T> bool exists(Session& sql, T id);

template<>
bool exists<string>(Session& sql, string screen_name) {
    int exists = 0;
    cout << "Ckeck conflict " << screen_name << endl;
    sql << "SELECT count(id) FROM user WHERE screen_name = :screen_name", use(screen_name), into(exists);
    return static_cast<bool>(exists);
}

template<>
bool exists<unsigned long>(Session& sql, unsigned long id) {
    int exists = 0;
    cout << "Ckeck conflict " << id << endl;
    sql << "SELECT count(id) FROM user WHERE id = :id", use(id), into(exists);
    return static_cast<bool>(exists);
}

template<class T>
bool check(const j::value& v) {

    // Error check
    if (v.is<j::object>()) {
        j::value error = v.get("error");
        if (!error.is<j::null>()) {
            cout << "Oops API error!! (" << v << ")" << endl;
            interval(1000);
            return false;
        }
    }

    // Type check
    if (!v.is<T>()) {
        cout << "Oops Type mismatch!! (" << v << ")" << endl;
        interval(100);
        return false;
    }

    return true;
}

template<class T>
T get(const j::value& v) {
    if (v.is<T>()) {
        return v.get<T>();
    }
    return T();
}

const unsigned int end_depth = 2;

template<class T>
void fetch(Session& sql, T id, unsigned int depth = 0) {

    while(true) {

        if (exists(sql, id)) {
            cout << "Ignore (" << id << ")" << endl;
        }
        else {
            // First fetch self data
            const j::value& value = request("/users/show", id);

            if (!check<j::object>(value)) continue;

            j::object v = value.get<j::object>();

            unsigned long uid = numeric_cast<unsigned long>(get<double>(v["id"]));
            unsigned long friends_count = numeric_cast<unsigned long>(get<double>(v["friends_count"]));
            unsigned long followers_count = numeric_cast<unsigned long>(get<double>(v["followers_count"]));
            unsigned long favourites_count = numeric_cast<unsigned long>(get<double>(v["favourites_count"]));
            unsigned long statuses_count = numeric_cast<unsigned long>(get<double>(v["statuses_count"]));
            unsigned long created_at = (lexical_cast<ptime>(get<string>(v["created_at"])) - ptime(date(1970, Jan, 1))).total_seconds();
            string time_zone = get<string>(v["time_zone"]);
            string screen_name = get<string>(v["screen_name"]);
            string profile_image_url = get<string>(v["profile_image_url"]);

            cout << "Insert " << screen_name << "(" << uid << ")" << endl;

            // Insert data
            sql << "INSERT INTO user VALUES("
                        ":id, :screen_name, :friends_count, :followers_count, :favourites_count, "
                        ":statuses_count, :created_at, :time_zone, :profile_image_url)",
                        use(uid), use(screen_name), use(friends_count), use(followers_count), use(favourites_count), 
                        use(statuses_count), use(created_at), use(time_zone), use(profile_image_url);
        }

        // Next fetch children data
        if (depth < end_depth) {
            cout << "Next depth" << endl;

            while (true) {
                const j::value &value = request("/friends/ids", id);

                if (!check<j::array>(value)) continue;

                j::array ids = value.get<j::array>();

                for (j::array::const_iterator it = ids.begin(); it != ids.end(); ++it) {
                    unsigned long uid = numeric_cast<unsigned long>(it->get<double>());
                    fetch(sql, uid, depth + 1);
                }
                break;
            }
        }
        break;
    }
}

int main() {
    // Setting ptime locale for lexical_cast
    locale::global(locale(locale(), new time_input_facet("%a %b %d %H:%M:%S +0000 %Y")));

    try {
        Session sql(sqlite3, "twitter.db");

        // Create table
        sql << "CREATE TABLE IF NOT EXISTS user ("
                    "id INTEGER PRIMARY KEY, screen_name TEXT UNIQUE, friends_count INTEGER, followers_count INTEGER, favourites_count INTEGER, "
                    "statuses_count INTEGER, created_at INTEGER, time_zone TEXT, profile_image_url TEXT)";

        fetch(sql, std::string("amachang"));
    }
    catch(const std::exception &e) {
        cerr << "Error: " << e.what() << "\n";
    }
}

new と delete と throw で気をつけること(自分用メモ)

  • delete する変数が未初期化になっている可能性を考えろ
    • コンストラクタの初期化子リストで throw とか
      • 初期化子リストで throw する可能性のある式を評価するな。 0 を入れておいて、コンストラクタ内で評価しろ
      • または、ポインタラッパー的なものを使え。(デフォルトコンストラクタで初期化されるから)
    • 未初期化変数への代入式で throw とか
      • とりあえず、変数初期化子で 0 代入しとけ
  • コンストラクタで例外が発生した場合にデストラクタが呼ばれないことに注意しろ
    • コンストラクタ内で new して、そのままとか
      • boost::shared_ptr 使え
      • または、 try { /* ... */ } catch(...) { delete p; throw; } って感じで、すべての例外を catch して delete しとけ

Visual Studio で exe を ldd する(依存する dll を調べる)方法

今日、教えていただいた方法。

Visual Studio には dumpbin.exe というコマンドラインツールがついているので、それを使う。

C:\bin> dumpbin /DEPENDENTS hoge.exe
Microsoft (R) COFF/PE Dumper Version 9.00.30729.01
Copyright (C) Microsoft Corporation.  All rights reserved.


Dump of file hoge.exe

File Type: EXECUTABLE IMAGE

  Image has the following dependencies:

    KERNEL32.dll
    msvcrt.dll
    msvcrt.dll

  Summary

        4000 .bss
        6000 .data
        1000 .idata
       35000 .rdata
        7000 .stab
       27000 .stabstr
       CE000 .text

これは便利

Amino というライブラリを使ってみた

内容

スレッド 1 で入力を Shift_JIS -> UTF-16 変換して、 UTF-16 のデータをキューに入れる。
スレッド 2 でキューから UTF-16 のデータを取り出して、 UTF-16 -> EUC-JP 変換して出力

結果

$ g++ -O2 -lpthread -licuuc -licudata main.cpp && time ./a.out > out
0.37user 0.03system 0:00.40elapsed 99%CPU (0avgtext+0avgdata 0maxresident)k
0inputs+0outputs (0major+8408minor)pagefaults 0swaps

$ g++ -O2 -lpthread -licuuc -licudata main.cpp -DMULTI_THREAD && time ./a.out > out
0.34user 0.07system 0:00.30elapsed 139%CPU (0avgtext+0avgdata 0maxresident)k
0inputs+0outputs (0major+6784minor)pagefaults 0swaps

うーん

なんか 0.1 秒早くなったけど、たぶん使いどころ違うんだろうなー。

ソース

#include <iostream>
#include <unicode/uclean.h>
#include <unicode/ucnv.h>
#include <amino/thread.h>
#include <amino/queue.h>
#include <boost/tuple/tuple.hpp>

const size_t CHUNKSIZE = 4096;

class DecodeOp
{
    std::istream &in_;
    UErrorCode uerr_;
    UConverter &ucnv_;
    int8_t charSize_;
    amino::LockFreeQueue<boost::tuple<UChar*, size_t> >& queue_;

public:
    DecodeOp(std::istream &in, const std::string &charset, amino::LockFreeQueue<boost::tuple<UChar*, size_t> >& queue)
        :   in_(in),
            uerr_(U_ZERO_ERROR), 
            ucnv_(*ucnv_open(charset.c_str(), &uerr_)),
            charSize_(ucnv_getMinCharSize(&ucnv_)),
            queue_(queue)
    {
    }

    ~DecodeOp()
    {
        ucnv_close(&ucnv_);
    }

    void operator()()
    {
        char inBuf[CHUNKSIZE];

        std::streamsize readSize = 0, consumedSize = 0, lastReadSize = 0;

        while (readSize = in_.rdbuf()->sgetn(inBuf, CHUNKSIZE - lastReadSize + consumedSize))
        {
            const char* inBufBegin  = inBuf;
            const char* inBufEnd    = inBufBegin + readSize;

            size_t bufSize          = charSize_ * readSize;
            UChar* buf              = new UChar[bufSize];
            UChar* bufBegin         = buf;
            const UChar* bufEnd     = bufBegin + bufSize;

            ucnv_toUnicode(&ucnv_, &bufBegin, bufEnd, &inBufBegin, inBufEnd, NULL, false, &uerr_);
            consumedSize = inBufBegin - inBuf;

            queue_.enqueue(boost::tuple<UChar*, size_t>(buf, bufBegin - buf));

            lastReadSize = readSize;
        }

        size_t bufSize          = charSize_ * (lastReadSize - consumedSize);
        UChar* buf              = new UChar[bufSize];
        UChar* bufBegin         = buf;
        const UChar* bufEnd     = bufBegin + bufSize;
        ucnv_toUnicode(&ucnv_, &bufBegin, bufEnd, NULL, NULL, NULL, true, &uerr_);
        queue_.enqueue(boost::tuple<UChar*, size_t>(buf, bufEnd - bufBegin));

        queue_.enqueue(boost::tuple<UChar*, size_t>(NULL, 0));
    }
};

class EncodeOp
{
    std::ostream &out_;
    UErrorCode uerr_;
    UConverter &ucnv_;
    int8_t charSize_;
    amino::LockFreeQueue<boost::tuple<UChar*, size_t> >& queue_;

public:
    EncodeOp(std::ostream &out, const std::string &charset, amino::LockFreeQueue<boost::tuple<UChar*, size_t> >& queue)
        :   out_(out),
            uerr_(U_ZERO_ERROR),
            ucnv_(*ucnv_open(charset.c_str(), &uerr_)),
            charSize_(ucnv_getMaxCharSize(&ucnv_)),
            queue_(queue)
    {
    }

    ~EncodeOp()
    {
        ucnv_close(&ucnv_);
    }

    void operator()()
    {
        boost::tuple<UChar*, size_t> bufData;

        while (true)
        {
            while (!queue_.dequeue(bufData)) { }

            UChar* buf      = boost::get<0>(bufData);
            size_t bufSize  = boost::get<1>(bufData);

            if (buf == NULL)
            {
                break;
            }

            const UChar* bufBegin   = buf;
            const UChar* bufEnd     = buf + bufSize;

            size_t writeSize   = bufSize * charSize_;

            char* outBuf = new char[writeSize];

            char* outBufBegin       = outBuf;
            const char* outBufEnd   = outBufBegin + writeSize;

            ucnv_fromUnicode(&ucnv_, &outBufBegin, outBufEnd, &bufBegin, bufEnd, NULL, false, &uerr_);

            out_.write(outBuf, (outBufBegin - outBuf));

            assert(bufEnd == bufBegin);

            delete[] outBuf;
            delete[] buf;
        }
    }
};

#include <sstream>

int main()
{
    struct Guard {
        UErrorCode err;
        Guard() : err(U_ZERO_ERROR) {
            u_init(&err);
        }
        ~Guard() {
            u_cleanup();
        }
    } guard;

    amino::LockFreeQueue<boost::tuple<UChar*, size_t> > queue;

    std::stringstream s;

    int i = 1000000;
    while (i--)
    {
        s << "\x82\xA0";
        s << "\x82\xA1";
        s << "\x82\xA2";
        s << "\x82\xA3";
    }

    DecodeOp fromSjisOp(s, "Shift_JIS", queue);
    EncodeOp toEucOp(std::cout, "EUC-JP", queue);

#ifdef MULTI_THREAD
    amino::Thread encoderThread(fromSjisOp), decoderThread(toEucOp);
    encoderThread.join();
    decoderThread.join();
#else
    fromSjisOp();
    toEucOp();
#endif

}

boost::is_convertible はどうやってるか

概ね以下のような感じ

template <typename From, typename To>
struct is_convertible_basic_impl
{
    // 2 つの同名関数を作って
    static no_type _m_check(...);
    static yes_type _m_check(To);

    // 関数の戻り値の型を見る(どっちの関数が使われるかを見る)
    static bool value = sizeof( _m_check(From) ) == sizeof(yes_type);
};

これで From 型のオブジェクトが To 型に代入できるかが、コンパイル時に分かるのな
sizeof の以外な使い道。

C++ で Buzztter を Growl する

はじめに

BuzztterRSS を持ってきて、新しいキーワードを Growl に表示するものを作ってみた。
C++ でも boost::asio とか、 libxml2 とかを使うとけっこうサクっと書ける。ってこともないか。。

必要なもの

ソース

#include <libxml/xmlreader.h>
#include <boost/asio.hpp>
#include <boost/shared_ptr.hpp>
#include <boost/unordered_map.hpp>
#include <Growl/Growl.h> 

static CFTypeRef notifications[] = {
    CFSTR("Buzz word arrived.")
}; 

static int notifyReading(void *in, char* buf, int len)
{
    return static_cast<std::istream*>(in)->rdbuf()->sgetn(buf, len);
}

int main(int argc, char *argv[]) 
{ 
    Growl_Delegate delegate; 
    InitGrowlDelegate(&delegate); 
    delegate.applicationName = CFSTR("cppclient"); 

    CFTypeRef keys[] = {
        GROWL_NOTIFICATIONS_ALL, 
        GROWL_APP_ID
    }; 

    boost::shared_ptr<const __CFArray> allNotifications(
        CFArrayCreate(
            NULL, 
            notifications,
            sizeof(notifications) / sizeof(notifications[0]),
            &kCFTypeArrayCallBacks
        ),
        &CFRelease
    ); 

    CFTypeRef values[] = {
        allNotifications.get(), 
        CFSTR("com.buzzter")
    }; 

    CFDictionaryRef dict = CFDictionaryCreate(NULL, 
            keys, values, 
            sizeof(keys) / sizeof(CFTypeRef), 
            &kCFTypeDictionaryKeyCallBacks, 
            &kCFTypeDictionaryValueCallBacks); 

    delegate.registrationDictionary = dict; 

    Growl_SetDelegate(&delegate); 

    bool first = true;

    boost::unordered_map<std::string, bool> seen;

    while (true)
    {
        boost::asio::ip::tcp::iostream s("buzztter.com", "http");
        s   << "GET /ja/rss HTTP/1.0\r\n"
            << "Host: buzztter.com\r\n"
            << "\r\n"
            << std::flush;

        std::string line;
        while (std::getline(s, line))
        {
            if (line == "\r")
                break;
        }

        boost::shared_ptr<xmlTextReader> reader(xmlReaderForIO(notifyReading, NULL, &s, NULL, NULL, 0), xmlFreeTextReader);

        while (xmlTextReaderRead(reader.get()) == 1)
        {
            if (xmlTextReaderDepth(reader.get()) == 3 && std::string("title") == reinterpret_cast<const char*>(xmlTextReaderConstName(reader.get())))
            {
                std::ostringstream title;

                while (xmlTextReaderRead(reader.get()) == 1 && xmlTextReaderDepth(reader.get()) == 4)
                {
                    title << std::string(reinterpret_cast<const char*>(xmlTextReaderConstValue(reader.get())));
                }

                if (!first && !title.str().empty() && !seen[title.str()])
                {

                    boost::shared_ptr<const __CFString> word(
                        CFStringCreateWithCString(
                            NULL, 
                            reinterpret_cast<const char*>(title.str().c_str()),
                            kCFStringEncodingUTF8
                        ),
                        CFRelease
                    );

                    Growl_NotifyWithTitleDescriptionNameIconPriorityStickyClickContext( 
                        CFSTR("Buzzter"), 
                        word.get(), 
                        CFSTR("Buzz word arrived."),
                        NULL,
                        0,
                        false,
                        NULL
                    ); 
                }

                seen[title.str()] = true;
            }
        }

        first = false;
        sleep(60);
    }
} 

こんな感じでコンパイル

$ g++ -framework Growl -framework Carbon -lboost_system-mt -lxml2 main.cpp