ドロップターゲットのC++による実装の例
ドロップターゲットをC++で実装する例
ドロップを抽象化するクラス。
動的生成で管理するならinitの内容をコンストラクターに移動するのもありだろう。
class TDropTarget; class TMyDrop { friend TDropTarget; private: HWND FHwnd; TDropTarget* FDropTarget; void release_target(); public: TDragAndDrop() : FHwnd(0), FDropTarget(NULL) {} virtual ~TDragAndDrop(); void init( HWND hwnd ); }; //------------------------------------------------------ TMyDrop::~TDragAndDrop() { if( FDropTarget ) release_target(); ::RevokeDragDrop( FHwnd ); ::OleUninitialize(); } //------------------------------------------------------ void TMyDrop::init( HWND hwnd ) { if( !FHwnd ) { ::OleInitialize( NULL ); } if( FDropTarget ) delete FDropTarget; FDropTarget = new TDropTarget( this, hwnd ); if( S_OK != ::RegisterDragDrop( FHwnd, FDropTarget ) ) { release_target(); ::OleUninitialize(); return; } FHwnd = hwnd; } //------------------------------------------------------ void TMyDrop::release_target() { if( FDropTarget ) { delete FDropTarget; FDropTarget = NULL; } } //------------------------------------------------------
IDropTargetを実装するクラス。
DropEffectは、MOVE、COPY、LINKがある。この辺は目的に沿って実装する。
もちろん、必要とするフォーマットによりFORMATETCあたりの定義は変更する。
class TDropTarget : public IDropTarget { private: TDragAndDrop* FParent; LONG FRef; HWND FHwnd; DWORD FEffect; void make_formatetc( FORMATETC &formatetc ); public: TDropTarget( TDragAndDrop *parent, HWND hwnd ) : FParent(parent), FRef(1), FHwnd(hwnd), FEffect(DROPEFFECT_NONE) {} virtual ~TDropTarget() {} STDMETHODIMP QueryInterface( REFIID ref_iid, void **ppObj ); STDMETHODIMP_(ULONG) AddRef(); STDMETHODIMP_(ULONG) Release(); STDMETHODIMP DragEnter( IDataObject *pDataObj, DWORD grfKeyState, POINTL pt, DWORD *pdwEffect ); STDMETHODIMP DragOver( DWORD grfKeyState, POINTL pt, DWORD *pdwEffect ); STDMETHODIMP DragLeave(); STDMETHODIMP Drop( IDataObject *pDataObj, DWORD grfKeyState, POINTL pt, DWORD *pdwEffect ); }; //------------------------------------------------------ void TDropTarget::make_formatetc( FORMATETC &formatetc ) { formatetc.cfFormat = CF_HDROP; formatetc.ptd = NULL; formatetc.dwAspect = DVASPECT_CONTENT; formatetc.lindex = -1; formatetc.tymed = TYMED_HGLOBAL; } //------------------------------------------------------ STDMETHODIMP TDropTarget::QueryInterface( REFIID ref_iid, void **ppObj ) { (*ppObj) = NULL; if( IsEqualIID( ref_iid, IID_IUnknown ) || IsEqualIID( ref_iid, IID_IDropTarget ) ) (*ppObj) = dynamic_cast<IDropTarget*>(this); else return E_NOINTERFACE; AddRef(); return S_OK; } //------------------------------------------------------ STDMETHODIMP_(ULONG) TDropTarget::AddRef() { return InterlockedIncrement( &FRef ); } //------------------------------------------------------ STDMETHODIMP_(ULONG) TDropTarget::Release() { if( 0 == InterlockedDecrement( &FRef ) ) { FParent->release_target(); return 0; } return FRef; } //------------------------------------------------------ STDMETHODIMP TDropTarget::DragEnter( IDataObject *pDataObj, DWORD grfKeyState, POINTL pt, DWORD *pdwEffect ) { FORMATETC formatetc; make_formatetc( formatetc ); if( S_OK == pDataObj->QueryGetData( &formatetc ) ) { *pdwEffect = FEffect = DROPEFFECT_COPY|DROPEFFECT_LINK; return S_OK; } else { *pdwEffect = FEffect = DROPEFFECT_NONE; return DRAGDROP_S_CANCEL; } } //------------------------------------------------------ STDMETHODIMP TDropTarget::DragOver( DWORD grfKeyState, POINTL pt, DWORD *pdwEffect ) { *pdwEffect = FEffect; return S_OK; } //------------------------------------------------------ STDMETHODIMP TDropTarget::DragLeave() { return S_OK; } //------------------------------------------------------ STDMETHODIMP TDropTarget::Drop(IDataObject *pDataObj, DWORD grfKeyState, POINTL pt, DWORD *pdwEffect) { FORMATETC formatetc; make_formatetc( formatetc ); *pdwEffect = DROPEFFECT_NONE; STGMEDIUM medium; if( S_OK != pDataObj->GetData( &formatetc, &medium ) ) { return E_FAIL; } *pdwEffect = FEffect; // それぞれの処理 ::ReleaseStgMedium(&medium); return S_OK; } //------------------------------------------------------
Shell IDList Arrayについて
クリップボードフォーマットにおいて、Shell IDList Arrayを用いた通信について記事が少ないので一言。
CIDAのフォーマットの定義は以下の通りだ。
typedef struct _IDA { UINT cidl; // IDLISTの数 UINT aoffset[1]; // IDLIST構造体のリスト } CIDA, * LPIDA;
実際には可変長構造体でaoffsetは0~cidlの序数を持つ。定義ではそうは読み取れないが、Win32apiやDLL周りを扱う場合には頻繁に出てくる表現だ。
ここで、aoffsetの個数がcidl+1個なのは間違いではない。
aoffset[0]は、フォルダーのIDLISTへのポインター、aoffset[1]~aoffset[cidl]は、フォルダー内でドラッグアンドドロップされているファイルのIDLISTへのポインターだ。
前者は絶対参照、後者は相対参照なので、ファイルのIDLISTを使ってSHGetPathFromIDListを呼び出しても正しいパスは得られない。
ファイルを得るためのコードを以下に示す。
// Shell IDList Arrayのクリップボードフォーマットがあるか確認 CLIPFORMAT get_ShellIDListArray_ID( IDataObject *pDataObj ) { TCHAR szFormatName[256]; unsigned int nSize = sizeof(szFormatName) / sizeof(TCHAR); FORMATETC formatetc; IEnumFORMATETC *pEnumFromatetc; pDataObj->EnumFormatEtc( DATADIR_GET, &pEnumFromatetc ); while( S_OK == pEnumFromatetc->Next( 1, &formatetc, NULL ) ) { ::GetClipboardFormatName( formatetc.cfFormat, szFormatName, nSize ); if( 0 == ::lstrcmp( szFormatName, CFSTR_SHELLIDLIST ) ) break; formatetc.cfFormat = 0; } pEnumFromatetc->Release(); return formatetc.cfFormat; } // ポインター操作用補助関数 BYTE* byte_cast( LPIDA pIda, UINT p ) { return reinterpret_cast<BYTE*>(pIda)+pIda->aoffset[p]; } // IDropTargetの実装として定義したTDropTargetのメンバー関数 STDMETHODIMP TDropTarget::Drop(IDataObject *pDataObj, DWORD grfKeyState, POINTL pt, DWORD *pdwEffect) { FORMATETC formatetc; formatetc.cfFormat = get_ShellIDListArray_ID( pDataObj ); formatetc.ptd = NULL; formatetc.dwAspect = DVASPECT_CONTENT; formatetc.lindex = -1; formatetc.tymed = TYMED_HGLOBAL; if( 0 == fotmatetc.cfFormat ) return E_FAIL; // クリップボードデータの取り込み STGMEDIUM medium; if( S_OK != pDataObj->GetData( &formatetc, &medium ) ) return E_FAIL; if( medium.tymed != TYMED_HGLOBAL ) { ::ReleaseStgMedium( &medium ); return E_FAIL; } // 内部データのメモリー展開 CIDA *pIda = static_cast<CIDA*>(::GlobalLock( medium.hGlobal )); // フォルダーIDLISTへのポインター PIDLIST_ABSOLUTE pidl_root = reinterpret_cast<PIDLIST_ABSOLUTE>( byte_cast( pIda, 0 ) ); // ファイルのリストアップ for( unsigned int i = 1; i <= pIda->cidl; ++i ) // 各ファイルの序数は1~cidl { // ファイルへのIDLISTへのポインター PIDLIST_RELATIVE pidl_file = reinterpret_cast<PIDLIST_RELATIVE>( byte_cast( pIda, i ) ); // ファイルへのフルパスのIDLISTを構成 PIDLIST_ABSOLUTE pidl = ::ILCombine( pidl_root, pidl_file ); // ファイル名を得る TCHAR szSrc[MAX_PATH]; ::SHGetPathFromIDList( pidl, szSrc ); // ・・・なにがしかの処理 // ファイルフルパスのIDLISTのメモリーを解放 ::ILFree( pidl ); } // メモリー展開を解除 ::GlobalUnlock(medium.hGlobal); // クリップボード構造体の解放 ::ReleaseStgMedium(&medium); }
ILCombineを用いて、絶対パスのPIDLIST_ABSOLUTEを得る。使用を終えたら解放を忘れずに。
あと、SHGetPathFromIDListではUWPプログラムへのパスを得られない。Microsoft Edgeのパスは得られないなど、UWPのプログラムへのパスを得るとか大変面倒だ。
Crypto APIによる暗号化
Crypto APIによる暗号化は、ちょっとした用途で暗号化強度がさほど必要でない場合に便利だ。
今回は文字列を暗号化・復号化するルーチンのサンプルを提示する。
もっと強度が必要な場合には、Cryptography API: Next Generationを用いるとよいようだ。
こちらについては、「主筆」さんのHPにある記事がわかりやすい。
http://www.syuhitu.org/other/cng/cng.html
まず、カプセル化。なお以下のコードではSHA-256などは用いることができない。
#include <wincrypt.h> class TMyCrypt { private: HCRYPTPROV hProv; HCRYPTHASH hHash; HCRYPTKEY hKey; bool is_init(); public: TMyCrypt() : hProv(0), hHash(0), hKey(0) {}; ~TMyCrypt() { finalize(); } bool init( const TCHAR *pass, ALG_ID hash_alg = CALG_SHA1, ALG_ID enc_alg = CALG_RC4, DWORD keyLen = 0x00800000 ); void finalize(); bool encrypt( BYTE *ret, const TCHAR *src, DWORD &dwLen ); bool decrypt( TCHAR *ret, const BYTE *src, DWORD &dwLen ); }; //---------------------------------------------------------------- bool TMyCrypt::is_init() { return (hKey)? true : false; } //---------------------------------------------------------------- bool TMyCrypt::init( const TCHAR *pass, ALG_ID hash, ALG_ID enc, DWORD dwKeyLen ) { finalize(); // 二重突入対策 if( !CryptAcquireContext( &hProv, NULL, MS_ENHANCED_PROV, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT ) ) return false; if( CryptCreateHash( hProv, hash, 0, 0, &hHash ) ) { DWORD dwLen = ::lstrlen(pass)*sizeof(TCHAR); if( CryptHashData( hHash, reinterpret_cast<const BYTE*>(pass), dwLen, 0 ) ) return CryptDeriveKey( hProv, enc, hHash, dwKeyLen, &hKey ); } finalize(); return false; } //---------------------------------------------------------------- void TMyCrypt::finalize() { if( hKey ) { CryptDestroyKey( hKey ); hKey = 0; } if( hHash ) { CryptDestroyHash( hHash ); hHash = 0; } if( hProv ) { CryptReleaseContext( hProv, 0 ); hProv = 0; } } //---------------------------------------------------------------- bool TMyCrypt::encrypt( BYTE *ret, const TCHAR *src, DWORD &dwLen ) { if( !is_init() ) return false; dwLen = ::lstrlen(src)*sizeof(TCHAR); memcpy( ret, src, dwLen ); bool bRet = CryptEncrypt( hKey, 0, true, 0, ret, &dwLen, dwLen ); return bRet; } //---------------------------------------------------------------- bool TMyCrypt::decrypt( TCHAR *ret, const BYTE *src, DWORD &dwLen ) { if( !is_init() ) return false; memcpy( ret, src, dwLen ); bool bRet = CryptDecrypt( hKey, 0, true, 0, reinterpret_cast<BYTE*>(ret), &dwLen ); return bRet; }
使用例は、以下のとおり。Crypto自体はバイナリの配列を暗号化するためencryptの返却値はBYTEだ。
バッファサイズについては十分配慮する必要がある。例では手抜きをしている。
今回の例では、カプセル化でパスワードは初期化時に渡しているが、この辺のさじ加減は好み次第だろう。
void test() { TCHAR *src = T_"TESTWORD"; TCHAR *pass = T_"DEBUG2"; TMyCrypt crypt; if( !crypt.init( pass ) ) // crypt.init( pass, CALG_SHA1, CALG_RC4, 0x00800000 )の省略形 return; BYTE buf[100] = {0}; TCHAR ret[100] = {0}; DWORD dwLen; if( crypt.encrypt( buf, src, dwLen ) ) { //・・・ if( crypt.decrypt( ret, buf, dwLen ) ) { //・・・・ } } crypt.finalize(); }
コードでTCHARを用いているので、文字列がcharの場合でもwchar_tの場合でも同じように動作する。
今回の例で選択可能なハッシュ鍵生成アルゴリズムは、認証の不要なハッシュアルゴリズムだ。
deleteをなくすには
deleteコードを減らすことは、複雑なコードの保守やリソースリークの観点で望ましい。
下のようなコードでdeleteを消したい。
なお、new[]で確保した配列は、delete[]で削除しなければいけないことに注意。
class C { public: char* buf; unsigned int size; C( char* p, unsigned int sz ) : buf(new char[sz]), size(sz) // new[]で確保 { memcpy(buf, p, sz); }; ~C() { delete[] buf; } // delete[]で削除 }; void test_code() { vector<C> v; char* m = new char[100]; // new[]で確保 C a(m, 100); v.push_back(a); delete[] m; // delete[]で削除 }
unique_ptrを用いてみよう。unique_ptrも配列については配慮が必要だ。
代入演算子で余分な削除が起きている(reset)。
class C { public: unique_ptr<char[]> buf; unsigned int size; C( unique_ptr<char[]>& p, unsigned int sz ) : buf(move(p)), size(sz) {} C( const C& lhs ) : buf(new char[lhs.size]), size(lhs.size) { memcpy( buf.get(), lhs.buf.get(), size ); } const C& operator=( const C& lhs ) { size = lhs.size; buf.reset( new char[size] ); memcpy( buf.get(), lhs.buf.get(), size ); return *this; } ~C() {} }; void test_code() { vector<C> v; unique_ptr<char[]> m( new char[100] ); C a(m, 100); // mからaへ配列ポインターは移動する v.push_back(a); }
配列ポインターの移動のためにコンストラクターを追加したということもあるが、コード数が増えてかえってメンテがしにくいような気もする。
このような場合は素直にvectorを使えば簡易になる。
class C { public: vector<char> buf; C() : buf() {} C( vector<char> &p ) : buf(p){} ~C() {} }; void test_code() { vector<C> v; vector<char> m(100); C a(m); v.push_back(a); }
文字列であればstringを用いることもできる
class C { public: string buf; C() : buf() {} C( string &p ) : buf(p){} ~C() {} }; void test_code() { vector<C> v; string m; m.resize(100); C a(m); v.push_back(a); }
vectorとunique_ptr その3
vectorはコピーを使う、だからauto_ptrとおなじくunique_ptrは使えない。
以下のコードはコンパイルエラーが発生する。
vector<unique_ptr<C> > v; v.push_back( unique_ptr<C>( new C ) );
まれに、このコードをおすすめするHPが見られるが、コンパイルできるコンパイラーがあるのだろうか?
コンパイルできたとしても、今まで見てきたように、sortなどで副作用が出るので行うべきではない。
C++11対応のコンパイラーであれば、push_backの代わりにemplace_backが使えるかもしれないが、セットは可能でも、やはりsortで問題が発生する。
対処方法はやはりshared_ptrに置き換えればよい。
vector< shared_ptr<C> > v; v.push_back( shared_ptr<C>( new C ) );
shared_ptrのコストを避けようとするのであれば、削除に備える必要がある。
typedef std::vector<unique_ptr<C> > Vip; typedef std::vector<unique_ptr<C> >::iterator Vip_itr; typedef std::vector<unique_ptr<C> >::const_iterator Vip_citr; class D { void _clear( Vip_itr iSt, Vip_itr iEd ) { for( ; iSt < iEd; ++iSt ) { if( *iSt ) delete *iSt; } } void _copy( Vip &dst, Vip_citr iSt, Vip_citr iEd ) { for( ; iSt < iEd; ++iSt ) { dst.push_back( new int(**iSt) ); } } public: D() {}; D( const C& lhs ) { _copy( v, lhs.v.begin(), lhs.v.end() ); } Vip v; void resize( unsigned int size ) { if(size<v.size()) _clear( v.begin()+size-1, v.end() ); } Vip_itr erase( Vip_itr itr ) { if( ( itr != v.end() )&&( *itr ) ) delete *itr; return v.erase( itr ); } void clear() { _clear( v.begin(), v.end() ); } };
D v;
v.v.push_back(unique_ptr<C>(new C));
これでも抜けがあるかもしれない。実際、重複したポインター参照などを考慮していない。vectorをそのまま使えないことも問題があるだろう。
文芸的にはよいかもしれないが、実践的ではないと思う。
vectorとunique_ptr その2
前回はコピーすべきところでムーブすべきではないことがわかった。
コードはshared_ptrを用いることで、すべてがうまくいくようになる。
class C { public: shared_ptr<int> d; C() : d(new int(0)) {} C( const int n ) : d(new int(n)) {} // コピーコンストラクタ、オペレータ=は自動生成でok ~C() {} }; bool operator<(const C& lhs, const C& rhs ) { return lhs.d.get() < rhs.d.get(); } void test_code() { vector<C> v; C a(3), b(2), c(1); v.push_back(a); v.push_back(b); v.push_back(c); sort( v.begin(), v.end() ); // OK }
ただし、shared_ptrは生のポインターやunique_ptrを用いる場合と比べてリソースやスピードなどのコストがかかる。
ポインターをムーブさせることがだめなのであるから、コピーはコピーということに立ち返れば、unique_ptrを使うこともできる。
class C { public: unique_ptr<int> d; C() : d(new int(0)) {} C( const int n ) : d(new int(n)) {} C( const C& lhs ) : d(new int(*lhs.d.get())) {}// コピーコンストラクタ const C& operator=( const C& lhs ) // 代入 { *d.get() = *lhs.d.get(); return *this; } ~C() {} }; bool operator<(const C& lhs, const C& rhs ) { return *lhs.d.get() < *rhs.d.get(); } void test_code() { vector<C> v; C a(3), b(2), c(1); v.push_back(a); v.push_back(b); v.push_back(c); sort( v.begin(), v.end() ); // OK }
上記コードでは、コピーでポインターをムーブさせるのではなく、ポインターの先をコピーさせるように変更している。
どちらがよいのだろうか?
shared_ptr版は、ポインターの先を共有するので、ポインターのコピーとしてはサイズが大きくなりかつ低速となる。しかしオブジェクトのコピーを行わない。
unique_ptr版は、ポインターの先を共有しない。このためポインターのコピーとは別にオブジェクトの生成コストがかかる。通常オブジェクトの生成はポインターのコピーよりずっと遅い。
ポインターを使いたくなるシーンを考えると、class Cの実装はshared_ptr版の方がメモリーサイズも速度も速くなる可能性が高いと思われる。また、書くべきコードも少なく見通しもよいだろう。
unique_ptr版の方がよい場合は、vectorにすべて異なるオブジェクトを格納し、かつ静的なコンテナ(変更しない読み出し専用)として用いる場合だけではないだろうか。