続cdecl_thunk

昨日のcdecl_thunkを使うと、ASIOのコールバック関数は次のようになります。

struct asio_callbacks
{
    cdecl_thunk<2> buffer_switch;
    cdecl_thunk<2> sample_rate_changed;
    cdecl_thunk<4> asio_message;
    cdecl_thunk<3> buffer_switch_time_info;
};

sample_rate_changedは引数1個なんですが、型がdoubleなので引数2個の扱いです。
サンクのメモリはVirtualProtect()で実行可能にしておく必要があります。VirtualProtect()するには、そのメモリをVirtualAlloc()で確保しておかなければいけません。
VirtualAlloc()はページ単位でメモリを確保するため、サンク毎にメモリを割り当てるとメモリを無駄に使います。ここでは、4つのサンクをまとめて1回のVirtualAlloc()でまかなうことにします。(それでも無駄が多いですが)

ただの途中経過の報告になってしまいますが、現状こんな感じになっています。

class virtual_memory : boost::noncopyable
{
public:
    explicit virtual_memory(
            std::size_t size, unsigned long mode=PAGE_READWRITE)
        : ptr_(::VirtualAlloc(0, size, MEM_COMMIT|MEM_RESERVE, mode))
        , size_(size)
    {
        if (ptr_ == 0)
            throw std::runtime_error("cannot allocate virtual memory");
    }

    ~virtual_memory()
    {
        ::VirtualFree(ptr_, 0, MEM_DECOMMIT|MEM_RELEASE);
    }

    void* address()
    {
        return ptr_;
    }

    // プロテクトの変更
    unsigned long protect(unsigned long mode)
    {
        unsigned long old = 0;
        if (!::VirtualProtect(ptr_, size_, mode, &old))
            throw std::runtime_error("failed VirtualProtect");
        return old;
    }

private:
    void* ptr_;
    std::size_t size_;
};

template<std::size_t Argc>
class cdecl_thunk
{
public:
    // 注:配置newが面倒なので初期化関数はコンストラクタでなくなった
    void set_instance(void* func, void* ptr)
    {
        // 昨日と同じなので略

        ::FlushInstructionCache(
            ::GetCurrentProcess(), &entry_[0], sizeof(entry_));
    }

    void* address() { return entry_; }

    // 関数の型を書くのが面倒くさいので自動判定してコピーする
    template<class T>
    void copy_address(T*& ptr)
    {
        ptr = reinterpret_cast<T*>(address());
    }

private:
    boost::uint8_t entry_[18+4*Argc];
};

class asio_sink::impl : boost::noncopyable
{
public:
    impl(/* 引数略 */) : thunks_(sizeof(asio_callbacks))
    {
        // アラインメントは問題ないはず
        asio_callbacks* cbs_ptr =
            static_cast<asio_callbacks*>(thunks_.address());

        // 実行コード生成
        cbs_ptr->buffer_switch.set_instance(
            reinterpret_cast<void*>(&impl::buffer_switch_helper), this);
        cbs_ptr->buffer_switch.copy_address(callbacks_.bufferSwitch);

        // 他のメンバの設定略

        // 書き込みを禁止、実行を可能に
        thunks_.protect(PAGE_EXECUTE_READ);

        // 続く
    }

private:
    virtual_memory thunks_;

    // サンクで設定する関数
    static void buffer_switch_helper(
        impl* this_ptr, long doubleBufferIndex, ::ASIOBool directProcess);
};

プロテクトの設定をPAGE_NOACCESSに変更すると、アクセス違反になったので設定は効いているようです。肝心のASIO再生には至っていません。