トップ回答者
Drag&Dropについて

質問
-
お世話になります。
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;
}
};
}
}
ご教授ください
回答
-
ここまで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です。
すべての返信
-
ご返事ありがとうございました。
キャストし、ファイルコンテンツをローカルに保存することができたのですが、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
-
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 使ってないからコメントが変です。 -
ここまで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です。 -
ご返事ありがとうございます。
私が作成したコードは、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