昆布大好き!

主にプログラミングの技メモ

Haskellコードを動的ライブラリにしてCから使用する その2

つづきです。

ghcで作成したライブラリを呼び出すコードです。

#include <iostream>

extern "C" {
int hslibInit(int argc, char *argv[]);
void hslibEnd();
int exprI(wchar_t *str);
int exprCheck(wchar_t *str);
}

int main(int argc, char *argv[])
{
    int n = 1;
    char *v_[] = {"exe.exe", NULL, };
    char **v = v_;
    hslibInit(n, v);    // hs_initを間接的に呼ぶ

    if ( exprCheck(L"8*3") ) {
        std::cout << exprI(L"8*3") << std::endl;
    }

    hslibEnd();    // hs_exitを間接的に呼ぶ
    return 0;
}

Haskellのコードを実行する前と後に、それぞれhs_initとhs_exit(ghc付属のHsFFI.hに定義)を呼び出します。 ここではこれらをDLLに含んで間接的に呼んでいます。 (HsFFI.hをVCのコンパイラからパスを指定して呼び出すこともなかろうなので)

hslibInitに渡しているのは、Haskellコードが参照するコマンドライン引数のようです。Haskell側のコードで参照していなければ適当なのを渡しても平気です。

プロトタイプ宣言の部分は別途ヘッダファイルにまとめておいた方が便利かと。 あとはこれを、ライブラリを参照してコンパイルすればよいです。

Haskellコードを動的ライブラリにしてCから使用する その1

それほど難しくはないのですが、その分細かい手順を忘れやすいので。

まずは、動的ライブラリ(DLL)の作成まで。 必要な手順は、

  1. Haskellのコードで関数を公開して、外部から参照できるようにする
  2. DLLのインタフェイスとなるCのコードから公開したコードを呼び出す
  3. コンパイルしてDLL(と静的ライブラリ)を作成する

となります。 (他にもやり方はあるかもしれません)

今回の環境。

Haskell側の記述です。公開したい関数を次のように指定します。Cの方の型を扱うため特別なimportも必要です。

-- ExprEval.hs
import Foreign.C.Types
import Foreign.C.String

foreign export ccall cexprI :: CWString -> IO CInt
foreign export ccall cexprF :: CWString -> IO CFloat
foreign export ccall cexprCheck :: CWString -> IO CInt

次はこれらを呼び出すC側の記述です。 DLLのインタフェイスとして実際に呼び出す関数はこちらです。 ラッパーとして作成して扱いやすくします。

// cexpr.c
#include <HsFFI.h>

#ifdef __GLASGOW_HASKELL__
#include "expr_stub.h"
extern void __stginit_ExprEval(void);
#endif


int exprCheck(const wchar_t *str)
{
    return cexprCheck((HsPtr)str);
}

float exprF(const wchar_t *str)
{
    return cexprF((HsPtr)str);
}

int exprI(const wchar_t *str)
{
    return cexprI((HsPtr)str);
}

HsBool hslibInit(int argc, char *argv[])
{
    hs_init(&argc, &argv);
    return HS_BOOL_TRUE;
}

void hslibEnd()
{
    hs_exit();
}

あとはこれらをまとめてghcコンパイルです。DLLと静的ライブラリが作成されます。 静的ライブラリの拡張子が.aとなるかもしれません。

ghc -shared cexpr.c ExprEval.hs -o ExprEval.dll
ren ExprEval.dll.a ExprEval.dll.a

それから、作成したDLLはHaskellのランタイムを含んでいます。1Mバイトほどサイズが大きくなります。 ghcのオプションに-dynamicをつけると含まなくなり、小さく作れます。

DLLを呼び出す側にもう少しお作法がありますので、次回にまた。

C++uniqu_ptrとshared_ptr

C++の標準ライブラリにはunique_ptrとshared_ptrと同じようなスマートポインタがあります。 何が違うのか?どう使い分ければ良いのか?

と、気になりましたので少し調べてみました。

実行環境は、

以下のようなコードを、 最適化無しで 実行してみましたところ、

#include <iostream>
#include <memory>
#include <ctime>

int main()
{
    const int N = 100000000;
    std::shared_ptr<int>  sptr(new int);
    std::unique_ptr<int>  uptr(new int);
    clock_t s, e;
    s = clock();
    for ( int i=0; i<N; i++ ) {
        int *p = sptr.get();
    }
    e = clock();
    std::cout << "clock=" << (e-s) << "[tick]" << std::endl;

    s = clock();
    for ( int i=0; i<N; i++ ) {
        int *p = uptr.get();
    }
    e = clock();
    std::cout << "clock=" << (e-s) << "[tick]" << std::endl;

    return 0;
}

shared_ptr型のgetは750[tick]、uniaue_ptr型のgetは453[tick]ほどで1.5倍ほどの差があります。

最適化無しでは 。 最適化すると、この程度の比較では両者の差は全く無くなります。 上のコード程度の単純な使い方であれば、使い分けることをあまり考えても良いのかもしれません。

厳密に性能面での差異を見つけるにはもっと別の面から比較する必要があるようです。

c++ list と vector

C++STLにはlistとvectorなどとよく似たコンテナ型がありますが、 何が違うのか?どう使い分けるか?

と、気になりましたので少し試してみました。

実行環境は、

まずは要素追加の関数push_backから。 このようなコードで追加個数(N)を変えながら試してみました。

#include <iostream>
#include <list>
#include <vector>
#include <ctime>

int main()
{
    const int N = 40000000;
    std::list<int>  l;
    std::vector<int> v;

    clock_t s, e;
    s = clock();
    for ( int i=0; i<N; i++ ) {
        l.push_back(0);
    }
    e = clock();
    std::cout << "clock=" << (e-s) << "[tick]" << std::endl;

    s = clock();
    for ( int i=0; i<N; i++ ) {
        v.push_back(0);
    }
    e = clock();
    std::cout << "clock=" << (e-s) << "[tick]" << std::endl;

    return 0;
}

結果、

N=10000000 N=20000000 N=40000000
vector 109 266 422
list 847 1672 3375

のように、およそ比例しているようです。 要素の追加は要素数にかかわらず定数時間(o(1))で行います。

リストの先頭への要素追加push_frontについては、vector型は不可能で、 list型はやはり定数時間で行います。

ランダムアクセスはlist型は不可能で、vector型はやはり定数時間で行います。

ここまでをまとめると、

push_back push_front ランダムアクセス
vector o(1) 不可 o(1)
list o(1) o(1) 不可

以上、もちろん実装にもよりますが、使い分けるときの参考になれば。

TCPコネクション リセットの理由

ソケットに対してcloseを呼び出して、TCPコネクションを切断すると切断をリセットされた(エラーメッセージだと'Connection reset by peer'など)と判断される場合があります。

この場合、TCPヘッダにRSTフラグがセットされています。 同じようにcloseを呼び出したつもりでも、リセットになるのは何故なのか?

次の二つを同時に満たした場合のようです。

  • 未受信(受信バッファからデータを取り出していない)がある
  • 明示的にshutdownを呼び出していない

主には一つ目のように正常に通信を終了していなさそうなことが原因になるようです。 それでもなお、明示的にshutdownを呼び出していた場合は、リセットでなく正常の切断(FINフラグ)とするようです。

(ソケットライブラリの実装にもよるかもしれません。今回は正確に覚えていませんが、Linuxのソケットライブラリでした)

リセットによる切断はあまり普通ではない、とのことなので理由としては納得できる気がします。

失敗しました Pythonからdllを読み込む

Pythonからdllを読み込もうとして失敗しました。

SetMouse.dllなる動的ライブラリをあらかじめ作成して、

import ctypes

dll = ctypes.windll.SetMous

と、すると

OSError: [WinError 193] %1 は有効な Win32 アプリケーションではありません。

などと読み込めないことがありました。

どうやら、dllを64ビット用にコンパイルしなければならなかったらしいのです。OSが64ビットのためなのか、インタプリタが64ビットのためなのか。 (エラーメッセージはなんだったのか)

先日の投稿

コマンドラインからVCコンパイル - 開発用コンソール設定 - 芸州伝 - げいしゅうでん

に話題が戻りまして、これがいけなかった。 32ビット向けの開発環境に設定されるようですね。

こちらの記事 方法: 64 ビットの Visual C++ ツールセットをコマンド ラインから有効にする の様に、別のバッチファイルでx86向けかx64か切り替えてからコンパイルしなければいけなかったのです。

が、

私の環境では、

vcvarsall x86_amd64

のように、少し異なる引数を与えて正しく実行されるようです。

なぜなのか。AMDとは何なのか。私はK6-3以降は知りません。

そういえば、このころはIntel CPUをAMDに乗せ換えたりできましたね。クロック数が3倍になりましたね。カオスでしたね。

コマンドラインからVCコンパイル - 開発用コンソール設定

Windows なのに、ターミナルを使うお話。

c:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\bin\vcvars32.bat

にあるようなバッチファイルを呼び出せば、VCコンパイラのために環境変数をセットしてくれます。 (上記は、私のPCの場合。OSのバージョンや、Visual Studioのバージョンによってパスは少し変わります。)

バッチを呼び出した後は、

cl main.c

のように、コマンドプロンプトからコンパイルを実行出来ます。

Visual Studioが起動するのを待ってられねーぜ。 といった、せっかちさんにはうってつけです。

が、

最近になってからようやく、あまりよろしくない、古いやり方だと思い知らされました。

これについてはまた後日。