VC++がC++じゃ無くなってた。。
PCに溜まりに溜まっていた未整理の、バックアップやらコピーやらしまくった
デジカメ画像共を整理すべく、久しぶりにVC++(2008)を弄ってみた。
VCはVC6がどセンターで2003年辺りに弄った以来で、おぼろげな記憶しか残ってないのだけど、
なんとかなると思っていた・・・
そう『C++/CLI』を知るまでは!!
CLI
実はCLI自体がどういう物かは知っていました。
こんな認識でした
- VB,C#,VC++などで共通に使えるプラットフォーム上の機能。
- 実体は.NET上に存在し各言語は.NETへの参照を取り扱う。
- C++から見た場合はメモリー周りを気にしなくて良い楽チンなオブジェクト!
- C++の貧乏臭い文字列処理能力から開放されるぜぃ〜
百聞は一見にしかずというのはこの事だ!
String^ foo = gcnew String("FOO");
oops!!
C++じゃないよ・・・
いやC++と名乗っちゃイカンだろ!これは!!
しかし敢えて言おう!
これで良い!
C++は非常に曖昧で難解な識別子が多く、そこに更に新しい要素(CLI)を加えてしまっては最早ナゾナゾになってしまう。
CLI入門
VC++2008のExpressEdition(無償)を使っています。
ビルドオプション
まずCLIを使うにはこの設定が必要。
[プロジェクト・プロパティ]
⇒[全般]
⇒[共通言語ランタイムサポート]
= "/clr"
または、プロジェクトを新規作成する際にCLIプロジェクトが選択できるのでそちらでも良い。
継承
C++同様に継承できますが、override , new を明示的に指定しなければならない。
ref struct Foo { virtual void foo() {} virtual void bar() {} }; ref struct Bar : Foo{ virtual void foo() override {} virtual void bar() new {} }; Foo ^foo = gcnew Bar(); foo->foo(); // Bar::foo() foo->bar(); // Foo::bar() ((Bar^)foo)->bar(); // Bar::bar()
overrideがC++の動作です。newは使いにくそう・・・
純粋仮想関数は扱いが面倒
ref struct Foo { virtual void baz() =0; }; ref struct Bar : Foo { virtual void baz(){} }; >> 'Foo' : 明示的に抽象として宣言されていませんが、抽象関数を含んでいます >> 'Bar::baz' : 基本 ref クラス メソッド 'Foo::baz' と一致しますが、 >> 'new' または 'override' に設定されていません。'new' (および 'virtual') を仮定します
Fooには abstruct を宣言して、Bar::bazにはoverrideを指定する!
ref struct Foo abstract{ virtual void baz() =0; }; ref struct Bar : Foo{ virtual void baz() override {} };
混ぜるな危険!(混ざりません)
CLI型はフレームワーク側で管理されているので
通常のC++クラスのメンバーとなったり、継承関係となったりが出来ません。
struct Baz {
Foo ^foo;
}
>> マネージ 'foo' をアンマネージ 'Baz' で宣言できません。
ref struct Foo { }; struct Baz : Foo{ }; >> 'Baz': アンマネージ型はマネージ型 'Foo' から派生することはできません。
わざわざ書かないけど、当然逆もダメ!
cliextコンテナ
stdコンテナは『混ぜるな危険!』の為、使い物になりません。
その代わりstdライクなコンテナが用意されてます。
#include <cliext/map> #include <cliext/set> using namespace cliext; using namespace System; typedef map<String^,String^ > mymap_t; typedef set<String^> myset_t;
ただ使い方が微妙に違います。
mymap_t ^mymap = gcnew mymap_t();
mymap->insert(mymap_t::value_type(key,value));
>>'Microsoft::VisualC::StlClr::GenericPair<TValue1,TValue2> ^' :
>> 関数スタイルをビルトイン タイプへ変換すると、受け取ることができる引数は 1 つです。
何を言ってるか解らなかったが、、どうもmake_valueって奴を使うらしい・・・
じゃあ value_type定義すんなよ!混乱するじゃんかー
mymap_t ^mymap = gcnew mymap_t(); mymap->insert(mymap_t::make_value(key,value)); for ( mymap_t::iterator it(mymap->begin()),itend(mymap->end()); it != itend; it++){ it->first; // key it->second; // value }
後はいつもの感じ!
で、、成果発表
仕様
- CUI(コマンドプロンプトで動作させる)
- 複数のディレクトリを指定する。
- 再帰的にファイルを総なめしてMD5を取る。
- MD5が一致した場合は同じファイルと見做す
- ファイルを削除する.batを出力
- 削除後に空になったディレクトリも良い様に処理する.batを出力
いきなり消すと怖いから、batファイル出力にしておいた。
でもこの慎重さでコマンドプロンプトのUTF8問題に嵌る事になる・・・
UTF8 on コマンドプロンプト問題
- コマンドプロンプトのデフォルト文字コードはCP932
- よってUTF8でbatを吐き出すと実行時に文字化けする
- かといってCP932に変換するのは面倒
- "chcp 65001"でコマンドプロンプト自体はUTF8になるがフォントと連動しない
- 処理できないフォントが出力されると『場合によってはcmdが落ちる!』
文字が出ないのは良いが、落ちるのは困る。。
思い悩んだ挙句何も出力しない事にした。
ソース
#include "stdafx.h" #include <cliext/map> #include <cliext/set> using namespace cliext; using namespace System; using namespace System::IO; using namespace System::Security::Cryptography; typedef map<String^,String^ > filemap_t; typedef map<String^,String^ > dubdmap_t; typedef set<String^> dirset_t; ref struct Callback abstract{ virtual void file(String ^file) = 0; virtual void dir(String ^dir) = 0; }; void folder_walk(String ^folder,Callback ^callback){ try { array<String^>^ file = Directory::GetFiles( folder ); Console::WriteLine(" - Checking {0}", folder); int i; for (i=0; i<file->Length; i++){ callback->file(file[i]); } array<String^>^ dir = Directory::GetDirectories( folder ); for (int i=0; i<dir->Length; i++){ folder_walk(gcnew String(dir[i]),callback); callback->dir(dir[i]); } }catch(IOException ^ex){ Console::WriteLine("'{0}'", ex->Message ); } } int pre_check(String ^folder){ try { array<String^>^ dir = Directory::GetDirectories( folder ); }catch(IOException ^ex){ Console::WriteLine("{0}", ex->Message ); return 0; } return 1; } void check(String ^folder,Callback ^callback){ folder_walk(folder,callback); } ref struct OrgCallback : Callback{ filemap_t ^filemap; dubdmap_t ^dubdmap; StreamWriter^ pwriter; OrgCallback(String ^log) :filemap(gcnew filemap_t()), dubdmap(gcnew dubdmap_t()), pwriter(gcnew StreamWriter(log)) { } virtual void file(String ^file) override{ try{ FileStream^ fs = gcnew FileStream( file, FileMode::Open, FileAccess::Read, FileShare::Read); MD5^ md5 = gcnew MD5CryptoServiceProvider(); array<Byte> ^hash = md5->ComputeHash(fs); fs->Close(); String ^md5str = BitConverter::ToString(hash)->ToLower()->Replace("-","")->ToString(); pwriter->WriteLine(md5str + " " + file); filemap_t::iterator it = filemap->find(md5str); if ( it == filemap->end()) { // not found ! filemap->insert(filemap_t::make_value(md5str,file)); }else{ // Doubled dubdmap->insert(dubdmap_t::make_value(file,it->second)); // Console::WriteLine(" *** Same file !! {0} == {1}'", file,it->second); } }catch(IOException ^ex){ (void)ex; // nothing to do } } virtual void dir(String ^dir) override{ } ~OrgCallback(){ pwriter->Close(); } }; ref struct RmdirCallback : Callback{ virtual void file(String ^file) override{ File::Delete(file); } virtual void dir(String ^dir) override{ Directory::Delete(dir,true); } }; void usage(int ret){ Console::WriteLine("Usage: DoubleFileChecker.exe Directory [Directory] .."); Threading::Thread::Sleep(5000); Environment::Exit(ret); } int main(array<String ^> ^args) { if ( args->Length < 1 ) { usage(1); } // PreCheck Console::WriteLine("====== Precheck ======="); for ( int i = 0 ; i < args->Length; i++ ){ String^ folder = gcnew String(args[i]); if ( ! pre_check(folder) ) { usage(1); } } // Temp directory String ^tmpDir = Path::GetTempPath() + "_DFC_\\"; RmdirCallback ^rmdirCallback = gcnew RmdirCallback(); folder_walk(tmpDir,rmdirCallback); Directory::CreateDirectory(tmpDir); String^ org = (gcnew String(args[0]))->Replace("\\","_")->Replace(":",""); String^ hashLog = tmpDir + org + ".log"; String^ dubdBat = tmpDir + org + ".bat"; String^ dirsBat = tmpDir + org + "_dir.bat"; // Check Console::WriteLine(" OK ! => (log) : " + hashLog); OrgCallback ^callback = gcnew OrgCallback(hashLog); for ( int i = 0 ; i < args->Length; i++ ){ String^ folder = gcnew String(args[i]); Console::WriteLine("====== Run check ======="); Console::WriteLine("TOP : " + folder); check(folder,callback); } // Gen batches Console::WriteLine("====== Generating results ======="); Console::WriteLine("Delete files : " + dubdBat); Console::WriteLine("Cleanup dirs : " + dirsBat); StreamWriter ^bwriter = gcnew StreamWriter(dubdBat); bwriter->WriteLine("echo off"); bwriter->WriteLine("md " + tmpDir); bwriter->WriteLine("rem use UTF-8"); bwriter->WriteLine("chcp 65001"); dirset_t ^dirs = gcnew dirset_t(); for ( dubdmap_t::iterator it(callback->dubdmap->begin()),itend(callback->dubdmap->end()); it != itend; it++){ bwriter->WriteLine("rem " + it->first + " same as " + it->second ); String ^srcdir = Path::GetDirectoryName(it->first); String ^dstdir = tmpDir + srcdir->Replace(":","") + "\\"; if ( dirs->find(srcdir) == dirs->end() ) { dirs->insert(srcdir); bwriter->WriteLine("md \"" + dstdir + "\" > NUL 2>&1"); } bwriter->WriteLine("move \"" + it->first + "\" \"" + dstdir + Path::GetFileName(it->first) + "\" > NUL 2>&1"); } bwriter->Close(); StreamWriter ^dwriter = gcnew StreamWriter(dirsBat); dwriter->WriteLine("echo off"); dwriter->WriteLine("md " + tmpDir); dwriter->WriteLine("rem use UTF-8"); dwriter->WriteLine("chcp 65001"); for ( dirset_t::iterator it(dirs->begin()),itend(dirs->end()); it != itend; it++){ dwriter->WriteLine("del /F /AH \"" + *it + "\\Thumbs.db\" > NUL 2>&1"); dwriter->WriteLine("rd \"" + *it + "\" > NUL 2>&1"); } dwriter->Close(); Threading::Thread::Sleep(5000); return 0; }
PS:
- ソースを微修正
- 頑張ったけど、やっぱ.batは貧弱すぎて対応できないファイル名もある模様・・・{}辺りが怪しいかな?