using System; using System.IO; using System.Collections.Generic; // For List<> using System.Linq; // For Enumration using System.Reflection; // Assembly. using System.Text.RegularExpressions; using System.Threading; using System.Threading.Tasks; using System.Runtime.Serialization; using System.ServiceModel; // for WCF using System.Diagnostics; // for Process // no need //using System.Runtime.InteropServices; // 作成クラス using CSRender; using CSRender.UtHash; using static CSRender.RenderPDF; // *重要* デスクトップアプリで UWP Api を呼び出す プロジェクトの設定方法が記載されている // https://docs.microsoft.com/ja-jp/windows/apps/desktop/modernize/desktop-to-uwp-enhance // 参照はwindows. winmd :C:\Program Files (x86) \Windows Kits\10\UnionMetadata\/ファサード namespace CSRenderMain { [DataContract] //[Serializable()] public class ParamData { [DataMember] public string pdfPath = ""; //対象ファイル [DataMember] public string pdfPathRef = ""; //比較ファイル [DataMember] public string outputImageDir = ""; // /O [DataMember] public string dpi = "72.0"; // /D [DataMember] public int para = 4; // 並行数(プロセス数) [DataMember] public int paraPage = 4; // 並行数(page処理スレッド) [DataMember] public string boxSelect = "Crop"; // /B [DataMember] public string pageRange = "1-*"; // /P [DataMember] public string mode = "page"; // /MODE [DataMember] public string imageType = "JPG"; // /JPG or /PNG [DataMember] public string jpegQ = "91"; // JPEGQ [DataMember] public bool bHash = false; // ハッシュ値を生成する [DataMember] public bool bMkHash = false; // ハッシュファイルを作成する [DataMember] public bool bFC = false; [DataMember] public string resultPath = ""; [DataMember] public bool bPDFium = true; // [DataMember] public bool bExeSepa = true;// 実行分離 [DataMember] public string subExe = null;// "Sub"が指定されたら、処理をExe分離する。内部コマンド // データはWCFの通信データを使う。引数はPipeName:PortAddressNameとする [DataMember] public bool bVerbose = false;// 詳細デバッグ [DataMember] public bool bVerify = false;// 詳細デバッグ public ParamData Clone() { return (ParamData)MemberwiseClone(); } } public class Program { static void DispHelpNoArg() { var asmName = Assembly.GetExecutingAssembly().GetName(); var pgName = asmName.Name;// "アセンブリ名" var v1 = asmName.Version; // "1.0.0.0" // 逐語的文字列リテラル $@, ダブルクウォートは2つでエスケープされる // HHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH Console.Write( $@" Version={ v1} {pgName} [/] :Rendering(to JPEG,TIFF,...) {pgName} /MkHash [/] :Make Imaged hash keys {pgName} /FC [/] :Compare files Render of PDF file.available 3 command mode:[Basic Rendering] [Make Hash command] [Compare command], and[Render Options] PDFの画像化は[Basic Rendering]。 比較は/MkHash([Make Hash command]) 後に/FC([Compare command]) で高速実行できます。 For more information,see /H /? or /? "); // HHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH } static void DispHelpDetail() { var asmName = Assembly.GetExecutingAssembly().GetName(); var pgName = asmName.Name;// "アセンブリ名" var v1 = asmName.Version; // "1.0.0.0" DispHelpNoArg(); // 逐語的文字列リテラル $@, ダブルクウォートは2つでエスケープされる // HHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH Console.Write($@" [Basic Rendering] 基本的なレンダリング {pgName} [/] /F : pdfPath(pdfファイル名|ディレクトリ) /Fは省略可能 /O|Output : 出力ディレクトリ。 省略時はPDF指定の場合は同一階層の"".IMG""フォルダ。 ディレクトリ指定の場合は""<ディレクトリ名>.IMG""、差分画像は""<ディレクトリ名>.DiffImage""。 [Render Options] レンダリングオプション /D|Dpi <解像度> : 解像度指定 9 - 300dpi(default=72dpi) /JPG,/JPEG,/PNG,/TIF,/TIFF,/GIF,/BMP: Select one output format.(default=/JPG) /JPEGQ : Jpegの品質指定1-100(default=91) /P|Page : ページの範囲を指定する(省略時は全ページ) 連続した範囲を指定する場合は、ハイフン('-')を用いる。終了側を省略すると最終pageまで。 複数のページを指定する場合は、カンマ(',')を用いる Ex. /P ""1,2,30-100"" //1,2pages and 30-100pages. [Unsupport] 未対応↓ /L : 入力PDFファイルリスト(*unsupport) /T : テンポラリフォルダを指定(省略時は出力先フォルダと同じ(*unsupport no need) /OP <0|1> : オーバープリントのOn/Off (省略時は1)(*unsupport allways on[1]) /U <0|1> : 同名上書き設定 0:上書きしない 1:上書き(*unsupport allways overwrite[1]) /OFFSET : ミリ単位でオフセットを指定する(省略時は共に0mm)(*unsupport) [Obs]/PDFium <0or1>: GoogoleのPDFiumViewerエンジンを使用する(default=1> [未]/BM,/BT,/BA,/BA,/BC: Select one box.(default=/BC:CrobBox): Boxies:MediaBox/BleedBox/TrimBox/ArtBox/CropBox [Make Hash command] /MkHash 比較用ハッシュ値作成コマンド {pgName} /MkHash ...... /MKHash : ハッシュ値を出力する。前記の[Render Options]を指定すること [Compare command] /Fc比較コマンド {pgName} /Fc ...... [/Tgt|Target] [/Ref|Refernce] /FC : 2つのPDFを比較する。前記の[Render Options]を指定すること。無名引数が2つ必要です 事前に/MkHashを実行しておくことで高速に処理できる /Tgt|Target <> : ターゲットファイル指定 /Ref|Reference <> : リファレンスファイル指定(比較先) /Verify : PureVerify mode. Diff画像をTGTとREFのサブ階層に出力する /Result : 比較結果を格納するファイルパス /FCコマンドを指定すると一致したら0,不一致なら1を返却するようになる は、,<[OK] or [@Difference]>の行で構成される [ELSE] その他のオプション /Para : <プロセス並行数>:本Exeの並行数を指定(デフォルト4) /ParaPage : <ページ処理スレッド数>:ページ処理のスレッド数を指定(デフォルト4) /Verbose [True|1] : 詳細表示 [obs]/NoExeSepa :実行分離しない(遅い) /H or /? : This help"); // HHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH } /// /// PDF render main /// /// /// 0 正常終了 static int Main(string[] args) { var argsStr = String.Join(@" ", args); var pm = new ParamData(); // 引数の保持 bool bDirMode = false; // ディレクト指定の場合 string[] pdfPathLst = null; string[] pdfPathLst2 = null; var pdfPathLstBoth = new List(); var pdfPathLstNoBoth = new List(); //ハッシュ関係 string hashBaseName = "RenderHash"; // No CSRender.Check.GetDPI(); // 未使用 //{// ベースハッシュ値の計算テスト // var streamPDF = ResData.GetBaseHashPDF(); // RenderPDF.RenderPdfInitCheck(streamPDF); //} var logicalProcessNum = Environment.ProcessorCount; if ( logicalProcessNum >= 10 ) {// 元々 4(exe)+4(page)なので+2 = プロセス数が10以上の時にexe数を増やす pm.para = logicalProcessNum - pm.paraPage; } /* * ↓引数解析 * */ var qu = new Queue(args); // 引数をQueに登録 (qu.Enqueue(a)でも可能) while (qu.Count > 0) { // 引数のパース var wd = qu.Dequeue(); // 取り出しqu.Dequeue()で次の要素を取得する if (wd.First() == '-') { // 先頭-(ハイフンもオプション扱いにする)→"/"に置換 wd = Regex.Replace(wd, @"^\-", "/"); } //大文字小文字無視でオプションチェック var eIgnoreCase = StringComparer.OrdinalIgnoreCase; // ローカル関数:オプションチェック bool isOpt(params string[] opts) => opts.Contains(wd, eIgnoreCase); // ローカル関数:オプションの値を取得 string getValue(string defaultValue = "" ) { if ( qu.Count == 0 ) return defaultValue; if ( Regex.Match( qu.Peek(), $@"^[\/\-]" ).Success ) return defaultValue; // 次のオプションキー -> 初期値を返す return qu.Dequeue(); //値をキューから取り出す } // ボックスオプション辞書 var BoxSelOptDic = new Dictionary(eIgnoreCase) { ["/BM"] = "Media", ["/BT"] = "Trim", ["/BB"] = "Bleed", ["/BC"] = "Crop", ["/BA"] = "Art" }; if (isOpt("/?", "/H","/Help")) { DispHelpDetail(); return -1; } else if (isOpt("/F","/Tgt","/Target")) { pm.pdfPath = getValue(); // next value. } else if (isOpt("/Ref","/Reference")) { pm.pdfPathRef = getValue(); // next value. } else if (isOpt("/O","/Output")) { pm.outputImageDir = getValue(); // next value. } else if (isOpt("/D","/DPI")) { pm.dpi = getValue(); // next value. if (!double.TryParse(pm.dpi, out double dmy)) { Console.WriteLine($"解像度が不正です:/D {pm.dpi}"); DispHelpNoArg(); return -1; } } else if (isOpt("/Para")) { string paraNum = getValue(); // next value. if (!int.TryParse(paraNum, out pm.para)) { Console.WriteLine($"並行数が不正です:/para {paraNum}"); DispHelpNoArg(); return -1; } } else if (isOpt("/ParaPage")) { string paraNum = getValue(); // next value. if (!int.TryParse(paraNum, out pm.paraPage)){ Console.WriteLine($"並行数(ページスレッド数が不正です:/ParaPage {paraNum}"); DispHelpNoArg(); return -1; } } else if (isOpt("/P","/Page")) { pm.pageRange = getValue(); // next value. } else if (isOpt("/JPG", "/JPEG")) { pm.imageType = "JPG"; } else if (isOpt("/PNG")) { pm.imageType = "PNG"; } else if (isOpt("/TIF", "/TIFF")) { pm.imageType = "TIFF"; } else if (isOpt("/GIF", "/GIFF")) { pm.imageType = "GIF"; } else if (isOpt("/BMP")) { pm.imageType = "BMP"; } else if (isOpt("/JPEGQ")) { pm.jpegQ = getValue(); // next value. if (!int.TryParse(pm.jpegQ, out int dmy)) { Console.WriteLine($"JPEG Qualityが不正です:/JPEGQ {pm.jpegQ}"); DispHelpNoArg(); return -1; } if (!(0 < dmy && dmy <= 100)) { Console.WriteLine($"JPEG Qualityが不正です(not 1-100):/JPEGQ {dmy}"); return -1; } } else if (isOpt("/M")) { pm.mode = getValue(); // next value. } else if (BoxSelOptDic.ContainsKey(wd)) { pm.boxSelect = BoxSelOptDic[wd];// "/BT" -> "Trim",... } else if (isOpt("/HASH")) { pm.bHash = true; } else if (isOpt("/MKHASH")) { pm.bMkHash = true; pm.bHash = true; } else if (isOpt("/FC")) { pm.bFC = true; } else if (isOpt("/PDFium")) { pm.bPDFium = true; var flgStr = getValue(); // next value. if (!int.TryParse(flgStr, out int dmy)) { Console.WriteLine($"PDFiumフラグが不正です:/PDFium {flgStr}"); return -1; } if (dmy == 0) { pm.bPDFium = false; } else if (dmy == 1) { pm.bPDFium = true; } else { Console.WriteLine($"PDFiumフラグが不正です:/PDFium {flgStr}"); return -1; } } else if (isOpt("/Verbose")) { var arg = getValue("True"); // next value. string[] sel = {"True","1","ON"}; pm.bVerbose = sel.Contains(arg, eIgnoreCase); } else if (isOpt("/SubExe")) { pm.subExe = getValue(null); // next value. var sp = pm.subExe.Split(':'); if (sp.Length < 2 ) { Console.WriteLine($"SubExe指定は\":\"区切りが必要: {pm.subExe}"); return -1; } } else if (isOpt("/NoExeSepa")) { pm.bExeSepa = false; } else if (isOpt("/RESULT")) { pm.resultPath = getValue(""); // next value. } else if (isOpt("/Verify")) { pm.bVerify = true; } else if (wd.First() == '/') { // 処理の無いオプションを明示的に無視する Console.WriteLine($"Warning::Ignore opt:{wd}"); } else { // 引数をファイル名として取得する if (pm.pdfPath == "") { pm.pdfPath = wd; continue; } if (pm.bFC && (pm.pdfPathRef == "")) { pm.pdfPathRef = wd;// 比較時のみ取得 continue; } } } // ↑引数解析終わり setEcho(pm.bVerbose); if (pm.bVerbose) echo("Varbose Mode!"); // bool bSubExe = (pm.subExe != null);// SubExeで起動されている場合。 xChangeWCFPipe.IXData cltSrv = null; if (!bSubExe) { /// サーバー側 echo("++MainProcess start"); Console.WriteLine($"LogicalProcNum={logicalProcessNum},ProcessParallelTasks={pm.para},PageParallelTasks={pm.paraPage}"); } else { // SubExe側の動作に差し替える var sp = pm.subExe.Split(':'); var pipeName = sp[0]; var pipeAddr = sp[1]; cltSrv = xChangeWCFPipeGEN.CLT_T.makeCLT(pipeName:pipeName,pipeAddress:pipeAddr); Ut.Ut.SetAutoSelfKillByMainProcEnd(); // 起動元が終了した場合に自身を終了させる pm = cltSrv.GetParam(); // パラメータを書き換える setEcho(pm.bVerbose); echo("++SubProcess start:" + pm.subExe); } if (pm.pdfPath == "") { Console.WriteLine("pdfファイルが指定されてません"); DispHelpNoArg(); return -1; } if (!(File.Exists(pm.pdfPath) || Directory.Exists(pm.pdfPath))) { // ファイルもしくはディレクトも見つからない場合 Console.WriteLine($"ファイルが存在しません:{pm.pdfPath}"); return -1; } var rdCond = new RenderPDF.RenderConditionParams { Dpi = double.Parse(pm.dpi), ImageType = pm.imageType, BoxType = pm.boxSelect, JpegQ = int.Parse(pm.jpegQ) }; bool bDir = File.GetAttributes(pm.pdfPath).HasFlag(FileAttributes.Directory); if (bDir) { // Directoryが指定されたのでファイルリストアップ bDirMode = true; pdfPathLst = System.IO.Directory.GetFiles(pm.pdfPath, "*.pdf"/*, System.IO.SearchOption.AllDirectories*/); pdfPathLst = Array.ConvertAll(pdfPathLst, f => Path.GetFileName(f)); // 配列書き換え(ファイル名のみにする) // var enumLst = pdfPathLst.Select(f => Path.GetFileName(f)); LINQ式に置き換えることも可能(返り値は配列ではない) //foreach ( var f in pdfPathLst) { // Console.WriteLine($@"path1={f}"); //} //Console.WriteLine($@"path1.Len={pdfPathLst.Count()}"); } var resultData = new SortedDictionary(); if ((!pm.bFC) && bDir) { // 単純レンダリング時 かつ ディレクト時に 対象リストに追加 foreach (var f in pdfPathLst) { pdfPathLstBoth.Add(f); } } if (pm.bFC) {// 比較モード時 if (pm.pdfPathRef == "") { Console.WriteLine("比較モードで2つ目のpdfファイルが指定されてません"); return -1; } if (!(File.Exists(pm.pdfPathRef) || Directory.Exists(pm.pdfPathRef))) { Console.WriteLine($"比較ファイルが存在しません:{pm.pdfPathRef}"); return -1; } // 同一フォルダorファイルの禁止 if (Path.GetFullPath(pm.pdfPath).Equals(Path.GetFullPath(pm.pdfPathRef),StringComparison.OrdinalIgnoreCase) ){ Console.WriteLine("ターゲットとリファレンスが同一です"); return -1; } bool bDir2 = File.GetAttributes(pm.pdfPathRef).HasFlag(FileAttributes.Directory); if (bDir2) { if (!bDir) { Console.WriteLine($"比較対象はファイルパスでないといけません:{pm.pdfPathRef}"); return -1; } // 2つ目のファイルリストアップ pdfPathLst2 = System.IO.Directory.GetFiles(pm.pdfPathRef, "*.pdf"/*, System.IO.SearchOption.AllDirectories*/); pdfPathLst2 = Array.ConvertAll(pdfPathLst2, f => Path.GetFileName(f));// 配列の書き換え // 共通のファイルを見つける。Lstの要素がLst2に含まれているかどうか foreach (var f in pdfPathLst) { if (pdfPathLst2.Contains(f)) { pdfPathLstBoth.Add(f); } else { pdfPathLstNoBoth.Add("tgt:"+f);// LstがLst2に含まれていない } } // Lst2のみファイルをNoBothに登録 foreach (var f in pdfPathLst2) { if (!pdfPathLstBoth.Contains(f)) { pdfPathLstNoBoth.Add("ref:"+f); } } // pdfPathBoth,pdfPathNoBothが作成済み if (pdfPathLstNoBoth.Count() != 0) { Console.WriteLine($@"不一致のファイル={pdfPathLstNoBoth.Count()}"); foreach (var f in pdfPathLstNoBoth) { Console.WriteLine($@"warning [no match]={f}"); } } } } var watch = System.Diagnostics.Stopwatch.StartNew(); // 時間の生成と計測開始を同時に行う var otDir = pm.outputImageDir; if (otDir == "") { if ( bDir ) { var suffix = pm.bFC ? ".DiffImage": ".IMG"; otDir = Path.Combine( Directory.GetParent(pm.pdfPath).FullName, Path.GetFileName( pm.pdfPath )+suffix); echo($"****Output dir={otDir} ******\n"); if (!Directory.Exists(otDir) && (!pm.bMkHash) ) { // Directory.CreateDirectory(otDir); } } else { // 出力ディレクトリの作成 // 元PDFの同一フォルダにIMGフォルダを作成する otDir = Path.Combine(Directory.GetParent(pm.pdfPath).FullName, "IMG"); } pm.outputImageDir = otDir;// pmに戻す } var otHashPath = ""; var otHashPath2 = ""; int ret = -1; //Console.WriteLine($@"Use bPDFium={pm.bPDFium}"); if (!bDirMode) { if (pm.bFC) { //比較モード // ハッシュデータの読み込み echo($@"pre pdfPath={pm.pdfPath}"); echo($@"pre pdfPathRef={pm.pdfPathRef}"); var hashDataTgt = HashData.load(pm.pdfPath, hashBaseName, pm.dpi, pm.boxSelect, pm.imageType, pm.jpegQ, ref otHashPath); var hashDataRef = HashData.load(pm.pdfPathRef, hashBaseName, pm.dpi, pm.boxSelect, pm.imageType, pm.jpegQ, ref otHashPath2); //Console.WriteLine(""); if (pm.resultPath != "") {// 初期化 File.WriteAllText(Path.GetFullPath(pm.resultPath), ""); } ret = RenderPDF.RenderPdfDocCompare( pdfPath: Path.GetFullPath(pm.pdfPath), refPdfPath: Path.GetFullPath(pm.pdfPathRef), resultDataPath: pm.resultPath, inDir : otDir, pm : rdCond, inPageRange : pm.pageRange, inHashDataTgt: hashDataTgt, inHashDataRef: hashDataRef, bPDFium : pm.bPDFium, nPageThreadNum: pm.paraPage, bVerify : pm.bVerify ); } else { // Sync version. // Console.WriteLine(""); // シングル指定では既存のハッシュファイルを読みださない → 単独名のハッシュファイル");[ var count = pdfPathLstBoth.Count; if (pm.bExeSepa &&(!pm.bFC)) { // FCモードは除外します。 var tokenSource = new CancellationTokenSource(); ParallelOptions options = new ParallelOptions { MaxDegreeOfParallelism = pm.para }; ret = 0;//Success var loopResult = Parallel.For(0,pdfPathLstBoth.Count, options, (index,lpState) => { //for (var index = 0; index < pdfPathLstBoth.Count; index++) { var tgt = Path.Combine(pm.pdfPath, pdfPathLstBoth[index]); var tgtID = $@"{index+1}/{pdfPathLstBoth.Count}"; Console.WriteLine($@"**Remain({count})****tgt({tgtID})={tgt}"); if (pm.bFC) { //比較モード var reff = Path.Combine(pm.pdfPathRef, pdfPathLstBoth[index]); } else { // Sync version(Paralles). var svrData = new xChangeWCFPipe.XData(); var pmClone = pm.Clone(); pmClone.pdfPath = tgt;// 引数のターゲットのみを書き換える svrData.SetParam(pmClone); string guidStr = Guid.NewGuid().ToString("N"); string pipeName = $"PN_{guidStr}"; string pipeAddress = $"P_{index}_{guidStr}"; var svrHost = xChangeWCFPipeGEN.SVR_T.makeSVR( baseIf : typeof(xChangeWCFPipe.IXData), obj : svrData, pipeName : pipeName, pipeAddress : pipeAddress ); if ( svrHost == null) { Console.WriteLine("WCF cannot make"); //tokenSource.Cancel();// 処理のキャンセル( throwされる) lpState.Stop(); return; } var myExePath = Assembly.GetExecutingAssembly().Location; var proc = Ut.Ut.DoCmd( cmdAndArgs: new string[] { myExePath, "/SubExe",$"{pipeName}:{pipeAddress}" } , bEcho: false , bSameConsole: true ); proc.WaitForExit(); var retProc = proc.ExitCode; echo($@"retProc={retProc}"); if ( retProc < 1 ) { //PDFページ数以外が返った時 ret = -1; Console.WriteLine( $@"Warning ::Cannot get PDFPage:{retProc},tgt={tgt},{argsStr}"); } else { ;// 初期値 ret=0; } //while (!proc.HasExited) { // System.Threading.Thread.Sleep(2 * 1000); //} var tmpH = svrData.GetHashData(); if (tmpH == null) { echo("tmpH is null"); } if ( (hashDataTgt!=null)&&(tmpH!=null)) { lock(hashDataTgt) { //hashDataTgt.Files += tmpH.Files; if ( tmpH.Files.Count() < 1 ) {// 中身が空なら警告 Console.WriteLine($@"Warning tempF.Count={tmpH.Files.Count()}"); } var merged = hashDataTgt.Files .Concat(tmpH.Files.Where(pair => !hashDataTgt.Files.ContainsKey(pair.Key)) ).ToDictionary( pair => pair.Key, pair => pair.Value ); hashDataTgt.Files = new SortedDictionary(merged);// hashを合成した。 } } proc.Dispose(); svrHost.Close(); svrData = null; echo($@"End tgt({tgtID})"); } //GC.Collect();// これでメモリリークが解決した Paraの中ではやめておく count--; }); if ( !loopResult.IsCompleted ) { Console.WriteLine("Abort!"); pm.bMkHash = false;// Hash値保存抑制 ret=-1; } ////tokenSource.Cancel();// 処理のキャンセル //if ( tokenSource.IsCancellationRequested ) { // pm.bMkHash = false;// Hash値保存抑制 //} //}; // Non Para Block echo($"********** コマンド終了*****{argsStr} "); } else if (pm.bExeSepa &&(pm.bFC)) { // セパのFCモード /* refのハッシュが存在しなければWCFで作成 tgtのハッシュは強制的にWCFで作成->refと同様に存在しなければ作成 */ echo($"****************セパのFCモード******{argsStr}************"); // リファレンス側のハッシュチェック hashDataRef = HashData.load(pm.pdfPathRef, hashBaseName, pm.dpi, pm.boxSelect, pm.imageType, pm.jpegQ, ref otHashPath2); // ハッシュファイルとファイルとの日付を比較する Console.WriteLine(otHashPath2); if ( (hashDataRef != null) && !hashDataRef.IsValidHashFile(pdfPathLst2)) { // ハッシュファイルが異常→ファイルを削除 File.Delete(otHashPath2); hashDataRef=null; } if ( hashDataRef == null ) { var myExePath = Assembly.GetExecutingAssembly().Location; var proc = Ut.Ut.DoCmd( cmdAndArgs: new string[] { myExePath, "/MkHash",pm.pdfPathRef,"/D",pm.dpi,$"/{pm.imageType }",$"/JPEGQ",pm.jpegQ } , bEcho: true , bSameConsole: true ); proc.WaitForExit(); hashDataRef = HashData.load(pm.pdfPathRef, hashBaseName, pm.dpi, pm.boxSelect, pm.imageType, pm.jpegQ, ref otHashPath2); if ( hashDataRef != null ) { echo($" OK hash ref"); } } else { echo($" OK hash ref(already exist)"); } // ターゲット側のハッシュチェック hashDataTgt = HashData.load(pm.pdfPath, hashBaseName, pm.dpi, pm.boxSelect, pm.imageType, pm.jpegQ, ref otHashPath); if ( (hashDataTgt != null) && !hashDataTgt.IsValidHashFile(pdfPathLst)) { // ハッシュファイルが異常→ファイルを削除 File.Delete(otHashPath); hashDataTgt=null; } if ( hashDataTgt == null ) { // ターゲット側のハッシュ計算 var myExePath = Assembly.GetExecutingAssembly().Location; var proc = Ut.Ut.DoCmd( cmdAndArgs: new string[] { myExePath, "/MkHash",pm.pdfPath,"/D",pm.dpi, $"/{pm.imageType }",$"/JPEGQ",pm.jpegQ } , bEcho: true , bSameConsole: true ); proc.WaitForExit(); hashDataTgt = HashData.load(pm.pdfPath, hashBaseName, pm.dpi, pm.boxSelect, pm.imageType, pm.jpegQ, ref otHashPath); if ( hashDataTgt != null ) { echo($" OK hash tgt"); } } //if (false/*true*/){// 2022.5.7 なぜ常に作成していたか? K.Matsuo // // ターゲット側のハッシュ計算 // var myExePath = Assembly.GetExecutingAssembly().Location; // var proc = Ut.Ut.DoCmd( // cmdAndArgs: new string[] { myExePath, "/MkHash",pm.pdfPath,"/D",pm.dpi, $"/{pm.imageType }",$"/JPEGQ",pm.jpegQ } // , bEcho: true // , bSameConsole: true // ); // proc.WaitForExit(); // hashDataTgt = HashData.load(pm.pdfPath, hashBaseName, pm.dpi, pm.boxSelect, pm.imageType, pm.jpegQ, ref otHashPath); // if ( hashDataTgt != null ) { // echo($" OK hash tgt"); // } //} echo($@"****************FC開始*****{argsStr}**************"); if (pm.resultPath != "") {// 初期化 File.WriteAllText(Path.GetFullPath(pm.resultPath), ""); } int noMatchPageNum=0; int noMatchFileNum=0; for (var index = 0; index < pdfPathLstBoth.Count; index++) { var tgt = Path.Combine(pm.pdfPath, pdfPathLstBoth[index]); Console.WriteLine($@"tgt={index+1}/{pdfPathLstBoth.Count}:{tgt}"); var reff = Path.Combine(pm.pdfPathRef, pdfPathLstBoth[index]); ret = RenderPDF.RenderPdfDocCompare( pdfPath: Path.GetFullPath(tgt), refPdfPath: Path.GetFullPath(reff), resultDataPath: pm.resultPath, inDir: otDir, pm: rdCond, inPageRange: pm.pageRange, inHashDataTgt: hashDataTgt, inHashDataRef: hashDataRef, bPDFium: pm.bPDFium, nPageThreadNum:pm.paraPage, bVerify : pm.bVerify ); if ( ret > 0 ) { // retは不一致ページ数 noMatchFileNum++; noMatchPageNum+= ret; } }; watch.Stop(); var SummaryResult = $"\n[結果]\n"; SummaryResult += $"Args ={argsStr}\n"; SummaryResult += $"Target ={pm.pdfPath}\n"; SummaryResult += $"Reference={pm.pdfPathRef}\n"; SummaryResult += $"不整合ファイル群={pdfPathLstNoBoth.Count()}\n"; foreach (var f in pdfPathLstNoBoth) { SummaryResult += $"\tWarning [no match]={f}\n"; } SummaryResult += $"NGファイル数={noMatchFileNum}\n"; SummaryResult += $"NGページ数 ={noMatchPageNum}\n"; SummaryResult += $"Result={ret},time={ watch.ElapsedMilliseconds / 1000.0 }[sec]"; Console.WriteLine(SummaryResult);// 結果標準出力 if (pm.resultPath != "") {// ファイル出力の先頭に追記 var rData = File.ReadAllText(pm.resultPath); SummaryResult += "\n[詳細] *find @Difference\n"; File.WriteAllText(Path.GetFullPath(pm.resultPath), (SummaryResult + rData)); } echo($"****************[終了]セパのFCモード***{argsStr}***************"); ret = noMatchPageNum;// 返値 0:OK,!0=不一致Page数 } else { // NoExeSepa -> Obsolate echo($@"****************NoExeSepa*****{argsStr}**************"); if (pm.resultPath != "") {// 初期化 File.WriteAllText(Path.GetFullPath(pm.resultPath), ""); } for (var index = 0; index < pdfPathLstBoth.Count; index++) { var tgt = Path.Combine(pm.pdfPath, pdfPathLstBoth[index]); Console.WriteLine($@"tgt={index+1}/{pdfPathLstBoth.Count}:{tgt}"); if (pm.bFC) { //比較モード var reff = Path.Combine(pm.pdfPathRef, pdfPathLstBoth[index]); //Console.WriteLine(""); ret = RenderPDF.RenderPdfDocCompare( pdfPath: Path.GetFullPath(tgt), refPdfPath: Path.GetFullPath(reff), resultDataPath: pm.resultPath, inDir: otDir, pm: rdCond, inPageRange: pm.pageRange, inHashDataTgt: hashDataTgt, inHashDataRef: hashDataRef, bPDFium: pm.bPDFium, nPageThreadNum:pm.paraPage, bVerify : pm.bVerify ); } else { // Sync version(Paralles). ret = RenderPDF.RenderPdfDoc( pdfPath: Path.GetFullPath(tgt), inDir: otDir, pm: rdCond, inPageRange: pm.pageRange, bSaveImage: pm.bMkHash ? false : true,// ハッシュ値生成ではイメージ保存しない。 bHash: pm.bHash,// bHash,dumy inHashData: pm.bMkHash ? hashDataTgt : null, //ハッシュコマンドモードのみDataを渡す bPDFium: pm.bPDFium, nPageThreadNum: pm.paraPage ); } count--; GC.Collect();// これでメモリリークが解決した }; } if (pm.bMkHash) { hashDataTgt.save(otHashPath);//ハッシュモードで保存する(Dutyチェックもいるでしょう } } watch.Stop(); Console.WriteLine($"result={ret},time={ watch.ElapsedMilliseconds / 1000.0 }[sec]"); return ret;//success. // 返値 0:OK,!0=不一致Page数 } // リソースからの取得 public static class ResData { public static Stream GetBaseHashPDF(string resName = "CSRender.RES.BaseHash.pdf") { System.Reflection.Assembly asm = System.Reflection.Assembly.GetExecutingAssembly(); var sel = from x in asm.GetManifestResourceNames() select resName; if (sel.Count() == 1) { return asm.GetManifestResourceStream(sel.First()); } return null; } } } } /// /// WCF通信モジュールのラッパクラス。 /// 2つのExe間をWCFパイプモードで通信を行う /// namespace xChangeWCFPipe { /// シリアライズ可能にすること /// [ServiceContract] public interface IXData { [OperationContract(IsOneWay = true)] void Execute(); [OperationContract] bool SetMessage(string msg); [OperationContract] bool SetProgress(int per);//0-100. [OperationContract] HashData GetHashData(); [OperationContract] void SetHashData(HashData d); [OperationContract] CSRenderMain.ParamData GetParam(); //CSRenderMain.Program.ParamData GetParam(); } // ServiceContract https://tnakamura.hatenablog.com/entry/20080606/1220023868 // https://devlights.hatenablog.com/entry/20111023/p2 /// /// 通信用複雑データ。スカラー型(int,double,,,.)以外は[DataCOntract]属性を /// つけて、通知用Interfaceの引数や、返値で利用する /// [DataContract] public class DataContainer { [DataMember] public CSRenderMain.ParamData pm; [DataMember] public HashData hdata; } } namespace xChangeWCFPipe { /// /// Main(サーバー)側 データ定義 /// [ServiceBehavior(InstanceContextMode = InstanceContextMode.Single, ConcurrencyMode = ConcurrencyMode.Multiple, UseSynchronizationContext = false)] public class XData : IXData { private readonly Object LockObj = new object();// 排他制御用 public delegate void callFunc(); public callFunc callDelegate; public XData() { _dc.pm = new CSRenderMain.ParamData(); } public void Execute() { lock (LockObj) { callDelegate(); // 実装はデリゲートします } } public bool SetMessage(string msg) { lock (LockObj) { Console.WriteLine($"SVR:called setMessage({msg}) from CLT"); _msg = msg; } return true; } public bool SetProgress(int per) {//0-100. lock (LockObj) { Console.WriteLine($"SVR:called setProgress({per}) from CLT"); _progress = per; } return true; } public void SetHashData(HashData h) { lock (LockObj) { _dc.hdata = h; } } public HashData GetHashData() { lock (LockObj) { return _dc.hdata; } } public CSRenderMain.ParamData GetParam() { lock (LockObj) { return _dc.pm; } } // クライアントに公開しなくてもよいI/F public void SetParam(CSRenderMain.ParamData pm) { lock (LockObj) { _dc.pm = pm; } } // private data private string _msg = "init msg"; private int _progress = 0;// 0-100 private DataContainer _dc = new DataContainer(); } } namespace xChangeWCFPipeGEN { /// /// Main(サーバー)側 データ定義 /// //[ServiceBehavior(InstanceContextMode = InstanceContextMode.Single, ConcurrencyMode = ConcurrencyMode.Multiple, UseSynchronizationContext = false)] //public class XData : IXData //{ // ... //} /// /// Main(サーバー)側クラス /// /// public class SVR_T { // - static - /// /// /// /// 公開するインターフェースクラス /// 実装インスタンス(baseIfを含むこと) /// /// /// static public SVR_T makeSVR(Type baseIf, object obj/*XData*/ , string pipeName = "PDFormstudioESorGS", string pipeAddress = "SubModuleAddress") { Type oType = obj.GetType(); bool hasBaseIf = oType.GetInterfaces().Any(t => (t == baseIf) ); if ( !hasBaseIf) { Console.WriteLine($@"Error:'{oType.FullName}' have not the interface '{baseIf.FullName}'");//A have not the interface B. return null; } return (new SVR_T(baseIf, obj, pipeName, pipeAddress)); } // - public I/F - public void Close() { echo($"Close Service:{_service != null},{_serviceHost != null}"); _service = null; _serviceHost.Close(); } // - private - private object _service = null;// 要らないかも private Type _baseIf = null; private ServiceHost _serviceHost = null; private SVR_T() { } private SVR_T(Type baseIf, object oInstance, string pipeName, string pipeAddress) { const string pipeBase = "net.pipe://localhost"; var uri = pipeBase + "/" + pipeName; _baseIf = baseIf; _service = oInstance; // デリゲート登録 //callDelegate = () => { Console.WriteLine("svr:Delegate func called"); } _serviceHost = new ServiceHost(_service, new Uri(uri)); try { _serviceHost.AddServiceEndpoint(_baseIf/*typeof(Ti)*/, new NetNamedPipeBinding(), pipeAddress); _serviceHost.Open(); } catch (AddressAlreadyInUseException) { Console.WriteLine("既にサービスは起動しています。"); } catch (Exception e) { Console.WriteLine(e.ToString()); } } } /// /// Sub(クライアント)側クラス /// public class CLT_T { /// /// Sub側の初期化処理。Main側とpipeName,pipeAddressを同じにすること /// /// /// /// static public Ti makeCLT(string pipeName = "PDFormstudioESorGS", string pipeAddress = "SubModuleAddress") { const string pipeBase = "net.pipe://localhost"; var address = pipeBase + "/" + pipeName + "/" + pipeAddress; var factory = (new ChannelFactory(new NetNamedPipeBinding(), new EndpointAddress(address))); Ti toMain = factory.CreateChannel(); return toMain; } } } namespace Ut { public class Ut { // Util // -------------------------------------------------------------- /// /// 親プロセスが終了したら、自身を終了させる /// /// public static void SetAutoSelfKillByMainProcEnd(bool bEnable = true) { if (_SingletonProc != null) return;//一度しか呼び出せない if (bEnable == false) return; const string fname = "SetAutoSelfKillByMainProcEnd"; // 自動的に親のプロセスがいなくなったら自動的にキルモードを設定する // メインプロセスの終了チェック // 親プロセスIDでProcessハンドルを取得、そのExitedイベントに自身の終了関数を設定(Environment.Exit()) int paProcID = GetParentProcessId(); var paProc = Process.GetProcessById(paProcID); paProc.EnableRaisingEvents = true; //Console.WriteLine($@"{fname}:ParrentProcessName ={paProc.ProcessName}({paProcID})"); paProc.Exited += new EventHandler( (object s, EventArgs a) => { Console.WriteLine($@"{fname}:Exited Event!!!!"); var ss = s as Process; Console.WriteLine($@"Sure ProcName is {ss.ProcessName}({ss.Id}) {ss.StartInfo.Arguments}"); ; System.Threading.Thread.Sleep(10 * 1000); ss.Close(); ss.Dispose(); Console.WriteLine($@"{fname}:Exited Event!!!!(afer10sec)"); //Environment.Exit(-1); }); // _SingletonProc = paProc; } private static System.Diagnostics.Process _SingletonProc = null; /// /// コマンドライン引数複数個をエンコードして、スペースで結合 /// /// string[] コマンドライン引数 /// コマンドライン文字列(Escaped) public static string makeCmdLine(IEnumerable args) { if (args == null) throw new ArgumentNullException("args"); string EscapeCmdLineArg(string v) { if (string.IsNullOrEmpty(v)) return ""; var containsSpace = v.IndexOfAny(new[] { ' ', '\t' }) != -1; v = ReCommandLineEscapePattern.Replace(v, @"$1\$&");//「\…\"」をエスケープ.「"」直前の「\」の数を 2倍+1 if (containsSpace) { v = "\"" + ReLastBackSlashPattern.Replace(v, "$1$1") + "\""; } return v; } return string.Join(" ", args.Select(v => EscapeCmdLineArg(v))); } private static Regex ReCommandLineEscapePattern = new Regex("(\\\\*)\""); private static Regex ReLastBackSlashPattern = new Regex(@"(\\+)$"); /// /// 親のプロセスIDを取得する /// /// 親ProcessID static int GetParentProcessId() { var myProcId = GetCurrentProcessId(); var query = string.Format($@"SELECT ParentProcessId FROM Win32_Process WHERE ProcessId = {myProcId}"); //クエリから結果を取得 using (var search = new System.Management.ManagementObjectSearcher(@"root\CIMV2", query)) using (var results = search.Get().GetEnumerator()) { if (!results.MoveNext()) throw new ApplicationException("Couldn't Get ParrentProcessId."); var queryResult = results.Current; //親プロセスのPIDを取得 uint pa = (uint)(queryResult["ParentProcessId"]); return (int)(pa); } } /// /// 自身のプロセスIDを取得する /// /// ProcessID static int GetCurrentProcessId() { return Process.GetCurrentProcess().Id; } /// /// 外部プログラムを起動する /// /// 引数文字列の配列 /// true:コンソールを呼び出しと共有 false:別Console Windows /// 標準出力(未実装) /// コマンドが終了するまで待つ(false:未実装,常に待つ) /// タイムアウト時間ms(未実装) /// public static Process DoCmd(IEnumerable cmdAndArgs, bool bSameConsole = true, bool bEcho = true, bool bWait = false, int nWaitLimitMSec = -1) { var cmdName = cmdAndArgs.First();//[0]; var argStr = makeCmdLine(cmdAndArgs.Skip(1)); // 先頭を除外してコピー new ArraySegment(cmdAndArgs.ToArray(), 1, cmdAndArgs.Count()-1) //Console.WriteLine($@"DoCmd:{cmdName}:args[{argStr}]"); //using (var p = new Process()) { var p = new Process(); { var info = p.StartInfo; p.StartInfo.FileName = cmdName; p.StartInfo.Arguments = argStr; if (bSameConsole) { //Console.WriteLine("Same windows mode"); p.StartInfo.UseShellExecute = false; // Shell経由で実行しない(必須) true: 別ウインドウ。false:コンソールを共有 p.StartInfo.CreateNoWindow = false; // ウィンドウを作成しない } else { //Console.WriteLine("New windows mode"); p.StartInfo.UseShellExecute = true; p.StartInfo.CreateNoWindow = true; } p.Start();// 実行 if (bWait == false) { return p; } if (nWaitLimitMSec == -1) { p.WaitForExit();// タイムアウト無し } else { bool bExit = p.WaitForExit(nWaitLimitMSec); if (!bExit) {// 処理の終了を待つ Console.WriteLine("処理が終了しないので強制終了"); p.Kill(); // 強制終了する return p; } } return p; } } } }