読者です 読者をやめる 読者になる 読者になる

ショートカットにおけるファイルの実体参照解決の順番

ショートカットを作成するプログラムのテストをしているとき、ショートカットの実体参照に優先順位があることに気がついた。

ショートカット内のプログラムへの実体参照はIDLISTとPathの2つがある。
ショートカットの実行では、まずIDLISTが参照され、IDLISTが設定されていないときのみPathが参照される。

そして、SHGetPathFromIDListではUWPプログラムへのパスが得られないのだが、UWPプログラムのIDLISTを設定できたのであれば、そのショートカットのアイコンが正しく表示されるし、実行もされる。

ドロップターゲットの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の序数を持つ。
ここで、数がcidl+1個なのは間違いではない。

aoffset[0]は、フォルダーのIDLISTへのポインター、1~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;
}

// 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>
                (reinterpret_cast<BYTE*>(pIda)+pIda->aoffset[0]);

  // ファイルのリストアップ
  for( unsigned int i = 0; i < pIda->cidl; ++i )
  {
    // ファイルへのIDLISTへのポインター
    PIDLIST_RELATIVE pidl_file = 
       reinterpret_cast<PIDLIST_RELATIVE>
          (reinterpret_cast<BYTE*>(pIda)+pIda->aoffset[i+1]);

    // ファイルへのフルパスのIDLISTを構成
    PIDLIST_ABSOLUTE pidl = ::ILCombine( pidl_root, pidl_file );

    // ファイル名を得る
    TCHAR szSrc[256];
    ::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にある記事がわかりやすい。
Cryptography API: Next Generationを使う


まず、カプセル化。なお以下のコードでは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にすべて異なるオブジェクトを格納し、かつ静的なコンテナ(変更しない読み出し専用)として用いる場合だけではないだろうか。