2026年2月12日 (木)

ZIPフォルダーからのドラッグ・アンド・ドロップをWPFアプリで受け取る

WPFで他のアプリからファイルのドラッグ・アンド・ドロップを受け取るときにはDropイベントハンドラーでDataFormats.FileDropを使う。でも、ときどき、これでは受け取れないドラッグ・アンド・ドロップがある。その代表が、Windows標準のZIPフォルダーというもの。ZIPを扱うアーカイバーの類をインストールしていない状態で、エクスプローラーでzipファイルを (「展開」せずに) ダブルクリックすると開くウインドウがそれだ。そのウインドウでZIPの中のファイルをいくつか選択してWPFアプリにドラッグ・アンド・ドロップすると、DROPイベントは発生するものの、GetDataPresent(DataFormats.FileDrop)が真にならない。無理やりGetData(DataFormats.FileDrop)しても結果はnull。これではファイルを受け取ることはできない。


実は、Windowsでファイルをドラッグ・アンド・ドロップする「方法」はいくつかあって、そのことはWindows SDKのドキュメント (https://learn.microsoft.com/windows/win32/shell/clipboard#formats-for-transferring-file-system-objects) に記載がある。C#/WPFのDataFormats.FileDropはWindows SDKではCF_HDROPと呼ばれているが、その他に重要なものにCFSTR_FILEDESCRIPTORがある。ZIPフォルダーはCFSTR_FILEDESCRIPTORを使うので、普通にWPFアプリを作るとファイルを受け取ることができないのだ。


CFSTR_FILEDESCRIPTORの文字列は"FileGroupDescriptorW"なので、テストアプリを書いて試すと、ZIPフォルダーからのドラッグ・アンド・ドロップでは確かにGetDataPresent("FileGroupDescriptorW")が真になる。それはいいのだが、GetData("FileGroupDescriptorW")しても、なんだかよく分からないMemoryStreamが得られるだけで、ファイルの中身は得られない。先述したWindows SDKのドキュメントは、COMを熟知していることを前提にC++用に書かれていて、C#からのアクセス方法は自明ではない。


ということで、正解はこちら (https://gist.github.com/AlissaSabre/074416480bac8408548fdcadc48ea460)。このクラスを使うと、CFSTR_FILEDESCRIPTORのドラッグ・アンド・ドロップをWPFアプリで受け取ることができる。使い方はdocumentation commentに書いたが、結論だけ言うと、次の例のような感じで使えばOK。


async void MyDragEventHandler(object sender, DragEventArgs e)
{
OutlookDataObject data = new OutlookDataObject(e.Data);
if (data.GetDataPresent("FileGroupDescriptorW"))
{
string[] names = (string[])data.GetData("FileGroupDescriptorW");
for (int i = 0; i < names.Length; i++)
{
using (Stream outstream = File.Create(names[i]))
{
MemoryStream content = data.GetData("FileContents", i);
await content.CopyToAsync(outstream);
}
}
}
}

ここで急にOutlookなんていう名前が出てくるが、Outlookに特有のことはやっていない。このコードはもともとMattyBoy4444氏のGist (https://gist.github.com/MattyBoy4444/521547) で、それがそういう名前だったのでそのままにしてある。ZIPフォルダーではなくてOutlookのメールの添付ファイルを受け取ることが目的だったので、名前にOutlookが入っているようだ。(たぶん、OutlookもCFSTR_FILEDESCRIPTORなんでしょう。知らんけど。) MattyBoy4444氏のコードは.NET 8までは動くのだが、.NET 10では動かなかった。原因は、WPFの実装内部にあるprivateなメソッドを、リフレクション経由で無理やり呼んでいること。.NET 10では、そのメソッド (GetDataFromHGLOBAL) がなくなってしまったらしいのだ。そこで、代わりのコードに書き換えたのがAlissa版。これは.NET Framework 4.8でも、.NET の 6~10でも動く。privateメソッドをゴニョゴニョしていないので、将来も動く可能性は高いと思う。


Alissa版は、先日紹介したaiimetaでドラッグ・アンド・ドロップをサポートするために書いた。この記事は答えが分かった後で書いているのだが、最初はドロップの受け取り方が分からなくて大変だった。ChatGPTに聞いたら「こうすればできます」としてコード例を教えてくれたが、そのコードは全く動作しなかった。でも、その情報の出典として教えてくれたURLの中にMattyBoy4444氏のGistが含まれていて、これがビンゴだったのだ。コード例はダメだったが、正解にたどり着けたのはChatGPTのおかげ。このgistは、Outlookの添付ファイルという体になっているので、おそらく普通の検索では自力で見つけられなかっただろうと思う。ChatGPTはウソも言うが役に立つ。そういうことだった。

2026年1月23日 (金)

CPUでのfp8の演算

fp8はGPU専用のデータ形式なので、CPUで扱おうとすると死ぬほど遅い、という話を見かけた。確かに普通のCPU (x64など) では、fp32はCPUが演算命令を持っているが、fp16/bf16/fp8を扱う命令はない。だから、これらのデータ形式をCPUで扱う場合は、いったんfp32に変換 (ソフトウェアで処理して、20命令くらい?) してfp32で演算し、結果をまた変換する、という感じになっているようだ。

でも、よく考えてみると、fp16はともかく、fp8というのはたかが1バイト。fp8同士に四則演算なんかはそれぞれ64KBの表にしておけば、素朴にやっても3命令で演算できる。この表も2次キャッシュなら乗り切っちゃうんじゃなかろうか。ちょっとした計算ならfp8のまま表引きで計算した方が速いんじゃないだろうか。

※ なんて、もう誰かが考えて試しているだろうと思って少し探したが見つからなかった。ちょっと記事を書く価値もないようなダメな案なのだろうか。

2026年1月17日 (土)

画像生成AIで遊んでいる人の役にたつかもしれません

aiimeta というツールを作りました。画像生成AIで生成した画像ファイルから、「AI生成メタデータ」というものを取り出して表示するアプリです。具体的にはプロンプト (いわゆる「呪文」) や使用したAIモデル、各種設定パラメーター等を表示できます。

このツールは「画像を分析してプロンプトを推測する」というAI的なことをやるわけではなくて、画像ファイル中に埋め込まれている情報を取り出すだけです。AI生成画像の全てにメタデータが埋め込まれているわけではないので、メタデータを含まない画像では表示できません。Stable Diffusion WebUI (通称 A1111) に PNG Info という機能がありますが、あれと同じようなものです。ただし、A1111 の PNG Info は、A1111 自身 (または互換ツール) で生成した画像の情報しか表示できませんが、aiimeta は ComfyUI や SwarmUI にも対応しています。なお、AI生成画像を投稿できるサイトはたくさんありますが、サイトによっては投稿された画像からメタデータを削除するところもあるようです。そういうサイトの画像を aiimeta に入れてもメタデータは表示できません。あしからず。

(pixivの「AI生成」タグのついた画像はAI生成メタデータを含んでいるものが多いような気がします。)

Windows 用の .NET 10 WPF アプリです。Stable Diffusion 等の画像生成 AI で遊んでいるかたなど興味のあるかたは GitHub からダウンロードしてお試しください。なお、.NET Desktop Runtime の 10.0 以降が必要です。(現時点では 10.0 が最新版なので「以降」というのは変ですが。)

この記事で触れた「文字ボケ」対策も入っています。タイムリーだったもので。

2026年1月 2日 (金)

WindowsのDPIの違うマルチモニター環境でWPFアプリの文字がボケないようにする

いくら探してもちゃんとした説明が見つからないし、ChatGPT 5が教えてくれたやり方も機能しなかった。

正解は shibayan/WPF-PerMonitorV2-Sample にあった (芝村さん、ありがとうございます!) が、解説が一切ない。せっかくなので、私が理解した範囲で解説しておく。

何が問題か

最近のWindowsでは、「ディスプレイ」の「拡大/縮小」という機能によってモニターごとに「倍率」を指定できる。開発者ではないユーザー向けの説明には「OSが自動的にアプリの表示を拡大縮小します」と書いてある。起動したアプリのウインドウを倍率が違うモニターに動かすと、アプリが何もしない場合は確かに「OSが自動的に表示を拡大縮小する」のだが、これは画像としての拡大縮小なので文字がボケて読みにくくなってしまう。

でも、Windowsの標準のアプリ (エクスプローラーとかメモ帳とか) はどれも、文字がボケずに表示される。それは、「アプリが何とかするので、OSは倍率の通知だけやってくれ」というモードがあって、それを使っているから。実際には個々のアプリが自分で「何とかするコード」を書くわけではなくてUIフレームワークが何とかしてくれる。今回の私の関心事はWPFなのだが、もちろんWPFもその機能を備えている。

…のだが、(マイクロソフトによると「互換性のために」) デフォルトではこの機能が無効になっている、というか、使い勝手の悪いモードがデフォルトになっているので、そのままだと結局文字がボケてしまう。この設定を変えれば解決する。

設定方法

プロジェクトにapp.manifestを追加して、次のように書く。

<?xml version="1.0" encoding="utf-8"?>
<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1">
<assemblyIdentity version="1.0.0.0" name="MyApplication.app"/>
<application xmlns="urn:schemas-microsoft-com:asm.v3">
<windowsSettings>
<dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">PerMonitorV2</dpiAwareness>
<dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true</dpiAware>
</windowsSettings>
</application>
</assembly>

※ プロジェクトにすでにapp.manifestが存在する場合は、それとマージする。新規に追加する場合、asseblyIdentity要素は、もちろんこの例のままじゃなくて適切な値に書き換える必要がある。

文字がボケる件に関係するのはdpiAwareness要素とdpiAware要素で、この内容は丸写しでいいはず。

Windows 10のサポートが終了し、Windows 12もリリースされていない現在では、dpiAwarenessは「PerMonitorV2」固定でいいと思うのだが、実際には5通りの設定が書けるようだ。その説明はここにある。ただし、この説明はWindows Forms用のMSBuildプロジェクトファイルの説明なのでApplicationHighDpiModeなんていう要素名になっている。XMLの書き方はWPFとは違うので参考にしてはいけない。

※ 「だったら、なんでそんなページを参照するのか」と思うだろうが、それはマイクロソフトの公式ドキュメントでPerMonitorV2の説明が他に見つからないからだ。一体、どうなっているのだろう。

.NET Framework 4.8 以降と.NET 6 (というか、Windows Desktop 6 なのか?) 以降で使えるはず。

本題と関係ない追記

大晦日の深夜に、1年ぶりにSecond Lifeにログインして、Arare Cafeにおじゃましたわけですが、ほとんど動かず会話もせずにじっとしていたのは、実は裏で本件の調査をしていたからでした。ちょっと急ぎだったのですが、年が明けた時点ではまだ解決していなかったので、SLはおろそかになってしまいました。久しぶりだったのに失礼しました。

2025年9月27日 (土)

ComfyUIの「Prompt has no outputs」というエラーは「ワークフローに結果を出力するノードが含まれていない」という意味

原因は、ワークフローの編集中にSave Imageノードを削除してしまったり、いろいろ実験していてBypassしてあったり、が多いと思う。

Comfy

私はこのメッセージの意味が分からなくてしばらく悩んでしまったよ。落とし穴は「Prompt」という言葉だ。画像生成AIで「プロンプト」と言ったら、生成する画像を方向付けるテキストデータ、通称「呪文」のことだと思うよね。でも、この「Prompt」はそれとは無関係だったのだ。ComfyUIの「ワークフロー」は、画像生成処理を実行する際にはpythonの内部データ形式 (dict らしい) に変換されているのだが、このデータのことをComfyUIのソース上ではpromptと呼んでいて、それのことだった。エンドユーザー的には、ComfyUIの開発者用語の「Prompt」は「ワークフロー」と同じ意味だと思えばOK。

ということを発見するまでに3時間くらいかかった。そんなこと一般ユーザーには分かりませんよ。Workflow contains no output node くらい言ってくれれば一瞬で分かったのに。

2025年2月 2日 (日)

AngleSharp.js の最新「stable」バージョンは最近の AngleSharp の stable バージョンとは互換がないらしい

AngleSharp (.core) の比較的最近のバージョンはどれも、AngleSharp.js の最新 stable バージョン (0.15) とは互換がないらしい。AngleSharp.js の 1.0.0-beta シリーズを使用する必要がある。

具体的に、AngleSharp 1.2.0 stable と AngleSharp 0.15 stable の組み合わせでは動作しなかった (エラーにもならず、警告メッセージようなものも出ず、単に JavaScript を実行しない)。AngleSharp 1.2.0 stable と AngleSharp 1.0.0-beta.41 との組み合わせは動作した。

このことに気づくまでに3日かかってしまった。(1日あたり2時間くらいずつしか費やしてないけど。) 同じプロジェクトのライブラリーで、最新の stable 同士で組み合わせられないなんて、普通考えないでしょ? しかも、nuget のメタデータでも組み合わせ可能なバージョンだって書いてあるし。解決には、ウェブでいろいろ調べまくっていてたまたま見つけた、Zennの「AngleSharp で ASP.NET Core ウェブアプリケーションの統合テストをする」という記事がヒントになった。この記事では、AngleSharp.js の stable 版はリリースが古く、最新の JavaScript に対応していないので 1.0.0-beta を勧める、みたいに書いてあるのだが、実際は、そういう問題ではなくて、stable 版は全く使い物にならないのだった。

※ ちなみに、困っているときに、BrowsingContext を作るコードを Microsoft Copilot に見せて「JavaScriptを実行できないんだけど何が悪いのか?」と聞いたら、「.WithJS() ではなく .WithJavaScript() を使用してJavaScriptを有効にします。」とか「using AngleSharp.Scripting; と宣言してあることを確認します。」とか言われて調査が迷走したのも時間がかかった一因。「.WithJS() とは別に .WithJavaScript() っていうのがあるのか?」って思ってググると、確かにそういうメソッドで config 作っているコードがたくさん見つかる。でも、実際に書くとそんなメソッドはないと言われ、コンパイラが「package reference が不足していませんか?」という。それで探したら、nuget に AngleSharp.Scripting.JavaScript」なんていうパッケージもあるんだもの。実際はこれは大昔のAngleSharpの話で、今は .WithJavaScript() なんていうメソッドは存在しないのだった…。

「人生に無駄なことなどない」と言うが、この経験もいつか役に立つことがあるのだろうか…。こんな経験が役に立つ場面には、二度と遭遇したくないものだが。

追記

nuget で単に AngleSharp.Js の 1.0.0-beta.41 をインストールすると、dependency として Jint のかなり古いバージョンが自動インストールされるが、それでは動作しない。Jint のもっと新しいバージョン (例えば 4.2.0) をインストールする必要がある。そうすると、nuget が「NU1608 Detected package version outside of dependency constraint: AngleSharp.Js 1.0.0-beta.41 requires Jint (>= 3.0.0 && < 4.0.0) but version Jint 4.2.0 was resolved.」などと文句を言うが、このメッセージは無視する。

なんなんだ。

2024年11月28日 (木)

シリカゲルの滑り止め?

アマゾンの様々な商品の説明を見ていると、変な場所に「シリカゲル」という言葉が使われているものがある。滑り止めやシール材をシリカゲルで作るはずはなく、実物はゴムっぽい素材なので、おそらくシリコーンゴムなのだと思う。そういう場合に販売者の住所を見ると、(今まで私が気づいたものは) 全て中国だ。逆に、(最近気にしているのだが) 住所が中国になっている販売者のいささか怪しい日本語の商品説明中で「シリコーンゴム」という言葉を見たことがない。常に「シリカゲル」と言っている。

おそらく、アマゾンが提供している機械翻訳システム (アマゾンに商品を出品する際に、商品説明を英語か中国語で入力すると自動的に日本語に翻訳してくれるらしい) が、中国語で「シリコーンゴム」を意味する言葉を「シリカゲル」と翻訳してしまうのではないかと想像して、いろいろと調べたのだが、良く分からない。

辞書などで調べると、「シリコーンゴム」は中国語では「硅橡胶」「矽氧橡胶」「聚烃硅氧」などと言うらしいのだが、実際にAWSの自動翻訳サービスで試すと前二者はちゃんと「シリコーンゴム」と翻訳される。最後の「聚烃硅氧」は「ポリ炭化水素シロキサン」という訳語になるのだが、どうやらこれは「オルガノポリシロキサン」の異称らしい。いわゆるシリコーン樹脂類の総称だ。(そういう意味では、「聚烃硅氧」はゴム質ではないシリコーン樹脂も包含する言葉なのかもしれないが、よく分からない。)

一方「シリカゲル」もいくつかの呼び方があるようだが、その中に「硅胶」というものがある。「硅橡胶」と比べると中一文字が抜けたものになっている。なので、(日本語や英語で「シリコン / silicon」と「シリコーン / silicone」を混同している人がいるように) 「硅胶」と「硅橡胶」を混同している人がいて中国語の原稿に最初から「硅胶」(シリカゲル) と書いてしまうことがある、ということならありそうだが、しかしそうであれば、ときどきシリカゲルになっている商品があるくらいならわかるが、100%どの商品説明もシリカゲルにはならないだろうと思う。

不思議だ。

2024年10月24日 (木)

何か変な焼き豚レシピ

ウェブで (Cookpadじゃない 😃) 見つけたプロっぽい人の焼き豚のレシピなのですが、変なポイントだけ書くと、



  1. (いろいろ下準備) ... 片栗粉は倍量の水で溶いておく。

  2. (タレを作ってかたまり肉を漬ける)

  3. 冷蔵庫に入れて一晩おく。

  4. 肉を低温加熱調理する (4時間)。

  5. 煮汁を小鍋にとって 1 の水溶き片栗粉を加え …


このレシピだと、仕込み (前日) のうちに片栗粉を水で溶いて、そのまま一晩+4時間おいておく、ってことになっちゃいますよね。なぜ、そんなことをするのだろうか…。(しかも、そのころにはせっかく溶いた片栗粉がすっかり沈殿しちゃっているはず … って、そこは大事じゃありませんが。)

2024年10月12日 (土)

Google の Cloud Translation API Advanced Service (v3) って昨日は不調でしたか?

このブログに書くのは初めてだと思うのだが、数年前から Google の Cloud Translation API というものを使っている。主に Advanced Service、通称「v3」。

それで、昨日、この API を使うあるツールを修正していたら、そのツールが動かなくなってしまった。

ちょっと大掛かりな修正 (機能追加) をやっていて、しばらくビルドできない状況が続いていたが、やっとビルドできるようになったので試したところ動かなかった。それだけなら至極よくあることだし、特に驚くようなことではないが、今回触ったのはコンテンツの部分だけなのに Cloud Translation API を呼び出す部分で例外が出ていた。ちょっとしたラッパークラスを自分で作っていて、そこは一切触っていなかったのだが、そのクラスの中で。Google.Cloud.Translate.V3.TranslationServiceClient.DeleteGlossary というメソッドの呼び出しが失敗して Grpc.Core.RpcException になる。

例外の StatusCodeInvalidArgument だった。Status.Details を見ても単に "Invalid argument." と書いてあるだけ。当惑したのは、このメッソドには引数が一つしかなく (実際は二つあるのだが、もう一つは省略していた)、文字列型で、そこには毎回固定の文字列を渡しているだけだったからだ。(その文字列は削除する glossary の識別子のようなもので、そのツールでは固定の glossary を一つだけ使うので。) もちろん、その文字列も今まで動いていたものから変更していない。もうちょっとエラーの詳細が知りたいところだが、「InvalidArgument」という以上の追加情報は得られないようだった。

最初に考えたのは、例外が発生するのはこのメソッドだが、実際に間違っているのはこれ以前の Cloud Translation API の呼び出しで、たまたまこのメソッドで間違いが発覚するのではないか、というもの。それで、ほとんど何もせずにこのメソッドに来るようにパッチしたコードを作り、少しずつ呼び出しを増やして原因を探ろうと考えた。ところが、何もしなくてもこのメソッドはエラーになる。クライアントオブジェクトを作って、クレデンシャルを設定して、それですぐ削除しても例外。クレデンシャルの設定に間違いがあるならその部分でエラーになるはずだし、そもそも削除の前の手順はさっきまで問題なく動いていたし。(ちなみに、削除するべき glossary は存在していたし (それはすぐに調べた)、もしも存在しない glossary を削除しようとすると StatusCode.InvalidArgument ではなく StatusCode.NotFound になることは分かっていた。)

いろいろと小さなテストコードを書いて状況を調べたりしたのだが、原因どころか、何が起きているのかもさっぱり分からず。もう深夜になっていたので、あきらめて寝た。

それで、今朝だ。朝起きてすぐに調査の続きをやろうと思ったら … 例外が発生しません。問題なく動きます。

いや、その。動いたからいいとも言えるが、昨日のアレは何だったんだ、と。Google さん、勘弁してよ、と。

とりあえず、不満の掃け口として、ブログに書いてみました。

ということで、たった一つの事象を一般化して導き出した法則:

Google の Cloud Translation API は、勝手に不調になって謎の例外が起きることがある。
その場合、放っておけば翌日には回復する。

(うーむ、本当だろうか?)


ちなみに、Cloud Transationi API を使い始めたころに作ったサンプルがここにあります。GCPのアカウントがあれば API を使った翻訳を試してみることができます。 (トラブったツールはこれじゃありませんがもしも機能これを動かしたら、一部機能は動作しなかったはず。DeleteGlossary 呼ぶ場所があるので。)

2024年10月 1日 (火)

git の「ワークツリー」という用語

日本語で git のことを解説している記事を読むと、初心者向けのものも、かなり高度な内容のものも、だいたいどれも「ワークツリー」という用語を使っているようだ。記事によっては「作業ツリー」になっている場合もあり、また work tree とアルファベットで書いている場合もあるが、アルファベット表記の場合は worktree と一語で書いている記事と work tree と二語で書いている記事が両方あるようだ。

それで、本来英語ではどういうのかと思って git の公式の (英語の) ドキュメントを調べた。そうしたら、なんと、work tree でも worktree でもなくて working tree と呼ぶのが正しいようだった。

びっくりだ。

git のコマンドには、git worktree というものがあって、これは一語の worktree が正しいサブコマンド名。また、git が受け付ける共通オプションの中に --work-tree= というものがあって、これは work tree と二語で書く (ものを、単一のオプション名にするために間にハイフンを入れる) のが正しい。コマンドとしてはそれが正しいのだが、ドキュメントの説明の文章が、それを指す言葉としては、worktree とか work tree とかは使われてなくて、一貫して working tree という言葉を使っているのだ。少なくとも、git の公式ドキュメントは。(The Git Reference Manuals も、Pro Git も。どちらも英語版。) 

それで、Reference Manual はだいたいどれも公式の日本語訳があったなと思って調べたところ、どうやら公式訳は「作業ツリー」と訳しているようだった。ただ、一か所だけ、用語集が「作業ツリー (working tree)」と英ママ (というか、対訳?) で書いている。

ということで、git のこの用語は、英語は「working tree」で、日本語は「ワークツリー」または「作業ツリー」ということらしい。


なぜ、英語の working tree をカタカナで書くと「ワーキングツリー」ではなくて「ワークツリー」になるのか。

すぐに思いつくのは、コマンドに出てくるのが worktree と work-tree なので、それが正しい用語だと思い込んでしまった、というもの。また、英文の公式ドキュメントを読まずに、日本語の解説記事ばかり読んでいるとどれも「ワークツリー」と書いてあるので、それを覚えてしまう人が続出して広まった、というもの。ストーリーとしてはもっともらしい。

でも、意図的にそうしている可能性もあるかもしれない。それは、世の中には英語からカタカナ表記を作る際の「ルール」みたいなものがあって、その中に「~ing は省く」みたいなのがあった気がするからだ。(「あるからだ。」と書こうとしてウェブで調べたが、そのルールを発見できなかった。なので「気がするからだ。」と書いた。私の思い込みかもしれない…。)

実際はどういう理由なのだろうか?

 

«AngleSharpでHTMLファイルを更新する

フォト
無料ブログはココログ