2020年5月25日月曜日

C# (.NET)でMS Officeファイルを扱う - Apache POI & IKVM

C#でWordとかExcelのファイルを読み書きする手段としてはいくつかあるのだけど、無償で利用可能な手段だと、だいたいこの2つが定番っぽい。

Microsoft.Office.Interop

Microsoftが提供している、Visual Studioに付属するライブラリ。
ググればいくらか情報も出てくる。
導入も簡単。参照設定に追加するだけだし。

だけど、超遅い。
10ページほどのdocファイルからテキストを抽出するコードを書いてみたのだけど、
それだけでも5~6秒とかかかる。新しいdocx形式のファイルだといくらかマシなようだが。(2019年製のSSDにCorei5の安価なPCだったけど、それにしたってこれは重すぎる。)

扱うファイルが少数で、それほど実行速度が問題にならないならこれでいい。
さすがにMicrosoftが提供しているだけあって、図形の扱いなど複雑なこともできるのは利点かもしれない。

NPOI

Apache POI というJavaのOfficeファイルを読み書きするライブラリを.NETに移植したもの。
NuGetから導入できる。高速に動作する。

ただし、扱えるファイルの種類は xls, xlsx, docx の3種類のみ。
元のApache POIだと doc, ppt, pptx などOffice系のファイルは一通り対応しているのだけど、どういう理由か扱えるのは前述の3種のみ。
また、テキストボックスや図形などには対応できていないようだ。

で、問題は、

  • それなりの速度が欲しい
  • doc, ppt, pptx なども扱いたい
  • テキストボックスや図形なども扱いたい
という場合。

Java の Apache POI を .NET から呼び出せるようにすればいいんじゃね?

と思うのだが、それが難しいからこそ NPOI を作ってるんじゃないのかなー
とか思いつつ調べていたら、ずばりそういうのがあるようだ。


IKVM

公式
wikipedia
「Mono及び.NET Framework上で実装された、Java仮想マシンを含むJavaの実装」
「他実装と比べ2重の仮想化が行われるため速度面では不利ではあるが、.NET上からJavaの各種ライブラリ等をプログラミング言語レベルで一切意識せずに利用できる。」
だそうな。(wikipediaより)

2015年あたりで更新が終了してしまっているのが惜しいところか。

でもソースもバイナリも公開は継続しているので、利用可能。

使い方よくわかんないし、

だれかこれで Apache POI を .NET で使えるようにしたモノを公開してないかなー
とか日和ったことを考えながら調べていると、
・・・あったよ。NuGetにあるじゃん。


「poi」で検索するといくつかそれっぽいのがでてくるのだけど、
試してみたところ、作成者「qcat」さんの「poi」が使えるようだ。
(「poi-ikvm」という名前のもあるが、こちらは対応するファイル形式が限られていて、なんか半端なもの(失礼)のようだ。)

これをインストールすると、プロジェクトの参照設定に

「poi」に加えて「IKVM.~」というライブラリが大量に追加される。



Apache POI でググると情報はたくさん出てくるのに加えて、
JavaとC#は構文がかなり似ているので、それほど参考資料には困らないはずなので、
ここではPOIの使い方については省略。

ただ、実際に試してみると、docxファイルを開いたときにエラーが発生したので、その対処法について。

エラーの内容を見ると、なにかXML関連のライブラリの参照が足りてない様子。で、↑の画像の参照設定と、IKVMの公式サイトからダウンロードしてきたバイナリ一式(ver. 7.2.4630.5)を見比べてみると、XMLと名前の付くもので
  • IKVM.OpenJDK.XML.Crypto.dll
  • IKVM.OpenJDK.XML.Transform.dll
  • IKVM.OpenJDK.XML.WebServices.dll
  • IKVM.OpenJDK.XML.XPath.dll
の4つが足りてないようなので、これら4つのファイルを直接、参照設定に追加すると、無事にエラーが解消した。
(Visual Studio のソリューションエクスプローラーで「参照」を右クリック
→ 参照マネージャー左側の「参照」 を選択
→ 右下の「参照」ボタン(OKボタンのとなり)で、上記4つのdllを選択)

試してみると、wikipediaでは速度面では不利なんて書いてあるけれども、十分実用範囲内だと思われる。

10ページ程度のdocxファイルからテキストをすべて抽出するのに約200msほど、同じファイルをdoc形式で保存したファイルだと50msほど(なぜか旧形式のほうがずっと速い)だった。
もちろんファイルの内容やマシンスペックに依存するだろうが Microsoft.Office.Interop よりは断然速い。

この「qcat」さん製のPOIライブラリは、現在公開されている Apache POI (最新版は4.1 @2020年5月現在)と比べるとバージョンが古いのが難点か。

自前でApache POIをIKVMを通して使えるように、
あとでIKVMの使い方を調べておくべきかな。

※追記 @2020.05.31
POI+IKVMの実行時、内部で大量に例外をスローしているようだ。
例外処理はされているようなので途中でプログラムが落ちたりはしないが、デバッグ出力に大量にエラーメッセージのログが表示される。
このせいで重くなっているらしい。
デバッグ無しで実行すると、かなり軽くなる。
上で.docxファイルだと200ms、.docファイルだと50msほど、と書いているが、これをデバッグ無しで実行するとそれぞれ約50msとか約20msぐらいまで速くなる。

2019年3月17日日曜日

HP移転しました

なんだかYahooジオシティーズが2019年3月末でサービス終了するらしいので、
ホームページを移転しました。

http://tegetegeosprey.g1.xrea.com/

そっくりそのまま移転しただけで何にも変わってないし、
そもそもホームページ自体、最近更新してないけど・・・。

2017年4月21日金曜日

Nintendo Switchのドックを改造:画面の傷対策

Nintendo Switchを買ったのだけど、本体の液晶に貼った保護シート(反射低減のやつ)が、どうもいつの間にか画面の右側の縁にばかり傷が増えていくので調べてみると、どうやらドックの本体を格納するスペースが微妙に歪んでいる。

具体的には、格納スペースの、ロゴ側から見て右側のほうが左側よりも約0.5mmほど狭く、そのせいで格納スペースの内側の突起部分が画面と接触しているらしい。



なので、任天堂に修理に出したら・・・
異常なしとか言ってそのまま返してきやがった。

つまり、ドックに本体を格納すると画面に傷が付くのは仕様らしい。

何このクソ欠陥設計。

で、だ。

ドック自体はどうやら分解記事なんかを見ているとほとんど空っぽらしく、画面に接触する部分は切り落としてしまっても問題なさそうなので、やってみる。

まずは分解記事を参考にバラす。
といっても、少々特殊な形状のネジが使われているだけで、対応するドライバーさえあれば割と簡単にバラせる。

一枚だけ入っている基板と端子類とを接続しているフレキシブル基板を外すのが少々面倒だけど、ピンセットで外せるのでコレも問題ない。

問題の、ロゴが書かれている前面パネルは、外側と格納スペースの内側の部分とで2つのパネルが組み合わせてあるが、これは手でスライドさせれば簡単に外れる。
この内側のパネルが画面に当たって傷を付けている。これはいらないので捨ててよし。

ロゴが書かれている前面パネルを、ちょうど絵と文字の間のあたり、外した内側のパネルがハマる部分を切り落とす。無駄に厚みがありカッターとかでは切れないのでノコギリで。


格納部分の底部のパーツの前面側の突起は不要になるので、これも切り落としておく。
あとは切り口をヤスリで整えてから組み立てれば完了。
(ちょっと切り口がずれてしまったが、見た目は気にしないということで。)


本体を格納するとこんな感じ。


これで、画面に傷が付く心配も無くなった。
また、改造後のほうが差し込む距離がたった2cmほどになり、かなり格納しやすくなった。
格納時の固定ぐあいは、元々、格納部分の底部にゴム製の押さえが付いているので問題ない。

2017年4月14日金曜日

Logicool G13の修理

普段使ってるLogicoolのG13が、ジョイスティックの下側のボタンがチャタリングを起こすようになってうっとうしくなってきたのだけど、買い換えるにも地味に高いので、自分で修理してみてダメだったら諦めて買い換えるしかないかなー、という感じで自分で修理してみることに。

あっれ、こんなに高かったっけ・・・

ググってみると同じ箇所でチャタリング起こしてる人が結構いるようなので、自己流修理方法のメモ。

何はともあれ、まずはバラすところから。
裏側のゴムと中央の丸いところを剥がして6カ所のネジを外し、マイナスドライバーなどを使ってこじ開ける。

で、問題の箇所。


右下の黒くて四角いのが問題のスイッチ。
よく見るとなんか簡単にバラせそうだったのでイジってみると・・・


先のとがったピンセットで簡単にカバーが外れた。
押し下げるボタン部分のパーツが小さいので注意。
ピンが3本あって、そこにバネを兼ねたスイッチの金具が引っかけてある構造。
このピンと金具が汚れて(サビて?)いたので、たぶんコレが原因で接触不良 → チャタリングを起こしているのかと。(上の写真は汚れを落とした後。)
ピンと金具を#1000ぐらいの目の細かいサンドペーパーで軽く磨いて終了。

で、ついでに、この部分のキーを押し下げるときにギシギシ引っかかるので、そこも直してしまおうかと思いボタン側のプラスチックのパーツを見ていると、上記のスイッチを押すパーツが折れ掛かっている、というか、まだ折れてないけど、プラスチックをグニャグニャ繰り返し曲げていくと折れるその一歩前みたいな、やばそうな状態。

なので、このパーツは折れる前に複製して置き換えることに。
こんなのを使うと、簡単にプラスチックの部品を複製できます。



  

「型取りくん」は、名前の通り。
熱湯で温めると柔らかくなる合成樹脂。
素手で扱えるし、手にもほとんどくっつかないので扱いやすい。
柔らかくなったところに複製したい部品を押しつけて埋めたら、冷ますだけで即、型取りが完了する。

「プラリペア」は、粉末と液体を混ぜ合わせると化学反応を起こして固まるプラスチック。というかアクリルの原材料みたいなものらしい。20~30分程度で硬化するし、粉末と液体の分量もかなり適当でいいし、硬化後の強度もあるので(アクリルですし)、小さいプラスチック部品の補修などに使いやすい。

で、元の部品(黒いほう)と複製したヤツ(白いほう)、型を取った「型取りくん」。
写真では裏になってる側にも突起があるので、両面を型取りしてます。
型取りくんに刺さってるのは、上面と下面の位置合わせ用の針金。

型をプラリペアの粉末でテキトーに埋めて、硬化用の液剤をややあふれ気味に加えて30分ほど放置。硬化したら型から取りだして(型はそんなにガチガチに硬くはないので、型を曲げればすぐに取り出せる)、気泡をまたプラリペアで埋めて、ヤスリとデザインナイフで形を整えて完了。・・・といいつつ、この形を整えるのが手間なんだけど。


複製したパーツをはめたところ。左のネジ止めされてる白いヤツ。
どうやらコレも、同じように破損しているらしき報告(グチ?)がwebで散見されます。


オリジナルと複製したのとで交換してみたりしながらボタンを押したときの引っかかりの様子を見ていると、どうやらこのパーツが原因の様子。
リング状のネジ止めする部分が、微妙に穴の内側に引っかかるらしい。
なので、複製したヤツはすこーしだけ外径を小さめに削ってます。だいたいネジの頭と同じぐらい。

あとは分解したのを元に戻してネジ止めして修理完了。
ちゃんとチャタリングやボタンの引っかかりは解消しました。

スイッチのチャタリングだけならたぶん1時間ぐらいで終わってたはずなのに、ボタンの引っかかりの原因を調べたり、パーツを複製したりなんかしてたので硬化時間を待ったりして、だいたい3~4時間ぐらいかかったかも。

2015年5月31日日曜日

Inno Setup : 上書きインストールかどうかを確認する

インストーラーを作成するのに、いろいろ便利なので最近は
Inno Setup
http://www.jrsoftware.org/isinfo.php
というのを使っているのだけど、
ちょいとその小ネタを。

Inno Setupには、前回インストールしたものに対して
バージョンアップしたものを上書きインストールしたい場合に、
前回インストールしたときのフォルダを検出して
自動的にそこにインストールしたりしてくれるような
機能が用意されていたりはするのだけど、
「旧バージョンがインストールされてるけどアップデートする?」
的なメッセージを表示させるような機能は用意されていないので、
それを作ってみた。


・・・作ってみたのだが、
これがまた簡単そうで意外と面倒くさかった。
[Code] セクションで自前でスクリプトを書かなければならず、
全く書いたことのなかったよくわかんらんPascal言語の文法で
悩まされることに・・・。

未だに If - else 構文の理屈がよくわんねー


ま、それは置いといて、
こんな感じのスクリプトになった。

[Code]

// ------------------------------------------------------------
// 前回インストールしたアプリケーションの情報が格納されている(はず)の
// レジストリのパスを返す関数。
// Inno Setupで作成したインストーラーは、
// ここにアプリケーション名やらバージョンなどの情報を格納している。
// (試しに regedit で覗いてみるのがおすすめ。)
// ------------------------------------------------------------
function GetAppInstallRegKey() : string;
begin
  Result := 'SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\([Setup]セクションのAppIdに設定した値)';
  
end;


// ------------------------------------------------------------
// 上書きインストールかどうかを調べる関数。
// (レジストリに別なバージョンのインストール情報があるかどうかを調べる。)
// ------------------------------------------------------------
function IsUpdateInstall() : Boolean;
var
  key : string;
begin
  key := GetAppInstallRegKey();

  if RegKeyExists(HKEY_LOCAL_MACHINE, key) then begin
    Result := true
  end else begin
    Result := false;
  end;

end;


// ------------------------------------------------------------
// すでにインストールされているヤツのバージョンを取得する関数。
// ------------------------------------------------------------
function GetPrevVersion() : string;
var
  prevVersion : string;
  key : string;
begin
  key := GetAppInstallRegKey();
  if RegQueryStringValue(HKEY_LOCAL_MACHINE, key, 'DisplayVersion', prevVersion) then
  begin
    Result := prevVersion;
  end;

end;


// ------------------------------------------------------------
// インストール開始時に呼び出される関数。
// ------------------------------------------------------------
function InitializeSetup(): Boolean;
var

  // すでにインストールされているヤツのバージョン
  prevVersion : string;

  // 上書きインストールを実行するかどうか
  doUpdate : Boolean;

begin

  // 別なバージョンがインストール済みかどうかを確認して、
  if IsUpdateInstall() then begin

    // そうなら、前回インストールしたヤツのバージョンを取得して、
    prevVersion := GetPrevVersion();

    // メッセージを表示。( #13 で改行文字が入るらしい。)
    // Yes が選択されたら 変数 doUpdate に true が入る。
    doUpdate := MsgBox('バージョン ' + prevVersion + ' がすでにインストールされています。' #13 'バージョン (ほげほげ) で上書きインストールを実行しますか?', mbConfirmation, MB_YESNO) = idYes;
    
    if doUpdate then begin
 
      // Yes が選択されたら、この関数の戻り値に true を設定。
      Result := true;

    end else begin

      // No なら、キャンセルされる旨のメッセージを表示して、
      MsgBox('セットアップはキャンセルされました。', mbInformation, MB_OK);

      // 戻り値に false を設定。
      // この関数が false を返すと、セットアップが中止される。
      Result := false;
      
    end;
    
  end else begin

    // 新規のインストールなので特に何もしない。
    Result := true;
    
  end;

end;


あと、[Setup]セクションで、

DisableDirPage=auto
DisableProgramGroupPage=auto

と書いておく。

DisableDirPage は、
インストール先ディレクトリの選択画面を表示するかどうかのオプション。

DisableProgramGroupPage は、
スタートメニューに作成するプログラムグループを指定する画面を
表示するかどうかのオプション。

これらを、"auto" にしておくと、
新規インストールの時はその画面が表示され、
アップデートするときは表示されずに前回インストール時の
ディレクトリなりプログラムクループなりが自動で選択されるようになる。



2015年4月26日日曜日

こっそり更新中

ずいぶん久しぶりの投稿になってしまったけど、
まぁ、毎度のことなのでそれは置いとくとして。

ImgViewerをこっそり更新してます。
何となく自分で使っていて不便なところをちまちまと。

画像の表示順を
WindowsのStrCmpLogicalW関数を使って
エクスプローラーの表示順に合わせてみたり、
FreeImageで画像のメタデータ(Exif情報とか)を簡単に取得できるので、
それを見れるようにしてみたりとか。

最近はあんまり新しいソフトウェアを作るネタも時間もないのだけど、
たまーに何かコードを書きたくなるんだよね・・・。
や、仕事ではいつもコード書いてるんだけど、
それとは別に、趣味というかお遊び的に好き勝手に
なにか気の向くままに作りたいというか。

そういや、Visual Studio を 2012 Expless に乗り換えたのだけど、
インストーラーが前と同じ方法では作れなくなってしまったので
なにかいいのはないかと探していたのだけど、
「Inno Setup」
というのが便利そう。

インストーラーとして必要な機能はほとんど一通りそろっているし、
スクリプト書けば、かなりいろいろできそうだし。

ただ、使っているユーザーは多そうなのだが、
どうにも日本語の情報が少なくて少々苦労する・・・。

あと、Pascalの文法が謎。
情報少なすぎるし、
if elseの構文が未だにどーいう理屈なのかよくわからん。

2013年1月1日火曜日

明けましておめでとうございます

今年もよろしくお願いします。

ということで、
今年の年賀状用に作ったCGです。



昨年とおんなじ感じで、
あえてポリゴンのカクカクした感じのデザインで。
テクスチャ作るのが面倒なので、ヘビの模様は無し。

文字の部分は、
はじめ、ガラスっぽく作ったら読みにくくなってしまったので、
正面側に不透明なポリゴンを貼ってみました。

ヘビの頭は、
ローポリゴンでカクカクしつつも、
それっぽくできたのではないかと思いますが、
いかがでしょうか?

(QRコードはブログ用に差し替えてあります。)