.NET 8.0 でWinFormsアプリ

動画の変換に ffmpeg をよく使ってます。ffmpegは便利なんですが、いかんせんコマンドをポチポチ入力しないといけません。
毎回決まったようなオプションを入力するのが手間で、バッチファイルを作成してできるだけ入力を抑えていたのですが、それもめんどくさくなって、ffmpeg の GUIを探してみましたが・・・あんまり見つかりません。

動画の変換の有名どころは、HandBreakですが・・・最近仕事も落ち着いて暇になってきたので、ffmpegのコマンドラインを組み立てて実行するだけのGUIを作ってみよう・・・と 久しぶりに Visual Studio 2022 community を起動して作り始めました。

Windowsでしか使わねーし、とか思って .NET Framework 4.8 の WinForms で作り始めたのですが・・・.NET Frameworkもいつまでサポートされるか分からないので、途中から 最新の .NET 8.0 の WinForms に変更。C#進化しすぎてて、迷う迷う。
List<T>とか配列の初期化にJavaScriptやperlみたいに、[] でできるのにちょいビビった。

ランタイムフレームワークを変更して気付いたのですが、フォームのデザイン画面のプロパティーで、Application Settings のバインディングする項目が消えちゃってますね・・・.NET Framework 4の時は、テキストコントロールとかチェックボックスのチェック状態を App.configファイルで保存して、Settings.Default経由でコントロールにバインドしてたんですが・・・.NET 8.0 になってGUIのプロパティで設定できなくなったのかなぁ???

しょうがないので、Formのコンストラクタで InitializeMember()メソッドをコールした後・・・

TextBox1.DataBindings.Add("Text", Settings.Default, "TextBox1Text") ;
CheckBox1.DataBindings.Add("Checked",Settings.Default,"CheckBox1Checked");

とかで、できそうだ。なんか別に方法があるかもしれない。よく分からないが、WinFormsもまだまだ使える💦
とりあえず、次のコミットで直そう。Gitは大体慣れてきたが、githubは、複雑過ぎてイマイチ分からん。普通のパスワードが使えなくなって最初、わからーーーん、どうやって認証するねーーーーん!!! ってなって焦った(笑)

関係ないけど、やっと 仕事で使っているVCS環境を Subversion から Git に移行できた。一部 Subversion に依存しているところはおいおい直すことにした。 リモートのリポジトリの置き場を Githubにはできないので Subversionで使っているVPSに同居。分けんといかんなぁ。。。

Windows Explorerシェルで複数のファイルをプログラムに渡したい

ファイルの差分表示に WinMerge (日本語版) を使っていました。TortoiseSVNにも自動で設定してくれるし、重宝してました。

一方、ubuntuとかCentOSのbashシェル内で作業しているときは、vim のdiffモードを使うことも増えてきまして、どうせならWindowsのデスクトップで作業しているときも gVim でやろうかな、と思い立ち、Windowsのレジストリをいじって関連付けしようとしました。

具体的には↓のように・・・

まぁ、でもこれは失敗します。この方法で2つファイルを選択した状態で、右クリックして opendiff をクリックするとgvimが二つ起動してそれぞれ別のgvimで開いてしまいます。失敗。
※追記 2021年6月24日 official buildのvimだと GvimExt32/GvimExt64 というシェルエクステンションが既にバンドルされてるみたいすね・・・。僕は kaoriya vim しか使ってなかったので気づかなかった💦

で、じゃぁ、%2とか%* ってのをつけ足せばいいんじゃない? って思うのですが、やってみれば分かりますが、失敗します。上記と同じように複数のgVimウィンドウが開きます。
どうやら標準で複数選択したファイルを同一のプログラムのインスタンスに渡すことはできないようです。

じゃあ、どうすればいいのか?というと、Explorerのシェルエクステンション(インプロセス COMサーバー)を書いてレジストリに登録する必要があります。(グーグル調べ)

ってことで、現実的に C++ で実装するのはヒジョーにメンドクサイ。探せばシェルエクステンション用のフレームワークみたいなものがあるとは思うのですが、今はC#をメインに使っているので、できるなら C# でできないか、調べたところ(グーグルで)、SharpShell という、C#のシェルエクステンション用のフレームワークがあるようです。

SharpShell
SharpShell makes it easy to create Windows Shell Extensions using the .NET Framework.

ショートカットメニュー用のシェルエクステンション作成とインストールはチュートリアルがあるので、この通りに進めていけば苦も無く作成できると思います。

チュートリアル(英語)

ただ、このチュートリアルはかなり古く2014年の時のものですが、基本的にはあまり変わっていません。
注意点は、SharpShellを nugetで入れる場合は、2021年2月現在最新のもの(2.7.2)を入れる場合、Server Manager などのツールも最新のものを使う必要があります。nugetで SharpShell Tools を入れると なぜか 2.2.0のものがインストールされてしまうので上記 githubサイトで配布されている 2.7.2 のものを使う方がいいと思います。

基本的には・・・Visual Studio 2019 Community を使用します。

  1. プロジェクトを .NET Framework のライブラリ(DLL)として作成。
  2. 「プロジェクト ⇒ nugetのパッケージ管理」で SharpShell をインストール
  3. チュートリアルで説明されているように、SharpContextMenu を継承したクラスを作成
  4. ショートカットメニューに必要な抽象メソッドを実装する(Visual Studio 2019のコード補完で自動的にスケルトンが出ると思います)
  5. プロジェクト・プロパティ⇒アプリケーション⇒アセンブリ情報 で「アセンブリをCOM参照可能にする」にチェック
  6. プロジェクト・プロパティ⇒署名⇒アセンブリに署名する(チェック)⇒新規作成 適当な名前をつける
  7. ビルド
  8. ServerManagerでビルドしたDLLファイルを読み込んで、Server⇒Install Server(x64) Register Server(x64) を順番に実行
  9. インストール&登録する前にテストしたければ、Test Server In Test Shell をクリックするとテスト用のエクスプローラもどきが出てくるのでそこでテストできるようです。

まぁ、そんなこんなで、やりたいことはできました(^▽^)/

※ただ一点注意しないといけないのは、.NET Frameworkのようなマネージコードをインプロセスサーバーとしてエクスプローラのプロセス内で動作させるのは未だにいいのかどうか分からないって点です。マイクロソフトの中の人も良いと言ってる人もいれば、やらない方がいいっていう人もいるようです。 この辺りは SharpShell のドキュメントでも書かれています。2021年現在、実際のところどうなんなんでしょうかね・・・・?

ただ、ショートカットメニューをクリックしてプロセスをキックするだけならあまり問題ないのかな~と思ってます。

ずっと前にC++で書いたシェルエクステンションのソースコードが残っているのでC++で書こうと思えば書けるんですが・・・やっぱりオッサンはもう頭が硬くなってるので時間がかかるんですよねぇ。

書いたコードはこんだけ。便利なライブラリに多謝・多謝。

簡易ISOファイル作成コマンド

■2021年8月2日 追記


これの続きです。

自分用のツールの置き場、備忘録として記録しています😅

CDやDVDのメディアからISOファイルを作成するのは結構カンタンにできました。
これで殆どやりたいことはできたのですが、やっぱりディレクトリからISOファイルを作るにはどうするんだろう??? という素朴な考えがでてきまして・・・

————– 追記
これが 非Windows OSならば、mkisofs コマンド(genisoimage)をdnfなりaptなりのパッケージマネージャで入れれば解決するんだけど・・・いかんせん、Windowsはそういう便利なコマンドはありません。oscdimg という Windowsのインストールメディアを作成するツールを使えばできるそうです。実用性を重視する人は oscdimg を使うといいと思います😀
追記ここまで —–

ググるとこれもWindowsで標準機能でできるようで・・・具体的にはイメージマスタリングAPIとしてCD/DVD等のファイルシステム作成からメディアへの書き込みまでの機能がCOMサーバーとして提供されているようです。COMサーバーで実装ってことは、WSHやC#から簡単に使える・・・ってことです。

サンプルのコードはもうマイクロソフトのコミュニティーサイトに載ってましたので、これを適当にいじって、任意のディレクトリをISOファイルにビルドできるコードを書いてみました。一つ前に書いたDVDメディアからISOファイルを作るコードも統合してみました。

イメージマスタリングAPIは.NET Frameworkのクラスライブラリで提供されておらず、C#から使用するには、TLBIMPコマンドを使用してタイプライブラリからCLRアセンブリに変換し、コンパイルするときにこのアセンブリを参照しないといけません。Visual Studio のIDE環境を使用する場合はプロジェクトにIMAPI2FS.dllの参照を追加するだけでいいかもしれません。
僕みたいにコンソール画面でカチャカチャする場合は・・・

>> tlbimp c:\WINDOWS\System32\imapi2fs.dll

のようにすると、同名のアセンブリ(dllファイル)ができるので、ソースコードをコンパイルするときに、このDLLファイルを /r オプションで追加します。

殆どサンプルコードをコピペしただけなので、特に特筆するところはありません・・・githubにリポジトリ作ろうと思ったけど、ファイル1個だけなんでgistにした。

一応、タイプライブラリのインポートもあるので nmake用の makefileも書く。

ロック画面のスポットライト画像をコピーしてデスクトップにも流用させたい

ロック画面は何にしてますか?僕は設定でWindowsスポットライトにしてます。
これ、たぶんBingから定期的にダウンロードしてロック画面にスライドショー的に表示していると思うんだけど、なぜかログイン後のデスクトップの背景の設定でWindowsスポットライトの選択肢がないんですよねぇ。

Microsoft Storeで壁紙系のアプリを探せばたぶんあると思いますが、たぶんに要らん機能がくっついてきたり個人情報が抜かれたりと面倒なのでやっぱり自分でなんとかするのが基本です。そうです、信じられるのは唯一自分なのです(笑)

Windowsスポットライトで使用される画像ファイルは、
%LOCALAPPDATA%\Packages\Microsoft.Windows.ContentDeliveryManager_cw5n1h2txyewy\LocalState\Assets
に保存されているようです。拡張子がないので判別はできませんが、概ね400KB以上のファイルサイズのファイルが該当する画像で、任意のフォルダにコピーして拡張子.JPGでリネームすれば使いまわせます。ライセンスとか分からんが、どこぞに配布するわけではないので大丈夫でしょう。

定期的にピクチャフォルダにコピーすればいいんですが・・・いちいちフォルダ開いてファイルを選別してコピーしてリネームして・・・うわぁぁぁあーーーーーーん、超絶メンドクセーーーーーーー。
さらに、バックグラウンドでダウンロードされる画像ファイルには縦長と横長がごちゃまぜになっている。縦長の画像は不要なので、これも選別項目です。さらにメンドクサイ。

UNIX系のOSなら find コマンドやらimagemagickを駆使してシェルスクリプト化すればいいんですが・・・。
PowerShellを使えばできそうですけど、んーーーー、C#で組んだ方が早ぇよ。。。ということで、組んでみる。

ファイルサイズを判別するのは簡単で、JPEGファイルの高さと幅を取得するのがメンドクサそうですが・・・単純にSOFマーカーから取得することで手抜きします。完璧にしようとすると僕の知識では無理です。

github

WebClient派生クラスでのクッキー読み書きについて

Tweet image download agent で、致命的なエラーを放置してた件。
もともとファボったツイートの画像だけを自動ダウンロードするために書いたコード。最近はもともとの動機となった機能はほとんど使わず、ツイッター内で検索した画像を自動ダウンロードするために使っていたので、いつの間にかログインできない状態になっていて、それにも気づかず・・・。コメントで報告してもらって初めて気づいたという、お粗末さ(^^;;;

それはともかく、原因は、ログイン後のクッキーの取り扱い。それが雑だったという、二重のお粗末さ・・・。C#使いとしては失格ですえ。

何が原因なのか、VisualStudio2015のIDEでとりあえず、該当箇所をステップ実行してデバッグしてたら、HttpWebResponse.Cookies に セッションクッキーしかストアされていないことに、まず気付いた。要するに、サーバーから返されたレスポンスヘッダ Set-Cookie の Expires が設定されていないものだけが HttpWebResponse.Cookiesにストアされている・・・。

どゆこと?

いくら実行しても、セッションクッキーしか保存されない・・・。これじゃログインが成功してたとしても、だめだわ・・・。
ChromeのDevToolsでTwitterサイトへのログインのレスポンスヘッダーを眺めてたら、あれ? もしかして、Expires に記述されている日付書式のパースに失敗してんのかな???と、グーグル先生に聞いてみると、.NETのSet-Cookieヘッダのパーサーはバカだよ(超意訳)、みたいな投稿が StackOverflowに出てた。

ってなわけで、WebClient.GetWebResponseをオーバーライドして、

WebResponse.Headers[“Set-Cookie”] から自前でクッキーをパースして、CookieContainer.Add しちゃいなよ!

っていうアドバイスに従い、テキトーにパースして Add しちゃう、しちゃう。

でもなー、前はちゃんと動いてたのに・・・。やっぱり、Twitter が吐く Set-Cookieヘッダが変わったぐらいしか、原因が分からないすッ。

探せばもっとマトモなコードがあると思われるので後で探そう・・・とりあえず↓でヨシとする。(要点のみ)
※ すべてのコードは、https://osdn.jp/users/earlgreyx/pf/TwitterImageDownloadAgent/wiki/FrontPage