none
Drag&Dropについて RRS feed

  • 質問

  • お世話になります。

    C#で、アプリケーションを作成しDrag&Drop機能を実装し、WEBDAVフォルダのファイルをアプリケーションDROPした時の
    データの解析を行っています。

    C#だけでやれないこともないが、C++での経験があるなら、C++/CLIを使用したほうが、比較的に簡単(?)に実装ができるとC#フォーラムでいただき、
    実装を試みています。

    プログラムの内容は、C#側の  private void OnDragDrop(object sender, DragEventArgs e)メソッドの、DragEventArgs eを、
    C++/CLI側のクラス・メソッドの引数して解析するプログラムを作成しています。

    質問1.
         Object^マネージ参照型からネイティブFILEGROUPDESCRIPTOR構造体へ変換をmarshal_asを拡張して実装したが
         こんなやり方で問題がないのかを、見てほしいです。 一応、動作検証は問題はありませんでした。

         なにを不安視しているかというと、もうひとつメモリのイメージがわかないのです。

              C++では、ハンドルから固定メモリにしてアドレスを取得していました。
         →FILEGROUPDESCRIPTOR *  file_group_descriptor = (FILEGROUPDESCRIPTOR *) GlobalLock(storage.hGlobal);
         C++/CLI
         →MemoryStream^ mems = (MemoryStream^)from;

         としていますが、同じ意味をもつのでしょうか。

    質問2.配列のコピー

         マネージ型の配列から、アンマネージ型の配列にコピーしていますが、こんなやり方でいいのか。
         もっとスマートな方法がないのか、です。
         

    質問3.FileContentsの取得方法

        Forms.IDataObject.GetData()ではなく、ComTypes.IDataObject.GetData()を直接呼ぶ方法がないのかです。    
         



    質問1、2.FILEGROUPDESCRIPTOR取得ソース

    // 解析メソッド
      void ShowFileGroupDescriptorW(DragEventArgs^ e)
      {
       Object^ from = e->Data->GetData(CFSTR_FILEDESCRIPTOR);
       marshal_context context;
       FILEGROUPDESCRIPTOR* filegr = context.marshal_as<FILEGROUPDESCRIPTOR*>(from);



    // marshal_asの拡張Object^→FILEGROUPDESCRIPTOR*へマーシャリング
    namespace msclr
    {
     namespace interop
     {
      // FILEGROUPDESCRIPTORマーシャリング
      template<>
      ref class context_node<FILEGROUPDESCRIPTOR*, System::Object^> : public context_node_base
      {
         private:
       marshal_context^ _context;
       byte* _ptr;

         public: 
       context_node(FILEGROUPDESCRIPTOR*& to, System::Object^ from)
       {
        MemoryStream^ mems = (MemoryStream^)from;
        array<byte>^ buff =gcnew array<byte>((int)mems->Length);
        mems->Read(buff,0,(int)mems->Length);

        pin_ptr<byte> p = &buff[0];

        _ptr = new byte[(int)mems->Length];
        memcpy(_ptr,p,(int)mems->Length);

        p = nullptr;


        to = (FILEGROUPDESCRIPTOR*)_ptr;
       }
       ~context_node()
       {
           this->!context_node();
       }
      protected:
          !context_node()
          {
        delete []_ptr;
       }
      };



    ご教授ください





    2010年1月13日 9:27

回答

  • ここまでManagedで記述するのなら、いっその事C#で続けて、
    C#をMasterするのも良いのかなと思います。

    >ftc.tymed = System::Runtime::InteropServices::ComTypes::TYMED::TYMED_HGLOBAL | >System::Runtime::InteropServices::ComTypes::TYMED::TYMED_ISTREAM;
    これ間違っていませんか?
    その後、IStream決めうちの処理が続きますが、本当にIStreamが取れているか確認しましょう。

    >ReleaseStgMedium
    たいしたことはしていないので、
    MSDNを読んで自分でReleaseStgMediumを実装するのもありです。

    さて、C++/CLIの利点であるManaged/Unmanagedの混在ですが、
    例えば以下のような記述になります。

    private: System::Void DragDropForm_DragDrop(System::Object^  sender, System::Windows::Forms::DragEventArgs^  e) 
    {
    	::IDataObject *lpDataObject;
    	::STGMEDIUM stgMedium;
    	::FORMATETC formatETC;
    	::CIDA *lpCida;
    	HRESULT hRes = S_OK;
    
    	//Initialize Local Variables...
    	::ZeroMemory(&stgMedium,sizeof(::STGMEDIUM));
    	::ZeroMemory(&formatETC,sizeof(::FORMATETC));
    	formatETC.dwAspect = DVASPECT_CONTENT;
    	formatETC.lindex = -1;
    	formatETC.tymed = TYMED_HGLOBAL;
    	formatETC.cfFormat = ::RegisterClipboardFormat(CFSTR_SHELLIDLIST);
    
    	//Initialize lpDataObject
    	::IUnknown *lpUnknown = (::IUnknown*)Marshal::GetIUnknownForObject(e->Data).ToPointer();
    	hRes = lpUnknown->QueryInterface(IID_IDataObject,(void**)&lpDataObject);
    	lpUnknown->Release();
    
    	//Get data
    	hRes = lpDataObject->GetData(&formatETC,&stgMedium);
    
    	//Process ShellIDList
    	lpCida = (CIDA*)::GlobalLock(stgMedium.hGlobal);
    	//Add your code here ....
    
    	//Clean up
    	::GlobalUnlock(stgMedium.hGlobal);
    	::ReleaseStgMedium(&stgMedium);
    	lpDataObject->Release();
    }
    
    Error処理は行っておりませんので、適宜確認してください。
    DragDropForm_DragDrop Event HandlerとMarshal::GetIUnknownForObjectがManaged Codeで、
    それ以外はUnmanaged Codeです。


    • 編集済み kozz 2010年1月16日 5:14 記載ミス
    • 回答としてマーク TAKAKUN 2010年1月26日 6:08
    2010年1月16日 5:00

すべての返信

  • Forms.IDataObject.GetData()ではなく、ComTypes.IDataObject.GetData()を直接呼ぶ方法がないのか

    e.Data を望みの型にキャストしてみりゃいいと思いますよ。
    さらに言うと、Marshal::GetIUnknownForObject で IUnknown* が取れたり取れなかったりします。取れたのなら、ネイティブの ::IDataObject に QueryInterface も可能ですね。
    2010年1月13日 10:44
  • ご返事ありがとうございました。

    キャストし、ファイルコンテンツをローカルに保存することができたのですが、OS自体が固まったりして不安定になります。
    なにか適切な解放忘れを行っていると思われるのですが、下記のソースコードから御教授をお願いします。

    自分で思うことは、ReleaseStgMedium()をコールしていないことが原因かと思うのですが、
    ReleaseStgMedium()メソッドのパラメータをどのように指定してよいのかわかりません。



      void SaveDataContents(DragEventArgs^ e,String^ filename,int nIndex)
      {
       // ComTypes::IDataObjectキャスト
       System::Runtime::InteropServices::ComTypes::IDataObject^ data;
       data = (System::Runtime::InteropServices::ComTypes::IDataObject^)(e->Data);
       
       // FORMATETC構造体の初期化
       System::Runtime::InteropServices::ComTypes::FORMATETC ftc;
       ftc.cfFormat = ::RegisterClipboardFormat(CFSTR_FILECONTENTS);
       ftc.lindex = nIndex;
       ftc.dwAspect = System::Runtime::InteropServices::ComTypes::DVASPECT::DVASPECT_CONTENT;
       ftc.tymed = System::Runtime::InteropServices::ComTypes::TYMED::TYMED_HGLOBAL | System::Runtime::InteropServices::ComTypes::TYMED::TYMED_ISTREAM;
       ftc.ptd = System::IntPtr::Zero;

       // グローバル メモリ ハンドルの取得
       System::Runtime::InteropServices::ComTypes::STGMEDIUM stgmedium;
       data->GetData(ftc,stgmedium);
       // 取得したハンドルからIStreamインターフェイスの取得
       System::Runtime::InteropServices::ComTypes::IStream^ iStream = (System::Runtime::InteropServices::ComTypes::IStream^)System::Runtime::InteropServices::Marshal::GetObjectForIUnknown(stgmedium.unionmember);
       System::Runtime::InteropServices::Marshal::Release(stgmedium.unionmember);
       // データサイズの取得
       System::Runtime::InteropServices::ComTypes::STATSTG stat;
       iStream->Stat(stat,STATFLAG_NONAME);
       // データの読み込み
       array<unsigned char>^ pv = gcnew array<unsigned char>((int)stat.cbSize);
       Int64 nReadCount = 0;
       iStream->Read(pv,stat.cbSize,IntPtr::Zero);
       // ファイルへの書き込み
       FileStream^ outfs = gcnew FileStream("c:\\temp\\"+filename, FileMode::Create);
       outfs->Write(pv,0,stat.cbSize);
       outfs->Close();
      }

    • 回答の候補に設定 OTAKA 2010年1月26日 6:09
    2010年1月14日 6:57
  • System::Runtime::InteropServices::ComTypes 内の型を使うなら、C++/CLI で記述する意味なんて全くないと思いますけどね(C# でも同等のコードを書ける)。
    GetData から返ってきた STDMEDIUM の tymed はチェックしなくても大丈夫なのでしょうか? HGLOBAL | ISTREAM で要求していますけど。
    STGMEDIUM::unionmember である IUnknown は勝手に Release しちゃってよかったんでしたっけ? ReleaseStgMedium に任せるものだった気がします。
    逆に Marshal::GetObjecForIUnknown で取得したマネージの COM 参照(RCW)が持っている参照カウントは Marshal::ReleaseComObject で減らしておかなければならないはず。
    なんにせよ、使い終わってから解放するようにするべきでしょう。
    ReleaseStgMedium は、対象とする STGMEDIUM がマネージである以上、DllImport 属性を使って定義する必要があります。

    // あと、HGLOBAL 使ってないからコメントが変です。
    2010年1月14日 10:57
  • ここまでManagedで記述するのなら、いっその事C#で続けて、
    C#をMasterするのも良いのかなと思います。

    >ftc.tymed = System::Runtime::InteropServices::ComTypes::TYMED::TYMED_HGLOBAL | >System::Runtime::InteropServices::ComTypes::TYMED::TYMED_ISTREAM;
    これ間違っていませんか?
    その後、IStream決めうちの処理が続きますが、本当にIStreamが取れているか確認しましょう。

    >ReleaseStgMedium
    たいしたことはしていないので、
    MSDNを読んで自分でReleaseStgMediumを実装するのもありです。

    さて、C++/CLIの利点であるManaged/Unmanagedの混在ですが、
    例えば以下のような記述になります。

    private: System::Void DragDropForm_DragDrop(System::Object^  sender, System::Windows::Forms::DragEventArgs^  e) 
    {
    	::IDataObject *lpDataObject;
    	::STGMEDIUM stgMedium;
    	::FORMATETC formatETC;
    	::CIDA *lpCida;
    	HRESULT hRes = S_OK;
    
    	//Initialize Local Variables...
    	::ZeroMemory(&stgMedium,sizeof(::STGMEDIUM));
    	::ZeroMemory(&formatETC,sizeof(::FORMATETC));
    	formatETC.dwAspect = DVASPECT_CONTENT;
    	formatETC.lindex = -1;
    	formatETC.tymed = TYMED_HGLOBAL;
    	formatETC.cfFormat = ::RegisterClipboardFormat(CFSTR_SHELLIDLIST);
    
    	//Initialize lpDataObject
    	::IUnknown *lpUnknown = (::IUnknown*)Marshal::GetIUnknownForObject(e->Data).ToPointer();
    	hRes = lpUnknown->QueryInterface(IID_IDataObject,(void**)&lpDataObject);
    	lpUnknown->Release();
    
    	//Get data
    	hRes = lpDataObject->GetData(&formatETC,&stgMedium);
    
    	//Process ShellIDList
    	lpCida = (CIDA*)::GlobalLock(stgMedium.hGlobal);
    	//Add your code here ....
    
    	//Clean up
    	::GlobalUnlock(stgMedium.hGlobal);
    	::ReleaseStgMedium(&stgMedium);
    	lpDataObject->Release();
    }
    
    Error処理は行っておりませんので、適宜確認してください。
    DragDropForm_DragDrop Event HandlerとMarshal::GetIUnknownForObjectがManaged Codeで、
    それ以外はUnmanaged Codeです。


    • 編集済み kozz 2010年1月16日 5:14 記載ミス
    • 回答としてマーク TAKAKUN 2010年1月26日 6:08
    2010年1月16日 5:00
  • ご返事ありがとうございます。

    私が作成したコードは、Hongliang さん、kozz さんのおっしゃる通り、
    C++/CLIでなくてもよい、というかマネージコードでした。

    もともとが、アンマネージコードなら実装経験があるから、C++/CLIへ言語を移行したつもりだったのですが、
    マネージコードからアンマネージコードへハンドルを切るタイミングが分からなかった為、ずるずると来てしまいました。
    アンマネージコードでの実装については、kozzサンのコードをもとに、勉強し直したいと思います。

    しかしここまできたら、とりあえず、現在のマネージコードを完成したと思いますので、もう少しお付き合いください。

    まず提示した関数の前提は、CFSTR_FILEDESCRIPTORWを判断したうえで、FILEGROUPDESCRIPTOR.cItemsの数だけループし
    FILEDESCRIPTORを取得して、ファイル名を取得して、コールされているサブ関数です。

    またご指摘された箇所をみなおして、下記のように修正してみましたが、何回か続けて処理をおこなうと、OS自体が不安定となります。
    解放処理も適切におこなっていると思われるのですが・・・


    namespace SysWin32
    {
     [DllImport("ole32.dll")]
     void ReleaseStgMedium(System::Runtime::InteropServices::ComTypes::STGMEDIUM^);
    }

    namespace DropAnalizer {

     public ref class DropAnlize
     {
     public:
      void ShowFileGroupDescriptorW(DragEventArgs^ e)
      {
       Object^ from = e->Data->GetData(CFSTR_FILEDESCRIPTOR);
       marshal_context context;
       FILEGROUPDESCRIPTOR* filegr = context.marshal_as<FILEGROUPDESCRIPTOR*>(from);

       for(unsigned int i=0; i<filegr->cItems; i++)
       {
        FILEDESCRIPTOR filedes = filegr->fgd[i];
        String^ filename = gcnew String(filedes.cFileName);
        //MessageBox::Show(filename);
        SaveDataContents(e,filename,i);
       }
      }

      void SaveDataContents(DragEventArgs^ e,String^ filename,int nIndex)
      {
       // ComTypes::IDataObjectキャスト
       System::Runtime::InteropServices::ComTypes::IDataObject^ data;
       data = (System::Runtime::InteropServices::ComTypes::IDataObject^)(e->Data);
       
       // FORMATETC構造体の初期化
       System::Runtime::InteropServices::ComTypes::FORMATETC ftc;
       ftc.cfFormat = ::RegisterClipboardFormat(CFSTR_FILECONTENTS);
       ftc.lindex = nIndex;
       ftc.dwAspect = System::Runtime::InteropServices::ComTypes::DVASPECT::DVASPECT_CONTENT;
       ftc.tymed = System::Runtime::InteropServices::ComTypes::TYMED::TYMED_HGLOBAL | System::Runtime::InteropServices::ComTypes::TYMED::TYMED_ISTREAM;
       ftc.ptd = System::IntPtr::Zero;

       // IStreamインタ^フェイスの取得
       System::Runtime::InteropServices::ComTypes::STGMEDIUM stgmedium;
       data->GetData(ftc,stgmedium);
       if(stgmedium.tymed == System::Runtime::InteropServices::ComTypes::TYMED::TYMED_ISTREAM)
       {
        // 取得したハンドルからIStreamインターフェイスの取得
        System::Runtime::InteropServices::ComTypes::IStream^ iStream = (System::Runtime::InteropServices::ComTypes::IStream^)System::Runtime::InteropServices::Marshal::GetObjectForIUnknown(stgmedium.unionmember);
        // データサイズの取得
        System::Runtime::InteropServices::ComTypes::STATSTG stat;
        iStream->Stat(stat,STATFLAG_NONAME);
        // データの読み込み
        array<unsigned char>^ pv = gcnew array<unsigned char>((int)stat.cbSize);
        Int64 nReadCount = 0;
        iStream->Read(pv,stat.cbSize,IntPtr::Zero);
        // ファイルへの書き込み
        FileStream^ outfs = gcnew FileStream("c:\\temp\\"+filename, FileMode::Create);
        outfs->Write(pv,0,stat.cbSize);
        outfs->Close();

        System::Runtime::InteropServices::Marshal::ReleaseComObject(iStream);
        SysWin32::ReleaseStgMedium(stgmedium);
       }
      }







    • 回答の候補に設定 OTAKA 2010年1月26日 6:09
    2010年1月18日 4:37
  • 直接的な要因は見つかりませんね。

    要因を特定するために、コードを削って現象が発生するか確認してみてはいかがでしょうか。

    例えば、SaveDataContentsのIStream処理を丸ごとComment Outして、
    テストしてみるなど、確認していってみてください。

    2010年1月21日 9:49
  • ご返事ありがとうございます。

    コードををシンプルにして、一つ一つ対応していきます。
    ここまできたら、なんかなりそうと思います。

    ありがとうございました。

    いま、kozzさんのコードを見ながら、アンマネージコードでの実装を勉強中でして・・・

    マーシャリングがわかれば、C+/CLIも理解が進めそうなんですが、
    あまりいい資料はありませんね。

    2010年1月26日 6:07