続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再生には至っていません。