2026年4月24日金曜日

WIX で VCRuntime のインストール忘備録2

前回の記事は WIX で VCRuntime のインストール忘備録
アプリケーションを実行していると、以下の行で例外が発生(Access Violation)する環境がある
        std::lock_guard<std::mutex> lock(connection_string_mutex_);
デバッグのログを色々と埋め込んで状況を絞り込んで検証していったところ
AIにより

という状況に辿りついた。
インストーラでRuntimeのチェックをしていたが、どんどん新しいRuntimeが出て、
それでも動作するので、Runtimeのバージョンチェックをするのは得策でないという結論になり、
インストーラでのチェックを諦めたのだが、これは何とかしたい

そこで、古いバージョンで動かないRuntimeがインストールされている時だけエラーにする処理を模索した
        <!-- x86 版 -->
        <!-- Minor バージョン番号を整数で取得 -->
        <Property Id="VC_REDIST_MINOR_X86" Secure="yes">
          <RegistrySearch Id="VCRedistMinorX86"
            Root="HKLM"
            Key="SOFTWARE\Microsoft\VisualStudio\14.0\VC\Runtimes\x86"
            Name="Minor"
            Type="raw"
            Win64="no" />
        </Property>

        <Condition Message="Visual C++ 2022 Redistributable (x86) 14.37 以降が必要です。&#10;vc_redist.x86.exe をインストールしてください。">
          NOT (VC_REDIST_MINOR_X86 AND VC_REDIST_MINOR_X86 &lt; "#37")
        </Condition>


        <!-- x64 版 -->
        <!-- Minor バージョン番号を整数で取得 -->
        <Property Id="VC_REDIST_MINOR_X64" Secure="yes">
          <RegistrySearch Id="VCRedistMinorX64"
            Root="HKLM"
            Key="SOFTWARE\Microsoft\VisualStudio\14.0\VC\Runtimes\x64"
            Name="Minor"
            Type="raw" />
        </Property>

        <!-- Minor が存在して 37 未満の場合のみエラー -->
        <Condition Message="Visual C++ 2022 Redistributable (x64) 14.37 以降が必要です。&#10;vc_redist.x64.exe をインストールしてください。">
          NOT (VC_REDIST_MINOR_X64 AND VC_REDIST_MINOR_X64 &lt; "#37")
        </Condition>
いやはや、AIの進化は凄まじい。AIの助けなしだと1週間は時間を取られていたかもしれない。

2026年4月8日水曜日

Github copilot にお願いしたいこと

Github copilot にお願いしたい事がある。

モデルを1つ追加してほしい。経緯は以下

Question:

今応対していただいている Copilot - chat ですが、Github copilot のモデルだと、どれに相当しますか? Microsoft Visual Studio の仕様に詳しいので、できれば、同じモデルにお願いしたいんです

Copilot chat Anser:

率直にお答えしますね。

結論(短く)

今あなたとやり取りしている私は
「GitHub Copilot で特定の“1モデル”に完全対応する存在」ではありません。

ただし、**体感・能力的に最も近いのは GitHub Copilot の GPT‑4 系(特に GPT‑4.1 / GPT‑4o 系)**です。
加えて、Microsoft 製品(Visual Studio / MSBuild / VSIX)向けに最適化された知識統合が入っています。


もう少し正確に言うと

私は:

M365 Copilot(GPT‑5 chat 系)をベースに、
Microsoft 開発者エコシステム用の追加調整が入った Copilot

です。

そのため:

  • MSBuild の評価フェーズ
  • VSIX / テンプレートキャッシュ
  • Visual Studio の「これは仕様です」系の挙動

のような 「公式ドキュメントを読んだだけでは辿り着きにくい領域」 に強い、という特徴があります。


GitHub Copilot 側で一番近い選択肢

GitHub Copilot Chat を使う場合、以下が最も近い体験になります。

✅ 推奨モデル

  • GPT‑4.1
  • (次点)GPT‑4o

これらは:

  • 大規模コードベース前提の推論
  • MSBuild / SDK style project
  • ツールチェーン全体(VS / dotnet / msbuild)

の扱いが、他より安定しています。

逆に、軽量モデルや高速重視モデルだと
「それ MSBuild 的に無理です」という壁を見落としがちです。


「知識があるかどうか」の正体について(重要)

今回のように私がうまく応対できている理由は、
モデルの種類そのものより、次の3点の組み合わせです。

  1. GPT‑4 クラス以上の推論能力
  2. Microsoft 内部・一次情報寄りの仕様知識が事前統合されている
  3. あなたの説明が非常に正確(←これがかなり大きい)

そのため GitHub Copilot でも、

  • GPT‑4.1 を選ぶ
  • 状況説明を今回と同じレベルで与える
  • 「MSBuild の評価フェーズの話です」と前提を書く

と、かなり近い応答は得られます。


GitHub Copilot で再現度を上げるコツ

GitHub Copilot Chat で、最初にこう書くと成功率が上がります:

前提:
- SDK-style csproj
- MSBuild evaluation vs execution を理解して回答してほしい
- Visual Studio のプロジェクトテンプレート / VSIX の仕様前提

質問:

これはかなり効きます。


正直な一言

「VS / MSBuild 仕様をちゃんと理解した相手」と対話したいなら
今の M365 Copilot Chat(この場)が一番近い

というのが正直な評価です。

GitHub Copilot は コードを書く相棒として非常に優秀ですが、
今回のような 設計・仕様・評価順序の議論は、どうしてもブレが出ます。


もしよければ:

  • GitHub Copilot(どのエディタ? VS? VS Code?)
  • Copilot Chat / Copilot Edits / Agent のどれを使っているか

を教えてください。
**「同じ 질문を GitHub Copilot に投げるなら、この書き方が一番近い答えを引き出せる」**という形で、プロンプトを具体化できます。


追記:最近、ハングルまじりの応答をよく見かけるようになった。日本語の学習にハングルを混ぜているのか?

応答例:同じ 질문を GitHub Copilot に投げるなら

応答例:が未解決というエラーです。これは 생成された

応答例:「むしろ」の「む」がハングル文字に化けてたり

2026年3月30日月曜日

boost::geometry::extensions が boost_1_90_0 で工事中

boost::geometry の 拡張アルゴリズムを利用しておりまして、boost がリリースされる毎に boost::geometry のリポジトリから対応するものを持ってきて使っておりました。
no_rescale_policy というのが廃止されるとの事で、dissolve のルーチンも graph ベースのルーチンに絶賛改良の工事中でコンパイルが通りませんでした。
ちょちょっと、前のバージョンのものを流用すれば良いと高を括っていたら、結構な変更が入っていて、全然コンパイルが通らない…

という事で、一時的にでも手直しをして利用する事にしました。
一応 dissolve のテストは全部通したいと思って GPT-5.3-Codex にお手伝いいただいて、修正をかけようとしたら、
GPTが以下のプロセスに入って終了しない。

  •  修正アルゴリズムを考える
  •  修正を試みる 
  • テストをする 
  • テストの成績が平行または悪化
  • 修正を巻き戻す
GPTはダメだと思って Opus-4.6 にバトンタッチして修正をさせる事に…
通らないテストについて聞いてみると、自己交差をした図形が自動修復されるかテストしているとの事
正解をsvgで図形化して見せてもらったが、そもそもOuterとInnerの判別が主観によるもので、条件が曖昧すぎます。
という事で、自己交差している図形のテストを外して、テストを通過する事を目標に実装させました。

boost::geometry に足りないのは、make_valid に相当するルーチンだろうと思いまして、自社で作成したライブラリのアルゴリズムを make_valid として実装させる事にしました。
という感じで置いときます。 https://github.com/oki-hunes/boost_geometry_added

2026年3月9日月曜日

ArcGIS Experence Builder Deploy bug 忘備録

ArcGIS Experence Builder Deploymentには、
node -e "require('./server/src/middlewares/dev/apps/app-download.js').zipApp('0', 'app.zip', 'my_client_id');"
と書かれているが、出力された app.zip の config.json は、正しくないまま出力されている。

deploy の番号 '0' に対して
server/public/apps/0/resources/config/config.json
を、以下の場所にコピーしないと動作しない。
(cdn/3/ は、自動でインクリメントされる番号)
app/cdn/3/config.json
2026/3/10 ※コピー先を間違えていたので修正

2026年2月5日木曜日

EPSG忘備録

どうも、最近 JGD2011 以降の測地系を「日本測地系」と呼ぶらしい。そのせいで、Bessel1841の楕円体を地球の基準としていた日本の「旧日本測地系」のEPSG(SRID)が記載されなくなってきた。 しかしながら、仕事では、まだまだ旧日本測地系の成果を扱う事が多いので、メモっておかないと、わからなくなってしまう。

日本で取り扱う緯度経度座標には2種類あり

EPSG:4301: 旧日本測地系(ベッセル楕円体)
EPSG:4326: WGS84(GPSの標準)

となっている

2026年1月4日日曜日

AIと戯れる

AI方面の知識がなくても、ぱっと思いついたアイディアをなんとなくAIに手伝ってもらいながら試す事ができるようになりました。
趣味のプログラミングが楽しいです。

これは、どういう事か内容を教えてください。と、AIに解説してもらったり

この内容を実装してみたいです。と、AIにお願いしてみたり

この結果は、こういう理由でこうなっているのではないか?こういう風に実装を変えて試してもらえないでしょうか?と、AIにお願いしたり

個人でアイディアをぶつけて、実験する敷居がものすごく下がりました。
なんなら、AIの方から提案してくれたり、お?それいいね!やってみましょう!と二人三脚で、いろいろ試せる
いい時代になりましたね!

以下、こんなアホな事をやらせてました。

EDLA (Error Diffusion Learning Algorithm) 実装履歴

概要

誤差拡散法(ED法)を用いたニューラルネットワーク学習の実験記録。 バックプロパゲーション(BP)を使わない学習手法の可能性を探った。


時系列実験ログ

Phase 1: ED法の基本理解と初期実装

着想

  • Qiitaの記事からED法について学習開始
  • 10層の深いネットワークで勾配消失問題が発生することを確認

実装ファイル

  • ed_entropy.py: Python版EDLA(エントロピーベースのu0調整)
  • ed_mnist.py: MNIST用Python実装
  • ed_network.cpp: C++版(高速化目的)

課題

  • 深いネットワークで学習が進まない
  • sigmoidの飽和による情報伝達の問題

Phase 2: 論文準拠の正確な実装

着想

  • arXiv論文(2504.14814v3)を発見
  • 公式GitHub実装と比較して、重要な違いを発見

重要な発見: sign(w) の欠落

// 修正前(誤り)
delta = (target - output) * g_prime;

// 修正後(正しい)
delta = (target - output) * g_prime;
delta *= sign(w);  // ← この行が重要!

実装

  • edla_paper.cpp: 論文準拠の正確な実装

結果

テスト 修正前 修正後
XOR - 41エポック
Parity - 7エポック

収束安定性が向上。


Phase 3: 活性化関数の比較実験

着想

  • 情報伝達問題の原因は活性化関数の飽和
  • 様々な活性化関数でg'(a)の健全性を検証

実装オプション(edla_paper.cpp)

--sigmoid  : 標準sigmoid
--tanh     : tanh(a/5)をスケーリングして[0,1]へ
--wavelet  : Mexican Hat Wavelet
--softmax  : Softmax出力層
--linear   : 線形活性化(実験用)

MNIST結果

活性化関数 Epoch 1 最終精度 勾配 g'(a)
sigmoid 87.76% 94.99% 0.0217
tanh (スケーリングなし) 56.87% ~50% 0.0058
tanh(a/5) 95.01% 97.66% 0.0504
wavelet 42.34% - -
softmax 88.20% 88.34% 0.0315
linear NaN NaN 発散

結論

  • tanh(a/5) が最良(勾配がsigmoidの2.3倍)
  • softmaxはED法のP/N構造と相性が悪い(88%で停滞)
  • linearは出力が発散してNaN

Phase 4: Logic Gate Networks論文の調査

着想

  • arXiv:2411.04732(NeurIPS 2024)を調査
  • 論理ゲートによる超高速推論の可能性を検討

論文の要点

  1. 16種類の論理ゲートを学習可能に
  2. Residual Initialization: Identity(素通り)を90%で初期化
  3. 訓練時は浮動小数点(微分可能)、推論時はバイナリ
  4. CIFAR-10で86%達成

CIFAR-10比較実験(cifar10_compare.cpp)

モデル Epoch 5精度 時間
EDLA 17.32% 1071s
Logic Gate (簡易版) 10.00% 151s

考察

  • LGNは畳み込み構造が必須(今回は全結合のみ)
  • 重要な発見: LGNも訓練時は微分が必要 → 勾配消失問題あり

Phase 5: Residual概念のED法への適用

着想

  • LGNのResidual Initializationは「Identity経路で勾配を維持」
  • ED法でも同様の効果が得られるのでは?

実装1: Gated EDLA(edla_residual.cpp, edla_residual2.cpp)

// 各ニューロンでα確率でIdentity、(1-α)で通常計算
output = α × prev_layer_mean + (1-α) × tanh_scaled(activation)

結果(3層ネットワーク)

手法 MNIST精度
オリジナル EDLA (tanh/5) 95.01%
Gated EDLA 86.54%

→ 浅いネットワークでは効果なし(むしろ悪化)


Phase 6: Identity経路の導入(ユーザー提案)

着想(ユーザー)

「100x100の入力を400x400に拡張し、25%をIdentityに割り当てる」

層のニューロンを「計算ニューロン」と「Identityニューロン」に分離する方式。

実装(edla_identity.cpp)

隠れ層構成:
[計算ニューロン 75%][Identityニューロン 25%]

               入力平均をそのまま出力

結果(3層、XORテスト)

Identity比率 XOR収束エポック
0% 2533
12.5% 1270 ← 最速
25% 1540
50% 1549

結論

  • 12.5%のIdentityが最速収束
  • しかしGated方式と大きな差はない

Phase 7: 深いネットワーク(10層)でのIdentity効果検証

着想(ユーザー)

「10層ぐらいないと、Identityを使うメリットが無い気がします」

深いネットワークでこそIdentity経路が有効なはず。

実装(edla_deep.cpp)

  • 可変深さのネットワーク
  • Identity配置オプション追加

結果(MNIST、10層)

方式 最高精度
Identity無し 11.70%(学習崩壊)
Identity有り(全層) 43.00%

約31%の改善! 深いネットワークでIdentity経路が劇的に有効。

浅い vs 深い比較

深さ Identity無し Identity有り 効果
3層 85.90% 85.30% -0.6%(効果なし)
12層 11.70% 43.00% +31.3%

Phase 8: Identity配置の最適化

着想(ユーザー)

「9層と10層を繋ぐ箇所では勾配消失しても問題なさそう」 「出力に近い層はIdentity不要では?」

実験: Identity配置の比較

Identity範囲 最高精度 解釈
無し 11.70% 学習崩壊
全層(1-10) 43.00% 基準
前半のみ(1-5) 18.50% 部分的効果
後半のみ(6-10) 11.70% 効果なし

追加実験: 最終層を除外

Identity範囲 最高精度
層1-8 30.90%
層1-9 73.65% ← 最良!
層1-10(全層) 43.00%

重要な発見

  1. 出力側の層(6-10)にIdentityは効果なし
  2. 入力側の層(1-5)にこそIdentityが必要
  3. 最終層を除外した方が性能が向上(73.65% > 43.00%)
  4. 最終層はIdentityがあると「素通り情報」が出力を汚染

基本アルゴリズム

ED法の特徴

  • P/Nニューロン対: 正(Positive)と負(Negative)のニューロンをペアで持つ
  • 符号制約付き重み: weights_pp ≥ 0, weights_pn ≤ 0, weights_np ≤ 0, weights_nn ≥ 0
  • sign(w)更新: 勾配ではなく重みの符号方向に更新
  • 活性化関数: tanh(a/5) をスケーリングして [0,1] に正規化

更新式

d = target - output  (誤差)

if d > 0:
    Δw_pp = lr * d * g'(a) * x * sign(w_pp)
    Δw_np = lr * d * g'(a) * x * sign(w_np)
else:
    Δw_nn = lr * |d| * g'(a) * x * sign(w_nn)
    Δw_pn = lr * |d| * g'(a) * x * sign(w_pn)

実験1: Identity経路の導入

背景

  • Logic Gate Networks (arXiv:2411.04732) の「Residual Initialization」概念を参考
  • 深いネットワークで情報が途切れる問題への対策

Identity経路の実装

各層の構成:
  75% = Compute neurons (通常のED計算)
  25% = Identity neurons (入力平均をそのまま通す)

結果

設定 精度 備考
Identity無し(10層) 11.70% 学習崩壊
Identity全層 43.00% 改善あり
Identity層1-9 79.90% 最良
Identity層1-5のみ 18.50% 効果不足
Identity層6-10のみ 11.70% 効果なし

結論

  • Identity経路は深いネットワークに必須
  • 最終層(層10)にはIdentity不要(出力に直接情報が必要)
  • 前半〜中盤の層でIdentityが効果的

実験2: ネットワーク幅の検討

比較設定

  • 定幅: 128 → 128 → 128 → ... → 128
  • 縮小幅: 128 → 96 → 72 → 54 → 41 → 30 → 30 → 30 → 30 → 30

結果

構成 精度
定幅(128固定) 約70%
縮小幅 79.90%

結論

  • 幅を徐々に縮小する方が良い
  • 後半の層は情報が集約されるため、少ないニューロンで十分

実験3: 学習率と安定性

問題

  • lr=1.0: Epoch 3で崩壊(73.65%→30%)
  • lr=0.5: Epoch 6で崩壊

解決策: 学習率減衰

lr = max(0.05, 0.3 * 0.95^(epoch-1))

結果

設定 最高精度 安定性
lr=1.0 固定 73.65% ❌ Epoch 3で崩壊
lr=0.5→decay 74.35% ❌ Epoch 6で崩壊
lr=0.3→decay(0.95) 79.90% ✅ 安定

学習曲線(lr=0.3, decay=0.95)

Epoch  1: 48.90%
Epoch  5: 72.30%
Epoch 10: 78.20%
Epoch 15: 79.90% ← ピーク
Epoch 17: 79.35%
Epoch 18: 78.85% ← プラトー開始

実験4: ED + BP ハイブリッド

アイデア

  • 前半8層: ED法で学習(粗い特徴抽出)
  • 後半2層: BP法で学習(精密な分類)

実装

  • edla_hybrid.cpp を作成
  • ED層とBP層を直列接続

結果

  • うまく機能しなかった

原因分析

  1. 重みの構造が異なる

    • ED法: 4つの符号制約付き重み行列(pp, pn, np, nn)
    • BP法: 1つの制約なし重み行列
  2. 出力の意味が異なる

    • ED層: 正負を分離した表現
    • BP層: 通常の実数値特徴を期待
  3. 更新方向が競合

    • ED法: sign(w)方向に押す
    • BP法: 勾配方向に引く
    • 互いに干渉する可能性

結論

ED法とBP法のパラメータは「意味が違いすぎる」ため、 同一ネットワーク内での直接結合は困難(「油と水」)


他手法との比較

MNIST精度ランキング

手法 精度 備考
SOTA (CNN + Ensemble) 99.87% 最先端
CNN (LeNet-5) 99.0% 古典的
Forward-Forward ~98.5% BP不使用
MLP (BP) 98.4% 3層1000ニューロン
SVM 98.6% 非NN
ED法(本実験) 79.90% -
ランダム 10% ベースライン

位置づけ

  • ED法は精度では他手法に遠く及ばない(約20%の差)
  • しかし、計算の単純さ・メモリ効率では優位性あり

学習率微調整の見込み

予測

改善項目 見込み精度
学習率微調整のみ 80.5% ~ 82.0% (+0.6~2.1%)
+データ増加(60k) 82% ~ 85%
+ネットワーク拡大 84% ~ 87%
全部合わせて 85% ~ 88%

試すべきパラメータ

  • 初期学習率: 0.25, 0.35
  • 減衰率: 0.93, 0.97
  • 最小学習率: 0.08, 0.10
  • ウォームアップ: 3エポック

実装ファイル一覧

ファイル 用途 Phase
ed_entropy.py Python版EDLA(エントロピー調整) 1
ed_mnist.py MNIST用Python実装 1
ed_network.cpp C++版基本実装 1
edla_paper.cpp 論文準拠実装(複数活性化関数対応) 2, 3
edla_residual.cpp Gated EDLA実験 5
edla_residual2.cpp Gated EDLA改良版 5
edla_identity.cpp Identity経路導入版 6
edla_deep.cpp 深層ネットワーク + Identity配置実験 7, 8
cifar10_compare.cpp CIFAR-10比較実験(EDLA vs LGN) 4
edla_hybrid.cpp ED+BPハイブリッド(失敗) -

今後の方向性

ED法を続ける場合

  1. データ量増加: 10,000 → 60,000枚
  2. ネットワーク幅拡大: 1.5倍(192→144→108→...)
  3. 学習率スケジュール: Cosine Annealing, Cyclic LR

別アプローチ

  1. 蒸留方式: ED出力を教師としてBPネットを別に学習
  2. アンサンブル: ED分類器とBP分類器を投票で統合
  3. 別タスク: 異常検知、強化学習など精度が絶対でない領域

主な知見

時系列で得られた知見

  1. sign(w)の重要性(Phase 2)

    • 公式実装に delta *= sign(w) がある
    • これなしでは収束が不安定
  2. 活性化関数の選択(Phase 3)

    • tanh(a/5) が最良(勾配2.3倍)
    • softmaxはP/N構造と相性悪い
  3. 深いネットワークとIdentity(Phase 6-8)

    • 3層ではIdentity不要
    • 10層以上ではIdentity必須
    • 最終層はIdentity除外が最適
  4. Identity配置の原則

    • 入力側の層(1-5): 必須
    • 中間層(6-9): あった方が良い
    • 最終層(10): 不要(むしろ有害)
  5. 学習率減衰が安定性の鍵

    • lr=0.3, decay=0.95 が最適
    • 高すぎると崩壊、低すぎると学習しない
  6. ED法とBP法の直接結合は困難

    • パラメータの意味が根本的に異なる
    • ハイブリッドは別の工夫が必要
  7. ED法の限界と利点

    • 精度ではBP系手法に劣る(~20%差)
    • 計算効率・メモリ効率では優位

実験環境

  • OS: macOS
  • コンパイラ: g++ (C++17)
  • 最適化: -O3
  • データセット: MNIST (10,000訓練 / 2,000テスト)
  • 乱数シード: 42

最終更新: 2026年1月4日

2025年6月23日月曜日

agent が pdf を読めない

今まで Visual Studio 2022 の github copilot の chat モードでAIと戯れていたんですが、 Visual studio code を使うと、github copilot の agent モードというのがあって、こいつ、Visual Studio code で開いたプロジェクトディレクトリ下なら、トランザクション操作付きでファイルを読み書きできると知りました(今まで、全く気が付いていませんでした)。

agent にお願いしたら、markdown 形式のテキストファイルなら、がんがん書き込んでくれます。
ファイルを書き込むためには、最初 mcp-filesystem が必要だと思ってたんで、びっくり。
こちらで実行ボタンを押す必要がありますが、agent にバッチファイルを書かせて、バッチファイルを実行させる事もできます。
調子こいて、Android Studio でビルドしたエラー switch 文を if文に修正する仕事を、Visual Studio code 上で agent に任せて仕事をさせました。
そしたら、ちゃんと修正してくれる。
ただし、書き換えた中身をチェックしないと、前後の id とかいう変数を勝手に if文に組み込んで書き換えようとしたり、時々、とんでもない間違いをします
5000行あるファイルの修正では、
  foo = hoge.bar();
という同じ内容が記載されている行間をバッサリと削除してくれたりもしました。
ファイルが大きいと、エラーメッセージで指摘されている行うよりも下の箇所を修正しようとしたりしまして、「当該箇所より40行ほど上の箇所」とエージェントに指示しないと正しい箇所を修正してくれない時もありました。

まあ、そんな感じでagent使えるじゃん!と、気をよくしてPDFから処理をさせようとしたら、pdfが読めない
先日 markdown ファイルを印刷したいと思って、pandoc を入れました。
PDFを直接読んでくれたら楽なんですけど、まだ、そこまでAIは学習していないようで、mcp-server で pdf を読めるものを試してみましたが、今一つ
ぼやいてたら、ツールの変換内容によって、ちゃんと文字列として変換できない場合もあり、そんな時は精度が上がらないと教えていただきました

じゃ、pdf に変換は? pandoc input.pdf -o output.md でOKかと思いきや、日本語関係でうまく行かない
で、こちらを試してみたんですが、これも日本語フォントがダメでうまく動かない
という事で、結局、こちらを参考に書きました

ほんと、やっつけです。ごめんなさい
pip install pymupdf4llm
して、下記 pdf2md.py を実行します。
C:\doc>python pdf2md.py
または、
C:\doc>python pdf2md.py hoge.pdf
以下、pdf2md.py です。
import os
import re
import sys
import glob
import pymupdf4llm
import pathlib


def extract_text_from_pdf(pdf_file):
    return pymupdf4llm.to_markdown(pdf_file)


def convert_pdf_to_marp(pdf_file):
    base_name = os.path.splitext(pdf_file)[0]
    md_file = f"{base_name}.md"

    try:
        # PDFからテキストを抽出
        extracted_text = extract_text_from_pdf(pdf_file)
        
        # 変更を保存
        with open(md_file, "w", encoding="utf-8") as file:
            file.write(extracted_text)

        print(f"Converted {pdf_file} to {md_file}")
    except Exception as e:
        print(f"Unexpected error occurred while processing {pdf_file}: {e}")

def main():
    if len(sys.argv) > 1:
        pdf_files = sys.argv[1:]
    else:
        pdf_files = glob.glob("*.pdf")
    
    if not pdf_files:
        print("No PDF files found or specified.")
        return

    for file in pdf_files:
        if os.path.isfile(file) and file.lower().endswith('.pdf'):
            convert_pdf_to_marp(file)
        else:
            print(f"Skipping {file}: Not a valid PDF file.")

if __name__ == "__main__":
    main()

2025年6月9日月曜日

Gradle 8.10.0 への苦難の記録(1)

Android の Project を Build しなくてはならなくなりました。気が重たい。Gradle のバージョンが上がると、必ずビルドが通らなくなります。今回も鬼のように問題が噴出して対応に1週間以上の時間を取られました。過去も大抵、数日から1週間以上の時間を要しました。

 なぜ、こんなに時間を取られるかというと、gradle の api が高速に移動するのでエンドユーザがバージョン毎にapiの書き換えや対応を強要される事、gradle 自身が変更により毎回バグを混入させる事にあります。 例を挙げれば、targetSdkVersion, compileSdkVersion は deprecate ですなんてメッセージが出て、信用して build.gradle の targetSdkVersion, compileSdkVersion の行を削除すると、Sync Gradle では、targetSdkVersion だか compileSdkVersion だかが見つからないとメッセージを表示して Sync に失敗するという体たらくです。 思いつきで、何でも簡単に変更しすぎです。gradle 自身も自身の下した api や property の廃棄や変更についていけてないし、バージョン間の整合も取れないので、ビルドシステム全体として見た場合は、ぐちゃぐちゃでカオスな状況が生じています。

 あなたが、Windows のアプリを開発していて、ある日 CreateFileEx という API名が GenerateFileEx という名前に変更されました。なんてアナウンスを受けたらどうなるか、想像がつきますか? コンパイルオプション /EHsc が急に廃止されて、config.options というファイルに記述しないと動作しなくなったら、どうなるか想像がつきますか?例外を有効にするオプションがデフォルト true から false に変更されたら、どうなるか想像がつきますか?

gradle では、それが日常的に病的に起こります。数か月前までビルドが通っていたはずなのに、ビルドが壊れて動かなくなるんです。... was moved. ... wad deprecated. ... was deprecated. ... was moved. まぁ、まだ moved や deprecated を表示してくれるようになっただけでも有り難いんですけどね。以前はエラーメッセージを検索して StackOverflowを見て、deprecated だった事を知って対策してましたから。ただ、エラーメッセージを検索してネットの情報を調べないと対処方法がわからないのは、相変わらずです。

  Copilot くんにお願いして名称の変更に限定して、変更をまとめてもらいました。
私の記憶が確かならば、この表は氷山の一角です。taskを使用している場合、taskの依存関係を記述しなければならないのですが、ヒステリックにtask名が変更されて、その都度、プロジェクトの修正を余儀なくされました。ある機能のtask名は、3回以上名前が変更されました。debugは間違ってるreleaseだ、いやJarじゃないRFileだといった具合に…。ころころと名前変えられたら、プロジェクトファイルも全部書き換えないと動きません。ファイルをコピーする関数名も変更されましたし、gradleの変更頻度は病的で、まともではありません。担当者のお気持ちで担当者がその名前が気に入らなければ名前が変更されます。エンドユーザは、気まぐれで変更された名前に合わせるようプロジェクトを修正しなければなりません。毎回毎回、山のように修正を余儀なくされます。


イライラはピークに達するものの落ち着いて、次のような書き込みをする決断に至ります。
Why does gradle deprecate and remove things so often? に象徴されていると思います。
 話を戻します。

 今回ビルドするプロジェクトは、legacy な Java による Application の話であり、KotlinでもGroovyでもありません。AndroidStudioにより作成できるプロジェクトは、Kotlin と Groovy の2択でlegacyのプロジェクトという選択肢が無いのも頭の痛い問題です。KotlinでもGroovyでもないのに、プロジェクトのバージョンアップ中に kotlin や groovy といったエラーを見かけ、これらのエラーを見る度に一体何のエラーなのか意味不明で頭の痛い思いをしました。

ライブラリのビルド
 gradle-3.5-all.zip と gradle-3.5.2
 gradle-7.5-all.zip と gradle-7.4.2 
以上の組み合わせから 
 gradle-8.14.1-bin.zip と gradle-10.0.0
へと移行する形になりました。ファイル名も -all から -bin に変更されています。なんでも allはファイルサイズがデカくなるからなんだそうです。gradle-8.14.0-all.zip ってやってエラーになるから何事や?と思ったら、知らんがな。いちいち、こんな調子で時間を取られます。
ここを参照すればわかるように私も質問者と同じ感覚です。
尚、試行錯誤している間は gradle-8.14.0-bin.zip しか出ていませんでしたが、途中で8.14.1がリリースされて、まともにビルドできるようになりました。その間はライブラリの依存関係バグがあったのか、何をやってもビルドが通りませんでした。

gradle-8.x になって変更された箇所は

・AndroidManifest.xml から package="com.foo.bar" の属性が削除された事
・build.gradle(app) で android { } の 下に namespace = "com.foo.bar" の記述が必要になった事。

ビルド時にサジェストがあったので、これに関しては比較的親切な対応と言えます。

しかし、こんな変更必要ですか?案の定、この変更によって、package.R.class が出力されないバグが混入し、アプリケーションのビルド時に、package R does not exist. というエラーが発生して、apk が作成できませんでしたが、gradle-8.10.1 で修正されました。gradle に振り回されて修正しても直らない地獄を徘徊しているうちに修正されました。

今回も deprecated の嵐で、バージョンアップすると、ことごとくビルドが失敗しました。何箇所も問題が発生し、たくさんの修正が必要でした。ライブラリをまともにビルドできるまでに4日以上費やしました。この他に android API の deprecated 対応にも4日以上の時間を取られました。

まずは、JDKのバージョンの整理が必要でした。今までは、なんとなくビルドできていましたが、そうはいきませんでした。
gradle は JDK のバージョンに煩い。jdk-11 じゃないと動きません。jdk-17 じゃないと動きません。jdk-18じゃないと動きません。と、色々注文が多いです。開発環境では、Oracleがインストールされていると、oracle が javac に対して path を通していたり、Visual Studio が javac に path を通していたり、Android Studio は SDK に設定されている JDKを使用しようと毎回 javac をリセットしたり、gradle は GRADLE 専用の JDK の PATH を設定してたりします。バージョンアップ中に GRADLE_JDK_LOCAL_PATH という名前への変更が行われたので、とにかく専用のPATHが設定されているという事です。これらpathの仕様は担当者の気まぐれで変更される恐れがあります。ライブラリのビルドにはJDK-17、アプリのビルドにはJDK-11を使用しました。プロジェクト>設定>検索>gradle で、モジュール>gradle の JDKの指定と、ウィンドウズの環境変数 path を制御する事で自分は対応しました。これに関しては gradleが JDKxxでは動作しないとメッセージを表示してくれるので対応しやすいです。

いろいろありすぎて、書ききれません…続きます。

android ProgressBar が deprecated

ProgressBar が deprecated になっていた。
ダイアログを表示すると、他の操作ができないから埋込レイアウトにしとけ
というのがGoogle先生の指示なんだけど、アプリケーションのステート的には、このタスクが終了しないと、どのみち次のオペレーションはできないので、埋込にする意味がないと思う。
処理の途中でアプリを切り替えられると、onDestroyが呼ばれるので、それも困る
そんな時はやり直しですしおすし。
それにダイアログを表示していてもアプリは切り替えられる
埋め込んだところで、大差がないと思うのである。
で、いちいち埋込の画面を作成するのは、大変なのでプログレスダイアログは欲しい。
package com.foo.Helper;

import android.app.Dialog;
import android.content.DialogInterface;
import android.os.Bundle;
import android.content.Context;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AlertDialog;
import androidx.fragment.app.DialogFragment;
import android.widget.ProgressBar;

import com.foo.helper.R;

public class CommonProgressDialog extends DialogFragment {

	private final Context context_;

	private final String title_;

	private final String message_;

	private AlertDialog.Builder builder_;

	private AlertDialog dialog_;

	private ProgressBar progressBar_;

	@NonNull
	@Override
	public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) {
		dialog_ = builder_.create();
		return dialog_;
	}

	private void BuildBase(final Context context, final String title, final String message) {
		builder_ = new AlertDialog.Builder(context)
				.setView(R.layout.progress)
				.setTitle(title)
				.setCancelable(false)
				//.setCanceledOnTouchOutside(false)
				.setMessage(message);
	}

	public CommonProgressDialog(final Context context, final String title, final String message) {
		this.context_ = context;
		this.title_ = title;
		this.message_ = message;
		BuildBase(context, title, message);
	}

	/// 中止ボタンを設置する
	public void setNegative(final DialogInterface.OnClickListener l) {
		if( dialog_ == null ) return;
		dialog_.setButton(DialogInterface.BUTTON_NEGATIVE, getString(android.R.string.cancel), l);
	}

	public void setMax(int maxValue) {
		if( dialog_ == null) return;
		ProgressBar pb = dialog_.findViewById(R.id.progress_bar);
		if( pb != null ) {
			pb.setMax(maxValue);
		}
	}

	public void setProgress(int progress) {
		if( dialog_ == null) return;
		ProgressBar pb = dialog_.findViewById(R.id.progress_bar);
		if( pb != null ) {
			pb.setProgress(progress);
		}
	}


	public void show() {
		if( dialog_ == null) {
			dialog_ = builder_.create();
		}
		dialog_.show();
	}

	public void dismiss() {
		dialog_.dismiss();
		dialog_ = null;
	}

	/*
	public CommonProgressDialog(Context context) {
		super(context);
		setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
	}
	public CommonProgressDialog(Context context, int theme) {
		super(context,theme);
		setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
	}

	 */
}


progress.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="horizontal"
    android:padding="20dp">
    <ProgressBar
        android:id="@+id/progress_bar"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_weight="1" />

    <TextView
        android:layout_width="0dp"
        android:layout_height="match_parent"
        android:layout_weight="4"
        android:gravity="center"
        android:text="Please wait! This may take a moment." />
</LinearLayout>

2024年1月5日金曜日

WPF ComboBoxにおけるIMEの制御

いやー、WPFのTextBoxならIMEの制御を設定できるんですけど、ComboBox のEditableをFalseのままで、IsTextSearchEnabled="True"とか設定して、項目を[コード]:[名前]にした時にIMEをオフにしないと、コード入力が効かないんですわ。
いろいろ検索してみたんですが、まともなのが無かったんで、書きましたよ。はい。
public class Util
{

    [DllImport("imm32.dll")]
    public static extern IntPtr ImmGetContext(IntPtr hWnd);

    [DllImport("imm32.dll")]
    public static extern Boolean ImmReleaseContext(IntPtr hWnd);

    [DllImport("imm32.dll")]
    public static extern Boolean ImmSetConversionStatus(IntPtr hIMC, Int32 fdwConversion, Int32 fdwSentence);

    [DllImport("imm32.dll")]
    public static extern Boolean ImmSetOpenStatus(IntPtr hIMC, Int32 fOpen);

    [DllImport("imm32.dll")]
    public static extern Int32 ImmGetOpenStatus(IntPtr hIMC);

    [DllImport("imm32.dll")]
    public static extern Int32 ImmAssociateContext(IntPtr hWnd, Int32 hIMC);

    const Int32 IME_CMODE_ALPHANUMERIC = 0x0000;
    const Int32 IME_CMODE_NATIVE = 0x0001;
    const Int32 IME_CMODE_CHINESE = IME_CMODE_NATIVE;
    const Int32 IME_CMODE_HANGUL = IME_CMODE_NATIVE;
    const Int32 IME_CMODE_JAPANESE = IME_CMODE_NATIVE;
    const Int32 IME_CMODE_KATAKANA = 0x0002;  // only effect under IME_CMODE_NATIVE
    const Int32 IME_CMODE_LANGUAGE = 0x0003;
    const Int32 IME_CMODE_FULLSHAPE = 0x0008;
    const Int32 IME_CMODE_ROMAN = 0x0010;
    const Int32 IME_CMODE_CHARCODE = 0x0020;
    const Int32 IME_CMODE_HANJACONVERT = 0x0040;
    const Int32 IME_CMODE_NATIVESYMBOL = 0x0080;

    const Int32 IME_CMODE_SOFTKBD = 0x0080;
    const Int32 IME_CMODE_NOCONVERSION = 0x0100;
    const Int32 IME_CMODE_EUDC = 0x0200;
    const Int32 IME_CMODE_SYMBOL = 0x0400;
    const Int32 IME_CMODE_FIXED = 0x0800;


    const Int32 IME_CONFIG_GENERAL = 1;
    const Int32 IME_CONFIG_REGISTERWORD = 2;
    const Int32 IME_CONFIG_SELECTDICTIONARY = 3;

    const Int32 IME_SMODE_NONE = 0x0; // センテンスに関する情報はなし
    const Int32 IME_SMODE_PLURALCLAUSE = 0x01; // 変換のための複文情報を使う
    const Int32 IME_SMODE_SINGLECONVERT = 0x02; // 単漢字変換する
    const Int32 IME_SMODE_AUTOMATIC = 0x04; // 自動モードで変換
    const Int32 IME_SMODE_PHRASEPREDICT = 0x08; // 次の文字を予想するためにフレーズ情報を使う
    const Int32 IME_SMODE_CONVERSATION = 0x0010;

    /// <summary>
    /// IMEモード
    /// </summary>
    public enum ImeMode
    {
        Off = 1, // IME オフ
        Hiragana = 2, // 全角ひらがな
        Katakana = 3, // 全角カタカナ
        HalfKatakana = 4, // 半角カタカナ
        Alpha = 5, // 全角英数
        HalfAlpha = 6, // 半角英数
    };


    /// <summary>
    /// IMEを制御する。WPF ComboBox 等の一部コントロールでは、IMEの制御ができない。そのための対応。
    /// TextBox等では、下記で制御できる
    /// <TextBox InputMethod.IsInputMethodEnabled="False"/> IME無効
    /// <TextBox InputMethod.PreferredImeState="On" InputMethod.PreferredImeConversionMode="FullShape,Native"/> ひらがな
    /// <TextBox InputMethod.PreferredImeState="On" InputMethod.PreferredImeConversionMode="Katakana,FullShape"/> カタカナ
    /// <TextBox InputMethod.PreferredImeState="On" InputMethod.PreferredImeConversionMode="Katakana,Native"/> 半角カタカナ
    /// <TextBox InputMethod.PreferredImeState="On" InputMethod.PreferredImeConversionMode="Alphanumeric,FullShape"/> 全角英数
    /// <TextBox InputMethod.PreferredImeState="On" InputMethod.PreferredImeConversionMode="Alphanumeric"/> 半角英数
    /// </summary>
    /// <param name="ctrl"></param>
    /// <param name="mode"></param>
    public static void SetImeMode(Control ctrl, ImeMode mode)
    {
        IntPtr handle = IntPtr.Zero;
        HwndSource hwnd = PresentationSource.FromVisual(ctrl) as HwndSource;
        if (hwnd != null)
        {
            handle = hwnd.Handle;
        }
        IntPtr himc = ImmGetContext(handle);
        Int32 dwConversion = 0;

        try
        {
            if( mode == ImeMode.Off)
            {
                if (ImmGetOpenStatus(himc) != 0)
                {
                    ImmSetOpenStatus(himc, 0);
                }
                return;
            } else
            {
                if( ImmGetOpenStatus(himc) == 0)
                {
                    ImmSetOpenStatus(himc, 1);
                }
            }
            switch (mode)
            {
                case ImeMode.Hiragana:
                    ImmSetConversionStatus(himc, IME_CMODE_FULLSHAPE | IME_CMODE_JAPANESE, IME_SMODE_AUTOMATIC);
                    break;
                case ImeMode.Katakana:
                    ImmSetConversionStatus(himc, IME_CMODE_FULLSHAPE | IME_CMODE_LANGUAGE | IME_CMODE_KATAKANA, IME_SMODE_AUTOMATIC);
                    break;
                case ImeMode.HalfKatakana:
                    ImmSetConversionStatus(himc, IME_CMODE_LANGUAGE | IME_CMODE_KATAKANA, IME_SMODE_AUTOMATIC);
                    break;
                case ImeMode.Alpha:
                    ImmSetConversionStatus(himc, IME_CMODE_FULLSHAPE | IME_CMODE_ROMAN, IME_SMODE_AUTOMATIC);
                    break;
                case ImeMode.HalfAlpha:
                    ImmSetConversionStatus(himc, IME_CMODE_ROMAN, IME_SMODE_AUTOMATIC);
                    break;
            }
        }
        finally
        {
            ImmReleaseContext(handle);
        }

    }

}
使い方は、GotFocusイベントをハンドリングして
   private void FooComboBox_GotFocus(object sender, RoutedEventArgs e)
   {
       Util.SetImeMode(sender as ComboBox, Util.ImeMode.Off);
   }
という感じ

2023年9月22日金曜日

コマンドプロンプトとCURLのエスケープ

コマンドプロンプトのエスケープは、\ と " がエスケープ処理の対象となります。

CURL のエスケープは、\ { } | がエスケープ処理の対象となります。

\ をエスケープすると、コマンドプロンプト用のエスケープとCURL用のエスケープが複合し、\\\\という形になります。

ふむふむ…
ふむふむ…
ふむふむふむふむ?…

コマンドプロンプトは \\\\ を " として取り扱います…

(`□′)╯┴┴

2023年6月23日金曜日

atldbcli.h のバグを踏んだ

atldbcli.h の CCommand<CDynamicStringAccessor> を利用していたコードが null pointer exception で落ちるようになった。
ほぇ?(#^ω^)ピキピキ

デバッガでトレースしていくと、m_pAccessor のメンバが nullptr で初期化されないまま放置されていた。
で、Accessor クラスは、どこに行った?
おそらく、class template refactoring により、自分自身に継承する形になったようです。

なので、CCommand<CDynamicStringAccessor>::SetAccessor 関数を使用して自分自身を設定する必要がありました。
せっかくなので、使い方のサンプル込みで、紹介です。
#include <iostream>
#include <objbase.h>
#include <atldbcli.h>

int main(int argc, char* argv[] )
{
  HRESULT hr = CoInitialize(NULL );
  if(FAILED(hr)) {
    std::cerr << "Fail to Initialize COM:" << hr << std::endl;
  }

  CDataSource ds;

  // OLEDBによるODBC接続文字列
  hr = ds.OpenFromInitializationString(
     L"Provider=MSDASQL.1;Password=password;"
     L"Persist Security Info=True;"
     L"User ID=username;"
     L"Data Source=odbc_data_source_name;"
     L"Extended Properties=\"DSN=data_source_name;UID=username;PWD=password\""
  );

  CSession ss;

  CCommand<CDynamicStringAccessor> rs;

  hr = ss.Open(ds );
  hr = rs.Open(ss, "select * from hoge" );

  // CCommand<typename T>::m_pAccessor を初期化するコードが欠落しているため
  // 自分自身を設定する必要がある(どこかのタイミングで混入したバグ対応)
  // 2023/7/13 修正されたような気がしますが、下記コードは正しくなくなった感じがします。
  // 仕様がよくわかりません。
  rs.SetAccessor(&rs);

  hr = rs.MoveNext();
  if( FAILED(hr) || hr == DB_S_ENDOFROWSET ) {
    std::cerr << "Fail to MoveNext: " << hr << std::endl;
  }
  // Get the column information
  ULONG ulColumns = rs.GetColumnCount();
  
  DBTYPE dbtype;
  rs.GetColumnType( 1, &dbtype );
  dbtype &= 0x3FFF;	// DBTYPE_BYREF を除去	
  if( 
       dbtype == DBTYPE_I4
    || dbtype == DBTYPE_UI4
    || dbtype == DBTYPE_R8
    || dbtype == DBTYPE_STR
    || dbtype == DBTYPE_WSTR
    || dbtype == DBTYPE_DECIMAL
    || dbtype == DBTYPE_VARNUMERIC
    || dbtype == DBTYPE_NUMERIC
  ) {
    // よくあるフィールド型
  } else {
    // 対応可能か検討が必要なフィールド型
    std::cerr << "Un supported dbtype: " << dbtype << std::endl;
  }

  int nMax = 10;
  while(nMax) {
    std::string strData;
    for( DBORDINAL col = 1; col <= ulColumns; col++ ) {
      DBSTATUS dStatus;
      DBLENGTH nLength;
      rs.GetStatus( col, &dStatus );
      rs.GetLength( col, &nLength );
      if( col > 1 ) {
        std::cout << ", ";
      }
      if( FAILED( dStatus ) ) {
        std::cout << "<<ERROR>>";
      } else if( dStatus == DBSTATUS_S_ISNULL ) {
        std::cout << "<<NULL>>";
      } else {
        std::cout << rs.GetString(col);
      }
    }
    std::cout << std::endl;
    hr = rs.MoveNext();
    if( FAILED(hr) || hr == DB_S_ENDOFROWSET )  break;
    --nMax;
  }

  rs.Close();
  ss.Close();
  ds.Close();
  CoUninitialize();

  return 0;
}
2023/07/13 追記: バグは修正されたのか、仕様が変わったのか、よくわからない状況になりました。

2022年9月22日木曜日

C#の Properties.Settings.Default が実際にどこに保存されるか忘備録

C# の Properties.Settings.Default 言語機構として用意していただけるのは有難いのですが、ここに環境系の設定を書いておくとインストールされたマシンの環境によって設定を変えたいとか出てくるんですよ。
ところが、こいつ、一体どこのファイルの設定を見てるねん???(#^ω^)ピキピキ
ってキレそうになる仕様なわけです。

という事で、.Net の古いバージョンでは、下記コードで導き出される場所に初期値が書かれています。
   System.Reflection.Assembly asm =System.Reflection.Assembly.GetExecutingAssembly();
   textBox1.Text = asm.GetName().Version.ToString(); // バージョン番号を取り出す
   // config のデフォルト値が記述されたファイルの場所
   textBox2.Text =  ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.PerUserRoamingAndLocal).FilePath;
.Net の仕様が変わり、現在は
   // Foo.exe.config 場合によっては Foo.dll.config 
   // ここは作成したプロジェクトの実行物の名前に依存します。
   textBox2.Text =  Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location) + "\\Foo.exe.config";
はてさて、ではコードで、これらの要素を変更した値はどこに書き込まれるのか?
いつも大変お世話になります。Stackoverflow さまによると
Where are the Properties.Settings.Default stored? によると
c:\users\USER\AppData\Local\COMPANY\APPLICATION.exe_Url_LOOKSLIKESOMEKINDOFHASH\VERSION\user.config
なんだそうで、How know LOOKSLIKESOMEKINDOFHASH? とは、ごもっともな質問。私も知りたい。
これも Stackoverflow にありました。
How to get hash value in user.config path?
(ノ`□´)ノ⌒┻━┻

2022年8月24日水曜日

IISのResponse Header を無効化 忘備録

PowerShell で、IIS の Server ヘッダや、X-ASPNET-Version ヘッダを消去、TRACEメソッドの無効化がしたくて、いろいろ試してみたが、やれ、静的コンテンツではなく、Exe経由のコンテンツのヘッダが消えないとか、ひどい目にあったので忘備録。

IIS ReWrite モジュールが必要です。

管理者権限で、下記スクリプトを実行します。
スクリプトだけでは完結しないので、コメントも読んでください
#
# PowerShell は BOM付き UTF-8 のエンコーディングで保存する事
#  さもなくば、「文字列に終端記号" がありません」という
#  エラーに悩まされます。(BOMの仕様必要?)
#
#
# X-ASPNET-VERSION を無効にする送信書き換えルールを
#   指定された PSPath に追加する
#
function global:AddXASPNetVersionRule {
  param ( $PSPath )

  echo "$PSPath の X-ASPNET-VERSION ヘッダを無効にします"

  $chk = Get-WebConfigurationProperty `
    -Filter "//rewrite/outboundRules/rule[@name='REMOVE_X-ASPNET-VERSION']" `
    -PSPath $PSPath `
    -Name name 

  if( $chk -eq $null ) {
    Add-WebConfigurationProperty `
      -Filter "//rewrite/outboundRules" `
      -PSPath $PSPath `
      -Name "Collection" `
      -Value @{name="REMOVE_X-ASPNET-VERSION"}

    Set-WebConfiguration `
      -Filter "//rewrite/outboundRules/rule[@name='REMOVE_X-ASPNET-VERSION']/match" `
      -PSPath $PSPath `
      -Value @{serverVariable="RESPONSE_X-ASPNET-VERSION";pattern=".+"}

    Set-WebConfiguration `
      -Filter "//rewrite/outboundRules/rule[@name='REMOVE_X-ASPNET-VERSION']/action" `
      -PSPath $PSPath `
      -Value @{type="Rewrite"}
    echo "  ...処理完了..."
  } else {
    echo "  ...処理済み skip..."
  }
}

#
# SERVER-VERSION を無効にする送信書き換えルールを
#   指定された PSPath に追加する
#
function global:AddServerVersionRule {
  param ( $PSPath )

  echo "$PSPath の Server ヘッダを無効にします"

  $chk = Get-WebConfigurationProperty `
    -Filter "//rewrite/outboundRules/rule[@name='REMOVE_SERVER-VERSION']" `
    -PSPath $PSPath `
    -Name name

  if( $chk -eq $null ) {
    Add-WebConfigurationProperty `
      -Filter "//rewrite/outboundRules" `
      -PSPath $PSPath `
      -Name "Collection" `
      -Value @{name="REMOVE_SERVER-VERSION"}

    Set-WebConfiguration `
      -Filter "//rewrite/outboundRules/rule[@name='REMOVE_SERVER-VERSION']/match" `
      -PSPath $PSPath `
      -Value @{serverVariable="RESPONSE_SERVER";pattern=".+"}
    
    Set-WebConfiguration `
      -Filter "//rewrite/outboundRules/rule[@name='REMOVE_SERVER-VERSION']/action" `
      -PSPath $PSPath `
      -Value @{type="Rewrite"}
    echo "  ...処理完了..."
  } else {
    echo "  ...処理済み skip..."
  }
}

#
# サーバ変数 RESPONSE_X-ASPNET-VERSION を追加する
#     X-ASPNET-VERSION: ヘッダとして利用される変数
#
$asv = Get-WebConfigurationProperty `
       -filter "//rewrite/allowedServerVariables/add[@name='RESPONSE_X-ASPNET-VERSION']"`
       -name name

if ($asv -eq $null)
{
  echo "サーバ変数 RESPONSE_X-ASPNET-VERSION を追加します"
  Add-WebConfigurationProperty `
    -Filter "//rewrite/allowedServerVariables" `
    -PSPath "IIS:\" `
    -Name "Collection" `
    -Value @{name="RESPONSE_X-ASPNET-VERSION"}

}

#
# サーバ変数 RESPONSE_SERVER を追加する
#      SERVER: ヘッダとして利用される変数
#
$asv2 = Get-WebConfigurationProperty `
       -filter "//rewrite/allowedServerVariables/add[@name='RESPONSE_SERVER']"`
       -name name

if ($asv2 -eq $null)
{
  echo "サーバ変数 RESPONSE_SERVER を追加します"

  Add-WebConfigurationProperty `
    -Filter "//rewrite/allowedServerVariables" `
    -PSPath "IIS:\" `
    -Name "Collection" `
    -Value @{name="RESPONSE_SERVER"}
}

#
# X-ASPNET-VERSION ヘッダを無効にする
#

  AddXASPNetVersionRule( "IIS:\" )

  AddXASPNetVersionRule( "IIS:\sites\Default Web Site" )

  # IIS-Admin上で手動追加するとエラーにならないが、コードで
  # 実行するとエラーになる不思議仕様
  #AddXASPNetVersionRule( "IIS:\sites\Default Web Site\foo" )

  # Application で無効化するために必要?
  # 仕様が不明すぎて草
  AddXASPNetVersionRule( "MACHINE/WEBROOT/APPHOST" )

#
# SERVER-VERSION ヘッダを無効にする
#
  AddServerVersionRule( "IIS:\" )

  AddServerVersionRule( "IIS:\sites\Default Web Site" )

  # IIS-Admin上で手動追加するとエラーにならないが、コードで
  # 実行するとエラーになる不思議仕様
  #AddServerVersionRule( "IIS:\sites\Default Web Site\foo" )

  # Application で無効化するために必要?
  # 仕様が不明すぎて草
  AddServerVersionRule( "MACHINE/WEBROOT/APPHOST" )


#
# IISの Server Header を無効化します。
#   静的コンテンツだけに効果があるため気休め
#
  echo "  IIS のサーバヘッダを無効化します  "
  echo "   ※ Application に対しては効果がありません"
  Set-WebConfigurationProperty `
    -pspath 'MACHINE/WEBROOT/APPHOST' `
    -filter "system.webServer/security/requestFiltering" `
    -name "removeServerHeader" `
    -value "True"


#
# IISの TRACE を無効化します。
#   静的コンテンツだけに効果があるため気休め
#
  echo "  IIS のTRACE メソッドを無効化します  "
  echo "   ※ Application に対しては効果がありません"
  echo "   コマンドで対応できる範囲は静的コンテンツのみです"
  echo "   要求フィルター>HTTP動詞>Trace;false をIIS設定から手動で行う必要があります"
#
#  要求フィルター HTTP動詞 TRACE をfalseにする
#
# エラーで動作しなくなるので、コマンドによる、このやり方はダメ
#  Add-WebConfigurationProperty `
#    -pspath "IIS:\" `
#    -filter "/system.webServer/security/requestFiltering/verbs" `
#    -name '.' -value @{verb='TRACE';allowed='False'}
#
  $colls = Get-IISConfigSection -CommitPath 'Default Web Site' -SectionPath 'system.webServer/security/requestFiltering' 
    #echo "colls : $colls"
  $verbs = Get-IISConfigCollection -ConfigElement $colls -CollectionName 'verbs'
    #echo "verbs : $verbs"
  try {
    New-IISConfigCollectionElement -ConfigCollection $verbs -ConfigAttribute @{ 'verb'='TRACE';'allowed'=$false }
    Stop-IISCommitDelay
  } catch {
    # 取得して、値を設定するコードが動作しない。(関数の仕様がわからない)
    #  echo " set trace "
    #  $trace = Get-IISConfigCollectionElement -ConfigCollection $verbs -ConfigAttribute @{ 'verb'='TRACE' }
    #  echo "trace : $trace.allowed $trace.verb"
    #  Set-IISConfigAttributeValue -ConfigElement $trace -AttributeName 'allowed' -AttributeValue $false
  }

#
# Web設定を無効にしてもアプリケーションからのヘッダが無効になるとは言ってない
# 気休め(無い方がまし)
#
#$rule3 = Get-WebConfigurationProperty `
#          -filter "/system.webServer/httpProtocol/customHeaders/add[@name='X-Powerd-By']" `
#          -Name name
#
#if ($rule2 -eq $null)
#{
#  echo "clear X-Powered-By"
#  Clear-WebConfiguration "/system.webServer/httpProtocol/customHeaders/add[@name='X-Powered-By']"
#}

2022年7月29日金曜日

apache maven で Jni DLL を梱包・備忘録

肝は、resources セクションの resource に対して、filtering=false を指定する事。
この filtering を true にすると、対象リソースをテキストファイルと見なして、文字列の置換が実行されてしまう。
dll ファイルは、バイナリで、utf-8の文字コード規則に収まらないから、false にしないとエラーになってしまう。

後は、Bundle-NativeCode に dll ファイルを resource セクションで指定したディレクトリからの相対パスで ; により列挙すればOK

  <build>
    <resources>
      <resource>
        <directory>src/main/resources</directory>
        <filtering>true</filtering>
      </resource>
      <resource>
        <directory>src/main/extlib</directory>
        <filtering>false</filtering>
      </resource>
    </resources>
    <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-compiler-plugin</artifactId>
        <version>${maven.compiler.plugin.version}</version>
        <configuration>
          <source>8</source>
          <target>8</target>
        </configuration>
      </plugin>
      <plugin>
        <groupId>org.apache.felix</groupId>
        <artifactId>maven-bundle-plugin</artifactId>
        <version>${maven.bundle.plugin.version}</version>
        <extensions>true</extensions>
        <configuration>
          <instructions>
            <Build-Number>${buildId}</Build-Number>
            <Bundle-SymbolicName>${project.groupId}.${project.artifactId}</Bundle-SymbolicName>
            <Bundle-ContactAddress>${sdk.contact.address}</Bundle-ContactAddress>
            <Bundle-Version>${project.version}</Bundle-Version>
            <Bundle-NativeCode>native/myJni.dll;native/libssl-1_1-x64.dll;native/zlib1.dll</Bundle-NativeCode>
            <Export-Package/>
            <Private-Package>jp.co.foo.bar</Private-Package>
          </instructions>
        </configuration>
      </plugin>
    </plugins>
  </build>

java のコードで、loadLibraryを記述する


  private static boolean tryLoadLibrary(String libname) {
    try {
      System.loadLibrary(libname);
      return true;
    } catch(Exception err) {
      err.printStackTrace();
    }
    return false;
  }

  // Bundle-NativeCode の dll を読み込む。Platformにより拡張子が異なるため
  // Java のコードでは、.dll .so といった名称まで指定しなくても良い。
  public static void tryLoadLibraries() {
    tryLoadLibrary("native/libssl-1_1-x64");
    tryLoadLibrary("native/zlib1");
    tryLoadLibrary("native/myJni");
  }
  
  // Load JNI
  static {
    // 自動でライブラリがロードされるはずなのだが、
    // この初期化が行われるより先に、JNI の関数が呼ばれる事態が
    // 発生する事がある。よって、クリティカルな場所から重複して
    // tryLoadLibraries をコールしても大丈夫なように実装している。
    tryLoadLibraries();
  }

2022年5月18日水曜日

WIX で VCRuntime のインストール忘備録

そろそろ VCのRUNTIMEをVisual Studio 2019に移行しとかないと、コンパイルできないライブラリが増えてきたかな?と思いまして、2015 から 2019 に引き上げました。
WIXを使ったインストーラで、2019用のVC runtime のマージモジュールを探したが見つかりません。
ほぇ?と思って検索したらマージモジュールを使用したコンポーネントの再配布に書いてありました。

Visual Studio 2019 以降、Visual C++ 再頒布可能ファイルのマージ モジュールは非推奨です
Visual Studio 2019 以降、Visual C++ 再頒布可能ファイルのマージ モジュールは非推奨です
Visual Studio 2019 以降、Visual C++ 再頒布可能ファイルのマージ モジュールは非推奨です
ふぁ???
という事で、VC Runtime がインストールされているかどうかチェックが必要になりました
Wix per user installer to detect the Visual C++ 2015 Redistributableにチェック方法がありました。StackOverflowさまさまです
How to check if the Microsoft Visual C++ Runtime is installedにも詳細が記述されてましたが、こちらだとサービスパックが追加される毎に対応が必要そうで、ちょっと重たい。

という事で、
<Product ...>
  ...
vc runtime 2015 x86 のインストール状況プロパティ
  <!-- C++ 2015 -->
  <Property Id="CPPRUNTIME2015X86" Secure="yes">
    <RegistrySearch Id="mfc140x86_23026" Root="HKLM" Key="SOFTWARE\Classes\Installer\Dependencies\{74d0e5db-b326-4dae-a6b2-445b9de1836e}" Type="raw" />
    <RegistrySearch Id="mfc140x86_24215" Root="HKLM" Key="SOFTWARE\Classes\Installer\Dependencies\{e2803110-78b3-4664-a479-3611a381656a}" Type="raw" />
  </Property>
vc runtime 2017 x86 のインストール状況プロパティ
  <!-- C++ 2017 -->
  <Property Id="CPPRUNTIME2017X86" Secure="yes">
    <RegistrySearch Id="mfc1416x86" Root="HKCR" Key="Installer\Dependencies\VC,redist.x86,x86,14.16,bundle" Type="raw" />
  </Property>
vc runtime 2019 x86 のインストール状況プロパティ
  <!-- C++ 2019 -->
  <Property Id="CPPRUNTIME2019X86" Secure="yes">
    <?foreach CPPRUNTIMEVERSIONPREFIX in 21;22;23;24;25;26;27;28;29;30;31;32;33;34;35;36;37;38;39;40?>
      <RegistrySearch Id="mfc14$(var.CPPRUNTIMEVERSIONPREFIX)x86" Root="HKCR" Key="Installer\Dependencies\VC,redist.x86,x86,14.$(var.CPPRUNTIMEVERSIONPREFIX),bundle" Type="raw" />
    <?endforeach ?>
  </Property>
vc runtime 2019 x86 がインストールされていないと、インストールを中止する条件の追加
  <!-- インストール時にC++ 2019 Runtime(x86) がインストールされているかチェック -->
  <Condition Message="Microsoft Visual C++ 2019 (x86) Redistributable missing">
    <![CDATA[Installed Or CPPRUNTIME2019X86]]>
  </Condition>
vc runtime 2015 x64 のインストール状況プロパティ
  <!-- C++ 2015 -->
  <Property Id="CPPRUNTIME2015X64" Secure="yes">
    <RegistrySearch Id="mfc140x64_23026" Root="HKLM" Key="SOFTWARE\Classes\Installer\Dependencies\{e46eca4f-393b-40df-9f49-076faf788d83}" Type="raw" />
    <RegistrySearch Id="mfc140x64_24215" Root="HKLM" Key="SOFTWARE\Classes\Installer\Dependencies\{d992c12e-cab2-426f-bde3-fb8c53950b0d}" Type="raw" />
  </Property>
vc runtime 2017 x64 のインストール状況プロパティ
  <!-- C++ 2017 -->
  <Property Id="CPPRUNTIME2017X64" Secure="yes">
    <RegistrySearch Id="mfc1416x64" Root="HKCR" Key="Installer\Dependencies\VC,redist.x64,amd64,14.16,bundle" Type="raw" />
  </Property>
vc runtime 2019 x64 のインストール状況プロパティ
  <!-- C++ 2019 -->
  <Property Id="CPPRUNTIME2019X64" Secure="yes">
    <?foreach CPPRUNTIMEVERSIONPREFIX in 21;22;23;24;25;26;27;28;29;30;31;32;33;34;35;36;37;38;39;40?>
      <RegistrySearch Id="mfc14$(var.CPPRUNTIMEVERSIONPREFIX)x64" Root="HKCR" Key="Installer\Dependencies\VC,redist.x64,amd64,14.$(var.CPPRUNTIMEVERSIONPREFIX),bundle" Type="raw" />
    <?endforeach ?>
  </Property>
vc runtime 2019 x64 がインストールされていないと、インストールを中止する条件の追加
  <!-- インストール時にC++ 2019 Runtime(x64) がインストールされているかチェック -->
  <Condition Message="Microsoft Visual C++ 2019 (x64) Redistributable missing">
    <![CDATA[Installed Or CPPRUNTIME2019X64]]>
  </Condition>
vc runtime 2010 x64 のマージモジュールを追加
  <Directory Id="System64Folder">
   <!-- VC100 ATL Runtime -->
   <Merge Id="Microsoft_VC100_ATL_x64.msm" Language="0" DiskId="1" SourceFile="C:/Program Files (x86)/Common Files/Merge Modules/Microsoft_VC100_ATL_x64.msm" />
   <Merge Id="Microsoft_VC100_CRT_x64.msm" Language="0" DiskId="1" SourceFile="C:/Program Files (x86)/Common Files/Merge Modules/Microsoft_VC100_CRT_x64.msm" />
   <Merge Id="Microsoft_VC100_MFCLOC_x64.msm" Language="0" DiskId="1" SourceFile="C:/Program Files (x86)/Common Files/Merge Modules/Microsoft_VC100_MFCLOC_x64.msm" />
   <Merge Id="Microsoft_VC100_MFC_x64.msm" Language="0" DiskId="1" SourceFile="C:/Program Files (x86)/Common Files/Merge Modules/Microsoft_VC100_MFC_x64.msm" />
   <Merge Id="Microsoft_VC100_OpenMP_x64.msm" Language="0" DiskId="1" SourceFile="C:/Program Files (x86)/Common Files/Merge Modules/Microsoft_VC100_OpenMP_x64.msm" />
  </Directory>
vc runtime 2010 x86 のマージモジュールを追加
  <Directory Id="SystemFolder">
      <!-- VC100 ATL Runtime -->
      <Merge Id="Microsoft_VC100_ATL_x86.msm" Language="0" DiskId="1" SourceFile="C:/Program Files (x86)/Common Files/Merge Modules/Microsoft_VC100_ATL_x86.msm" />
      <Merge Id="Microsoft_VC100_CRT_x86.msm" Language="0" DiskId="1" SourceFile="C:/Program Files (x86)/Common Files/Merge Modules/Microsoft_VC100_CRT_x86.msm" />
      <Merge Id="Microsoft_VC100_MFCLOC_x86.msm" Language="0" DiskId="1" SourceFile="C:/Program Files (x86)/Common Files/Merge Modules/Microsoft_VC100_MFCLOC_x86.msm" />
      <Merge Id="Microsoft_VC100_MFC_x86.msm" Language="0" DiskId="1" SourceFile="C:/Program Files (x86)/Common Files/Merge Modules/Microsoft_VC100_MFC_x86.msm" />
      <Merge Id="Microsoft_VC100_OpenMP_x86.msm" Language="0" DiskId="1" SourceFile="C:/Program Files (x86)/Common Files/Merge Modules/Microsoft_VC100_OpenMP_x86.msm" />
  </Directory>
vc runtime 2010 x64 と x86 のマージモジュールを参照
  <Feature ...>
      <!-- VC10 runtime -->
      <MergeRef Id="Microsoft_VC100_ATL_x64.msm" />
      <MergeRef Id="Microsoft_VC100_CRT_x64.msm" />
      <MergeRef Id="Microsoft_VC100_MFCLOC_x64.msm" />
      <MergeRef Id="Microsoft_VC100_MFC_x64.msm" />
      <MergeRef Id="Microsoft_VC100_OpenMP_x64.msm" />
      <!-- VC10 runtime -->
      <MergeRef Id="Microsoft_VC100_ATL_x86.msm" />
      <MergeRef Id="Microsoft_VC100_CRT_x86.msm" />
      <MergeRef Id="Microsoft_VC100_MFCLOC_x86.msm" />
      <MergeRef Id="Microsoft_VC100_MFC_x86.msm" />
      <MergeRef Id="Microsoft_VC100_OpenMP_x86.msm" />
  </Feature>
  ...
</Product>
というように対応できそうです。

2025/05/20 追記: CPPRUNTIMEVERSIONPREFIX が想定した番号を超えていた。Microsoft Visual C++ 2019 (x64) Redistributable missing というメッセージが出てインストーラが動作しない。という事で、インストーラを下記のように番号を増やしたのだが、この番号、どこまで互換性があるのか…というより、チェックするのも面倒くさくなってきた。readme.md かなんかに、vc runtime が必須ですと書いて、チェックいらないんじゃね?と思った。
  <!-- C++ 2019 -->
  <Property Id="CPPRUNTIME2019X86" Secure="yes">
    <?foreach CPPRUNTIMEVERSIONPREFIX in 21;22;23;24;25;26;27;28;29;30;31;32;33;34;35;36;37;38;39;40;41;42;43;44;45;46;47;48;49;50?>
      <RegistrySearch Id="mfc14$(var.CPPRUNTIMEVERSIONPREFIX)x86" Root="HKCR" Key="Installer\Dependencies\VC,redist.x86,x86,14.$(var.CPPRUNTIMEVERSIONPREFIX),bundle" Type="raw" />
    <?endforeach ?>
  </Property>
2025/12/08 追記:
Visual Studio 2026 が出て、プラットフォームツールセット: v145 になりました。
こいつ、下位互換性は有りますが、上位互換性は有りません。下位バージョンのruntimeだと、static メンバーの初期化がうまく動作しません。
よって、バージョンを切り分ける必要がありそうです。
  <!-- C++ 2026 -->
  <Property Id="CPPRUNTIME2026X86" Secure="yes">
    <?foreach CPPRUNTIMEVERSIONPREFIX in 50;51;52;53;54;55;56;57;58;59;60?>
      <RegistrySearch Id="mfc14$(var.CPPRUNTIMEVERSIONPREFIX)x86" Root="HKCR" Key="Installer\Dependencies\VC,redist.x86,x86,14.$(var.CPPRUNTIMEVERSIONPREFIX),bundle" Type="raw" />
    <?endforeach ?>
  </Property>

2022年5月13日金曜日

Windows DLL x86とx64で異なる関数名調整の備忘録

AESのDLLをビルドしていて、x64でビルドしたものは素直に利用できるのにx86でビルドしたものを利用しようとするとLNK2019: _aes_encrypt_key が見つからないというリンクエラーになってしまった。
test_lib.exe のソースを見ると
#ifdef  AES_MODES

    fn->fn_test_align = (g_talign*)GetProcAddress(h_dll, etad_name);
    fn->fn_mode_reset = (g_reset*)GetProcAddress(h_dll, eres_name);
    fn->fn_ecb_enc = (g_enc1*)GetProcAddress(h_dll, ecbe_name);
    fn->fn_ecb_dec = (g_dec1*)GetProcAddress(h_dll, ecbd_name);
    fn->fn_cbc_enc = (g_enc2*)GetProcAddress(h_dll, cbce_name);
    fn->fn_cbc_dec = (g_dec2*)GetProcAddress(h_dll, cbcd_name);
    fn->fn_cfb_enc = (g_enc3*)GetProcAddress(h_dll, cfbe_name);
    fn->fn_cfb_dec = (g_enc3*)GetProcAddress(h_dll, cfbd_name);
    fn->fn_ofb_cry = (g_enc3*)GetProcAddress(h_dll, ofb_name);
    fn->fn_ctr_cry = (g_enc4*)GetProcAddress(h_dll, ctr_name);

    if(   !fn->fn_mode_reset || !fn->fn_test_align
       || !fn->fn_ecb_enc || !fn->fn_ecb_dec
       || !fn->fn_cbc_enc || !fn->fn_cbc_dec
       || !fn->fn_cfb_enc || !fn->fn_cfb_dec
       || !fn->fn_ofb_cry || !fn->fn_ctr_cry )
       ok = 0;
#endif
と、DLLを LoadLibrary した後で、GetProcAddress により関数のアドレスを取得し、呼び出すという事をしていた。
で、名前の定義は aestst.h に記述されており、x86 と x64 で切り替えている。
#if defined( _WIN64 )

#define gt_name             "aes_init"
#define ek_name128          "aes_encrypt_key128"
#define ek_name192          "aes_encrypt_key192"
#define ek_name256          "aes_encrypt_key256"
#define ek_name             "aes_encrypt_key"
#define eb_name             "aes_encrypt"
#define dk_name128          "aes_decrypt_key128"
#define dk_name192          "aes_decrypt_key192"
#define dk_name256          "aes_decrypt_key256"
#define dk_name             "aes_decrypt_key"
#define db_name             "aes_decrypt"

#define etad_name           "aes_test_alignment_detection"
#define eres_name           "aes_mode_reset"
#define ecbe_name           "aes_ecb_encrypt"
#define ecbd_name           "aes_ecb_decrypt"
#define cbce_name           "aes_cbc_encrypt"
#define cbcd_name           "aes_cbc_decrypt"
#define cfbe_name           "aes_cfb_encrypt"
#define cfbd_name           "aes_cfb_decrypt"
#define ofb_name            "aes_ofb_crypt"
#define ctr_name            "aes_ctr_crypt"

#else

#define gt_name             "_aes_init@0"
#define ek_name128          "_aes_encrypt_key128@8"
#define ek_name192          "_aes_encrypt_key192@8"
#define ek_name256          "_aes_encrypt_key256@8"
#define ek_name             "_aes_encrypt_key@12"
#define eb_name             "_aes_encrypt@12"
#define dk_name128          "_aes_decrypt_key128@8"
#define dk_name192          "_aes_decrypt_key192@8"
#define dk_name256          "_aes_decrypt_key256@8"
#define dk_name             "_aes_decrypt_key@12"
#define db_name             "_aes_decrypt@12"

#define etad_name           "_aes_test_alignment_detection@4"
#define eres_name           "_aes_mode_reset@4"
#define ecbe_name           "_aes_ecb_encrypt@16"
#define ecbd_name           "_aes_ecb_decrypt@16"
#define cbce_name           "_aes_cbc_encrypt@20"
#define cbcd_name           "_aes_cbc_decrypt@20"
#define cfbe_name           "_aes_cfb_encrypt@20"
#define cfbd_name           "_aes_cfb_decrypt@20"
#define ofb_name            "_aes_ofb_crypt@20"
#define ctr_name            "_aes_ctr_crypt@24"

#endif
なるほど、だから x64 ではリンクエラーにならず、x86ではリンクエラーになったのだ。
x86 と x64 でマングリングの名前が違う事に自分も苛ついてはいました。
def ファイルに alias 指定できないの???と思って調べたら、ありました。
EXPORTS
alias=function_name
としてやれば良かったようです。
という事で、リンクエラーになった x86 用のビルドだけ以下のdefファイルを適用するようにしました。
LIBRARY		Aes

EXPORTS
   _aes_init=_aes_init@0
   _aes_encrypt_key128=_aes_encrypt_key128@8
   _aes_encrypt_key192=_aes_encrypt_key192@8
   _aes_encrypt_key256=_aes_encrypt_key256@8
   _aes_encrypt_key=_aes_encrypt_key@12
   _aes_encrypt=_aes_encrypt@12
   _aes_decrypt_key128=_aes_decrypt_key128@8
   _aes_decrypt_key192=_aes_decrypt_key192@8
   _aes_decrypt_key256=_aes_decrypt_key256@8
   _aes_decrypt_key=_aes_decrypt_key@12
   _aes_decrypt=_aes_decrypt@12

   _aes_test_alignment_detection=_aes_test_alignment_detection@4
   _aes_mode_reset=_aes_mode_reset@4
   _aes_ecb_encrypt=_aes_ecb_encrypt@16
   _aes_ecb_decrypt=_aes_ecb_decrypt@16
   _aes_cbc_encrypt=_aes_cbc_encrypt@20
   _aes_cbc_decrypt=_aes_cbc_decrypt@20
   _aes_cfb_encrypt=_aes_cfb_encrypt@20
   _aes_cfb_decrypt=_aes_cfb_decrypt@20
   _aes_ofb_crypt=_aes_ofb_crypt@20
   _aes_ctr_crypt=_aes_ctr_crypt@24
こうする事で、見事にLNK2019を回避する事ができました。
今まで、やり方がわからなくて、Delphi 用 DLL には序数で指定したりと、大変だったんですよね。

2022/06/29 追記:
bigtypes.h に
#ifndef RETURN_VALUES
#  define RETURN_VALUES
#  if defined( DLL_EXPORT )
#    if defined( _MSC_VER ) || defined ( __INTEL_COMPILER )
#      define VOID_RETURN    __declspec( dllexport ) void __stdcall
#      define INT_RETURN     __declspec( dllexport ) int  __stdcall
#    elif defined( __GNUC__ )
#      define VOID_RETURN    __declspec( __dllexport__ ) void
#      define INT_RETURN     __declspec( __dllexport__ ) int
#    else
#      error Use of the DLL is only available on the Microsoft, Intel and GCC compilers
#    endif
#  elif defined( DLL_IMPORT )
#    if defined( _MSC_VER ) || defined ( __INTEL_COMPILER )
#      define VOID_RETURN    __declspec( dllimport ) void __stdcall
#      define INT_RETURN     __declspec( dllimport ) int  __stdcall
#    elif defined( __GNUC__ )
#      define VOID_RETURN    __declspec( __dllimport__ ) void
#      define INT_RETURN     __declspec( __dllimport__ ) int
#    else
#      error Use of the DLL is only available on the Microsoft, Intel and GCC compilers
#    endif
#  elif defined( __WATCOMC__ )
#    define VOID_RETURN  void __cdecl
#    define INT_RETURN   int  __cdecl
#  else
#    define VOID_RETURN  void
#    define INT_RETURN   int
#  endif
#endif
と定義されていて、x64は呼び出し規約が異なるので問題なく動作するが、x86で利用する場合は利用する側で
DLL_IMPORT をプリプロセッサの定義に追加しないと、コールスタックが破壊されて、よくわからないエラーになる
尚、aes.dll を使った dll を書きたい場合は、DLL_EXPORT や DLL_IMPORT といった汎用的な定義で判別していると、定義がバッティングするので、AES_DLL_EXPORT と言った定義に変更した方が良いと思う。できれば、DLL_IMPORT は定義しなくても AES_DLL_EXPORT が定義されていなかったら、利用する側のコードだと判別できるので、このマクロの書き方は改良した方が良いかな?

もうひとつ、vsyasm の -f オプションが Win32 ではなく win32 しか認識しないので、
vsyasm.props ファイルを修正する
...
      <CommandLineTemplate Condition="'$(Platform)'=='Win32'">"$(YASM_PATH)"vsyasm.exe -Xvc -f win32 [AllOptions] [AdditionalOptions] [Inputs]</CommandLineTemplate>
      <CommandLineTemplate Condition="'$(Platform)'!='Win32'">"$(YASM_PATH)"vsyasm.exe -Xvc -f $(Platform) [AllOptions] [AdditionalOptions] [Inputs]</CommandLineTemplate>
...

2022年4月25日月曜日

oracle x64 と x86 なクライアント環境を切替え(windows)忘備録

Oracle が面倒くさいのである。

請負の開発をしていると、このプロジェクトは32bitアプリで、このプロジェクトは64bitアプリで、という事は、普通にあります。
で、Oracle は2つ同時に両立しないので、両立させる環境の構築についての忘備録

  • x64版のOracle Clientをインストール
  • 環境変数 ORACLE_HOME を ORACLE_HOME_X64 に変更
  • インストール先の名前を _x64 付きに変更
  • x86版のOracle Clientをx64と同じフォルダ名(_x64なし)にインストール
  • インストール先の名前を _x86 付きに変更
  • インストール先の名前に _x86 のリンクディレクトリを作成する
  • 切替用のバッチファイルを作成

x64 用バッチのサンプル
cd %ORACLE_HOME%
cd ..
rmdir client_1
mklink /D client_1 client_1_x64

x86 用のバッチのサンプル
cd %ORACLE_HOME%
cd ..
rmdir client_1
mklink /D client_1 client_1_x86

これで、x64 と x86 を切替えできる。切り替えた後は、Windows を再起動

尚、リンクディレクトリの作成は、mklink /D により行う

2022年3月31日木曜日

datetime の 曖昧なフォーマットに対応する忘備録

日付のフォーマットと制約が厳しい。
わりとありそうな日付の表記ゆれをパースするグラマーを構築してみた。
#include <boost/spirit/include/qi.hpp>  // boost::spirit::qi を利用します
#include <boost/fusion/adapted/struct/adapt_struct.hpp>
#include <iostream>
#include <string>


#include <boost/date_time.hpp>

namespace pt = boost::posix_time;
namespace qi = boost::spirit::qi;


struct timeinfo {
  int16_t   year_;
  uint16_t  month_;
  uint16_t  day_;
  uint16_t  hour_;
  uint16_t  minute_;
  uint16_t  second_;
  //uint32_t  millisec_;
  std::string millisec_;
};

BOOST_FUSION_ADAPT_STRUCT(
  timeinfo,
  (int16_t,     year_)
  (uint16_t,    month_)
  (uint16_t,    day_)
  (uint16_t,    hour_)
  (uint16_t,    minute_)
  (uint16_t,    second_)
  //(uint32_t,    millisec_)
  (std::string,  millisec_)
)

template <typename Iterator, typename Skipper>
struct time_grammar : qi::grammar<Iterator, timeinfo(), Skipper> {
  qi::rule<Iterator, timeinfo(), Skipper>  start_;
  qi::int_parser<int16_t,10,4,4> int4_;
  qi::uint_parser<uint16_t,10,2,2> uint2_;
  qi::rule<Iterator, std::string(), Skipper> numeric_;

  time_grammar() : time_grammar::base_type(start_, "time_grammar") {
    numeric_ = *qi::char_('0','9');
    start_ = (
        int4_ 
        >> -(qi::lit('/') | qi::lit('-')) >> uint2_
        >> -(qi::lit('/') | qi::lit('-')) >> uint2_
        //>> (qi::lexeme[qi::lit(' ')] | qi::lit('T'))
        >> -(qi::lit('T'))
        >> uint2_
        >> -(qi::lit(':')) >> uint2_
        >> -(qi::lit(':')) >> uint2_
        >> (
          //-( (qi::lit('.') | qi::lit(',')) >> qi::uint_)
          -( (qi::lit('.') | qi::lit(',')) >> numeric_)
          | qi::attr(0)
        )
        >> -(qi::lit('Z'))
    );
  }
};



int main() {
  
  std::string input[] = {
    "20210412T123455Z",
    "20210412T123455,987654321Z",
    "20210412T123455.987654321Z",
    "2004-03-21 12:45:33",
    "2004/03/21 12:45:33",
    "23.09.2004 04:12:21",
    "2003-02-11"
  };

  time_grammar<std::string::iterator, qi::standard::space_type> myg;
  for( int i = 0; i < sizeof(input)/sizeof(input[0]); i++ ) {
    timeinfo ti;
    qi::phrase_parse( input[i].begin(), input[i].end(), myg, qi::standard::space, ti );
    std::cout << "--------------" << std::endl;
    std::cout << ti.year_ << std::endl;
    std::cout << ti.month_ << std::endl;
    std::cout << ti.day_ << std::endl;
    std::cout << ti.hour_ << std::endl;
    std::cout << ti.minute_ << std::endl;
    std::cout << ti.second_ << std::endl;
    std::cout << ti.millisec_ << std::endl;
  }
  return 0;
}
2023/06/02 追記: lexeme がうまく動作しないのでスペースを無視した。millisec が ,020Z と指定された場合 2 に変換されてしまうので std::string で扱うようにした。バグでした。

2022年3月28日月曜日

boost::log windows で /dev/null 忘備録

boost::log でデバッグ用ログの埋め込みを行っていて、リリース時には取り除きたい場合の措置。
これだと、nul デバイスに対して出力する処理が入るので、負荷を取り除くという面では、よろしくないコード

ポイントは、
Windows では nul というファイルが /dev/null に相当する
nul というファイルは、どこにでも存在している扱いになるので、追加書き込みモードで開く必要がある点

#include <boost/date_time.hpp>

#include <boost/log/trivial.hpp>
#include <boost/log/utility/setup/file.hpp>
#include <boost/log/attributes.hpp>
#include <boost/log/expressions.hpp>
#include <boost/log/support/date_time.hpp>

namespace logging = boost::log;
namespace attrs = boost::log::attributes;
namespace keywords = boost::log::keywords;
namespace sinks = boost::log::sinks;
namespace expr  = boost::log::expressions;

...

//#define DEBUG_LOG_FILE  "c:/logs/%Y_%m_%d.log"

...

  boost::log::add_file_log(
#ifdef DEBUG_LOG_FILE
    keywords::file_name = DEBUG_LOG_FILE,
#else
    keywords::file_name = "nul",
    keywords::open_mode = std::ios_base::out | std::ios_base::app,
#endif
    keywords::time_based_rotation = sinks::file::rotation_at_time_point(0, 0, 0),
    keywords::format = 
    (
        expr::stream
            << expr::format_date_time< boost::posix_time::ptime >("TimeStamp", "%Y-%m-%d %H:%M:%S")
            << ": <" << logging::trivial::severity
            << "> " << expr::smessage
    ),
    keywords::auto_flush = true
  );
  BOOST_LOG_TRIVIAL(debug) << "this is a debug output.";

...