手続き型音楽の日常

関数型音楽に乗り換えたい

StringBuilderをstringと同じだけ作ると、どれほど遅いのか。

この間、以下のようなコードを見かけました。

StringBuilder strSql = new StringBuilder();
strSql.Append(@"SELECT * FROM ...
                WHERE ..."); // SQLは長いので省略。
this.ExecuteNonQuery(strSql.ToString());

フォームのメソッドに書かれていたのですが、フォーム自体がユーザコントロールになっていて、データベース接続の機能をすでに実装しているんですね。

なので、それにSQLを投げれば完了というすごくシンプルなものなのですが。

StringBuilderにする必要あったのかなって。

StringBuilderを必要とする理由

StringBuilder は、 string 型が演算で毎回新しいインスタンスを生成したり破棄したりするのに対して、最初のインスタンスを使いまわすために使う、というものだと思います。

具体的には次のコード。

string s = "";
s = s + "a";
s = s + "b";
s = s + "c";

上記のコードでは、最終的に変数 s に入るのは "abc" という文字列ですが、その文字列を得るまでにインスタンスを生成する回数は7回です。

それに対し、

StringBuilder s = new StringBuilder();
s.Append("a");
s.Append("b");
s.Append("c");

上記のコードでも同様に、最終的に変数 s に入るのは "abc" という文字列ですが、インスタンスの生成は4回で済みます。

インスタンスの生成、という観点からみると、 string 型より StringBuilder 型のほうが同じインスタンスを使いまわすためオーバーヘッドが少ないと考えられます。

しかし。

最初のコードって、はじめに StringBuilder 型の変数を確保しているので、毎回インスタンスを生成していることになるんですよね。

これではstring型を直で触るのと変わらないのでは…。

ということで調べてみました。

検証コード

次の検証コードを用意しました。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace ConsoleApplication1
{
    class Program
    {
        /// <summary> 出力用ダミー変数 </summary>
        static string d_out;

        /// <summary>
        /// メインエントリポイント
        /// </summary>
        static void Main(string[] args)
        {
            try
            {
                int count1;
                int count2;
                List<double> measuredList = new List<double>();

                //------------------------------//
                // 入力
                //------------------------------//

                do
                    Console.WriteLine("1回の計測で確保を行う回数を指定してください...");
                while (!int.TryParse(Console.ReadLine(), out count1));

                do
                    Console.WriteLine("計測を行う回数を指定してください...");
                while (!int.TryParse(Console.ReadLine(), out count2));

                //------------------------------//
                // 計測 string
                //------------------------------//

                measuredList.Clear();
                Console.WriteLine("-------------------------------------------------------");
                Console.WriteLine("stringクラスで計測します。");

                GC.Collect(GC.MaxGeneration);
                for (int i = 0; i < count2; i++)
                {
                    measuredList.Add(MeasureTime_String(count1));
                    Console.WriteLine((i + 1).ToString().PadLeft((int)Math.Log10(count2) + 1) + "回目 : " + measuredList[i]);
                }
                Console.WriteLine("平均 : " + measuredList.Average());

                //------------------------------//
                // 計測 StringBuilder
                //------------------------------//

                measuredList.Clear();
                Console.WriteLine("-------------------------------------------------------");
                Console.WriteLine("StringBuilderクラスで計測します。");

                GC.Collect(GC.MaxGeneration);
                for (int i = 0; i < count2; i++)
                {
                    measuredList.Add(MeasureTime_StringBuilder(count1));
                    Console.WriteLine((i + 1).ToString().PadLeft((int)Math.Log10(count2) + 1) + "回目 : " + measuredList[i]);
                }

                Console.WriteLine("平均 : " + measuredList.Average());

                //------------------------------//
                // 計測 StringBuilder + Append
                //------------------------------//

                measuredList.Clear();
                Console.WriteLine("-------------------------------------------------------");
                Console.WriteLine("StringBuilderクラス + Appendメソッドで計測します。");

                GC.Collect(GC.MaxGeneration);
                for (int i = 0; i < count2; i++)
                {
                    measuredList.Add(MeasureTime_StringBuilderAppend(count1));
                    Console.WriteLine((i + 1).ToString().PadLeft((int)Math.Log10(count2) + 1) + "回目 : " + measuredList[i]);
                }
                Console.WriteLine("平均 : " + measuredList.Average());

            }
            finally
            {
                Console.WriteLine("何かキーを押してください...");
                Console.ReadKey();
            }
        }

        /// <summary>
        /// stringクラスを指定された回数確保し、その時間を計測します。
        /// </summary>
        /// <param name="count">回数</param>
        /// <returns>時間(秒)</returns>
        static double MeasureTime_String(int count)
        {
            DateTime strT = DateTime.Now;

            for (int i = 0; i < count; i++)
            {
                string s = "";
                s = s + "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890";
                Program.d_out = s.ToString();
            }

            DateTime endT = DateTime.Now;

            return (endT - strT).TotalSeconds;
        }

        /// <summary>
        /// StringBuilderクラスを指定された回数確保し、その時間を計測します。
        /// </summary>
        /// <param name="count">回数</param>
        /// <returns>時間(秒)</returns>
        static double MeasureTime_StringBuilder(int count)
        {
            DateTime strT = DateTime.Now;

            for (int i = 0; i < count; i++)
            {
                StringBuilder s = new StringBuilder("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890");
                Program.d_out = s.ToString();
            }

            DateTime endT = DateTime.Now;

            return (endT - strT).TotalSeconds;
        }

        /// <summary>
        /// StringBuilderクラスを指定された回数確保し、Appendメソッドで文字列を指定します。その時間を計測します。
        /// </summary>
        /// <param name="count">回数</param>
        /// <returns>時間(秒)</returns>
        static double MeasureTime_StringBuilderAppend(int count)
        {
            DateTime strT = DateTime.Now;

            for (int i = 0; i < count; i++)
            {
                StringBuilder s = new StringBuilder();
                s.Append("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890");
                Program.d_out = s.ToString();
            }

            DateTime endT = DateTime.Now;

            return (endT - strT).TotalSeconds;
        }
    }
}

汚いとか言わない。

実際にやってみた。

実際に動かしました。スペックは以下の通り。

  • CPU: Atom Z3775 (1.46GHz 4core)
  • メモリ: 2GB

f:id:yuzutan_hnk:20170226021055p:plain:w300

やっぱり、string型のほうがオーバーヘッドは少ないみたいですね。

追記 2017/02/26 実験に使ったスペックを記述していなかったので追記。

内臓HDDのUbuntuをUSBメモリのGrub2から叩く。

非常に初歩的なことですが、かなり調べて納得がいったようないってないようなところまで行きついたので、メモっておきます。

今回は 起動できればいい というスタンスで行くので、詳しいことは全くわかりません。これから勉強します。

環境

今回試したのは、以下の環境。

目標は簡単なことです。 USBのGrub2 から SATAのHDDのUbuntuを起動する だけです。普通はしないけど。

なんでこんなまどろっこしいことがしたいかというと、Ubuntuを内臓HDDにインストールしたまではよかったんですけど、UEFIが入っているはずのGrub2を認識してくれないのです。もしかしてUEFIモード対応のBIOSだったのかこれ?

順序

あくまで理論的な話。

Grub2が起動すると、規定ではOSを選ぶメニューが表示されます。

f:id:yuzutan_hnk:20170217014440j:plain:w300

このとき、メニューで「c」キーを押すとシェル画面に遷移します。この画面ではbashライクな初歩的なコマンドにより、ドライブの中身を確認したりOSをブートさせたりできます。

f:id:yuzutan_hnk:20170217014441j:plain:w300

この機能を使って、実際にUSBのGrub2のメニューには出てこない、内臓HDDのUbuntuを起動させます。

手順

まずは確認

まずは、ディスクの名前を確認します。これは ls コマンドで一覧を取得できます。

f:id:yuzutan_hnk:20170217014442j:plain:w300

grub> ls
(memdisk) (hd0) (hd0,msdos1) (hd1) (hd1,gpt4) (hd1,gpt3) (hd1,gpt2) (hd1,gpt1) (hd2) (hd2,msdos1)

現在起動に使っているUSBは (hd0) として認識されています。試しに、 (hd0) の情報を表示してみます。

f:id:yuzutan_hnk:20170217014443j:plain:w300

なんか、大量にエラーが出ていますが、今回は 起動ができればいい ので無視します。一応、最後の一文がそれっぽい結果になっています。

grub> ls (hd0)
Device hd0: No known filesystem detected - Sector size 512B - Total size 7834624KiB

確かに容量が8GBに近いですので、起動USBと考えて間違いないでしょう。

次に、起動するUbuntuの入ったHDDを確認します。

たぶん見るからに (hd1) っぽいです。なぜかというと、grub上(というかLinuxでは全般に)では、 MBRパテーションテーブルは msdos として表示 されますし、 GPTパテーションテーブルは gpt として表示 されるからです。起動したいのはGPTのHDDです。

f:id:yuzutan_hnk:20170217014443j:plain:w300

grub> ls (hd1)
Device hd1: No known filesystem detected - Sector size 512B - Total size 976762584KiB

案の定ですね。これが1TBのHDDです。

では、今度はUbuntuを起動させるため、Linuxカーネルの配置を確認します。パテーションの1つめはESPなので、2つめのファイル一覧を取得します。

f:id:yuzutan_hnk:20170217014444j:plain:w300

grub> ls (hd1,gpt2)/
lost+found/ boot/ hdata/ etc/ media/ var/ bin/ dev/ home/ lib/ lib64/ mnt/ opt/ proc/ root/ rules.d/ run/ sbin/ snap/ srv/ sys/ tmp/ usr/ vmlinuz initrd.img cdrom/ initrd.img.old vmlinuz.old

Ubuntuのルートですね。さて、これでUbuntuを起動させられるだけの情報は整いました。

え?カーネルのフルネームを確認できていないって?そうそう、なんだか知りませんが、 システムのルートにシンボリックリンクが張ってあるのでそれを使えば問題ない そうです。

起動する

では、実際に起動します。次のコマンドを打って、終わり。

grub> set root=(hd1,gpt2)
grub> linuxefi /vmlinuz root=/dev/sda2
grub> initrdefi /initrd.img
grub> boot

f:id:yuzutan_hnk:20170217014445j:plain:w300

linuxefi コマンドで、起動するLinuxカーネルを指定します。今回はシステムルートにある vmlinuz というシンボリックリンクを使って指定しています。これがない場合は、 boot/ 以下にある本体をフルネームで指定します。

同時にシステムのルートを指定します。 root=/dev/sda2 の部分です。多くのサイトの解説では、今回のような (hd1,gpt2) をルートとしたい場合、 /dev/sdb2 とするのが一般的だそうです。たしかに (hd0) があるので、それを sda とすると、 (hd1)sdb になります。理屈はわかります。

しかし、 それでは起動しませんでした 。憶測ですが、もしかしたら起動時にカーネルからデバイスの再スキャンが行われて、カーネルが認識した順番がGrubの認識した順番と違う、という現象が起こっているのかもしれません。全く分からない。

次に initrd コマンドですが、どうもモジュール等を含むデータをメモリに読み込むためのコマンドのようです。よくわかりませんが、これも initrd.img というシステムルートにあるシンボリックリンクを使います。ない場合は、 boot/ 以下にある本体をフルネームで指定します。

最後に boot コマンドでブートさせます。

f:id:yuzutan_hnk:20170217014446j:plain:w300

ちなみに、UEFI環境でない場合は、efiがついていないコマンドでブートします。UEFIでも使えるので、実質どちらでもいいのでは…?

grub> set root=(hd1,gpt2)
grub> linux /vmlinuz root=/dev/sda2
grub> initrd /initrd.img
grub> boot

最後に

f:id:yuzutan_hnk:20170217014447j:plain:w300

無事に起動できました。grubのシェルを直で触るなんて初めてで、いろいろ試行錯誤して、起動できたときは本当に感動しました。大歓喜でした。

今回は本当に 起動できればいい というスタンスなので、あまり深入りはしませんが、これで調べるべき対象が一つ増えた気がします。

やっぱりLinuxは楽しい(まだ何もわかっていないのに)。

C#でJavascriptみたいな即時関数を実行する

個人的には、「即時関数」より「即時構文」というほうがしっくり来ます。

名づけ親の本では「即時関数パターン」とされているので、それでもしっくり来ます。

「即時関数」じゃ、少し足らない気がします。

Javascriptでよく使われる即時関数について

まず即時関数というものが何者かを解説します。

ずばり、匿名関数をその場で実行する書き方です。

普通は関数を別に定義して、名前()で呼び出すのが普通ですが、Javascriptでは関数が立派なオブジェクトですので名前をつける必要がありません。そこで、名前を付けずにその場で実行してしまおうという書き方になります。なんとも野心的。

書き方

例1:

var x = (function() { return 1; })();

上記では、xに 1 が代入されます。つまり、function { return 1; }が実行され、その結果が代入されます。

例2:

var x = (function(num) {
    return function(innNum) { return innNum * num; };
})(5);

上記では、xにはfunction(innNum) { return innNum * 5; }が代入されます。

一見「なんだこれ?」ですが、中でfunctionを返しているというところに注目してください。

中のfunction文は即時関数を使っていないので、実行されません。すなわち匿名関数の定義のみ行います。

そして、Javascriptでは匿名関数の定義は評価時に行われるという特徴があります。今回の場合、クロージャの影響でnumに 5 が入っていることを参照してから定義するため、返される匿名関数内のnumは 5 に固定されるのです。

C#で書く

C#は最近、匿名関数だのラムダ式だの、Javascript関数型言語パラダイムを意欲的に取り入れているため、同じように即時関数を書くことができるようです。

Javascriptで挙げた2つの例を、C#で書き直してみます。

例1:

int x = new Func<int>(() => 1)();

C#Javascriptと違って静的な型付け言語のため、匿名関数も型にはめてあげないといけないようです。new Func<int>を省略すると「メソッド名が必要です」というコンパイルエラーが発生します。

例2:

Func<int, int> x = new Func<int, Func<int, int>>((num) => new Func<int, int>((innNum) => innNum * num))(5);

Func<>がとんでもなく多い。さらに、引数と戻り値と型をいちいち指定しなければならない。この場合はちょっと無理がありそうですね。

関数を返すような処理をしなければ、一応使えそうな書き方ですね。

初心者が初めてグラボを取り付けたお話

去年の暮れ、はじめてグラフィックボードなるものをパソコンに取り付けました。

理由としては、年賀状の作成に使っているInkscapeのフィルタ機能が極端に重いので、

「もしかしてグラボつけたら速くなるんじゃね?」

という安直な思いの元、購入したのでした。

結果速くはならなかったんですけど。

初めてグラボを買う

今回、購入したグラボはこちら↓

f:id:yuzutan_hnk:20161224125551j:plain:w300

GeForce GTX 1050です。Tiではないのでビデオメモリは2GBです。1050にしては特徴ガン無視の補助電源ありのモデルです。

価格として15000円強。普通の1050なら10000円ちょっとで買えるものなのですが、HDMIが3本出力できるというところで選びました。今のところ2つしか使いませんが、ちょうど2つなんでいうグラボはなかったんですね。

早速開ける

f:id:yuzutan_hnk:20161224125539j:plain:w300

半分くらいが発泡スチロール。そして本体は黒みがかったビニールに包まれています。

f:id:yuzutan_hnk:20161224125528j:plain:w300

取り出しました。まあまあの重さです。

大きなファンとごついプラスチックケース。この中に基盤が収まっているんですね。

グラボって無駄にかっこいいよね

f:id:yuzutan_hnk:20161224125503j:plain:w300

たしかにHDMIが3本、DisplayPort、DVIがあります。すべてキャップが付いてます。

これがパソコンの背面に出てくる形になるんですね。

取り付け準備

まず、PCケース背面にあるカバーを取り外します。

f:id:yuzutan_hnk:20161224125931j:plain:w300 f:id:yuzutan_hnk:20170104010713j:plain:w300

ねじで止まっているので、ドライバーを使って外します。外すと、外側から中が見えるようになります。

f:id:yuzutan_hnk:20161224125909j:plain:w300

まずは練習

初めてパソコンにパーツを増設するため、一応練習も兼ねて小さな基盤も購入しました。

f:id:yuzutan_hnk:20161224125516j:plain:w300

こちらはPCI Express x1に指すとUSBの口が4つ(?)増えるものです。背面にAポートが2つ、そしてヘッダが1つ増えます。もともとのUSBの口が足らなくなってきたのもあったので、購入しました。

f:id:yuzutan_hnk:20161224125856j:plain:w300

端子をあわせて……

f:id:yuzutan_hnk:20161224130440j:plain:w300

挿す。

すると、背面にちょうどUSBの端子が見えるようになります。

f:id:yuzutan_hnk:20161224130426j:plain:w300

最後に、このUSB基盤は補助電源にSATAの口があるため、ハードディスク用のSATA電源をつなぎます。そして、先ほど外したねじでカバーの部分を固定します。

f:id:yuzutan_hnk:20161224130411j:plain:w300 f:id:yuzutan_hnk:20161224130357j:plain:w300

案外、簡単じゃない?

グラボを取り付ける

次にグラボを取り付けていきます。

グラボも同じ要領で、端子を合わせて挿すだけです。ただし本体が分厚いため、斜めにして確認することは難しいです。また先にUSB基盤を取り付けてしまったため、横から確認することも難しいです。

とりあえず勘で挿したらうまくいきました。なんとか。

f:id:yuzutan_hnk:20170104010915j:plain:w300

PCI Express x16 は1つしか使いませんが、背面は2スロット使います。

f:id:yuzutan_hnk:20161224130921j:plain:w300

グラボの補助電源は、PCI Express 補助電源コネクタを使用します。

基本的に6ピン構成、電力が大きいものだと8ピン構成のものもあります。

f:id:yuzutan_hnk:20161224130859j:plain:w300

端子を合わせて…

f:id:yuzutan_hnk:20161224130848j:plain:w300

挿します。

グラボもカバー部分をねじで止めます。

f:id:yuzutan_hnk:20161224130837j:plain:w300

以上で完成です。繋ぐだけです。本当に簡単ですね。

ドライバインストール

ハードが完成したので、今度はソフト側を構成します。

グラボを挿すと、自動的にビデオ出力はグラボ側から出るようになります(オンボードは使えなくなります。使える場合もあるそうですが)

ただし初回はドライバがインストールされていないため、互換モード(640x480?)で出力されます。

f:id:yuzutan_hnk:20161224131244j:plain:w300

このような付属のCDがあればそこから、インターネットにつながっていれば公式サイトから落とすことができます。

が、ここで予想外の出来事が。

f:id:yuzutan_hnk:20161224131259j:plain:w300 f:id:yuzutan_hnk:20161224131328j:plain:w300 f:id:yuzutan_hnk:20161224131344j:plain:w300

自動でドライバを認識してくれた!!

このあと、再起動すると普通にネイティブ解像度で出力され、環境構築が完了していました。うーん、なんだか微妙な気持ち…

ベンチマーク

Plag and Playの影響なのか何なのかはわかりませんが、すぐに使えるようになったので早速ベンチマークします。

今回使ったのは、ドラゴンクエスト10PSO2ベンチマークソフトです。

ゲームが本来の目的ではないので、一応簡単にできればいいかなと…

ドラクエ10

  • 設定
    • 画質 : 最高画質
    • 解像度 : 1920x1080
    • フルスクリーン
  • その他スペック
    • CPU : i7-6700
    • メモリ : 16GB

↓(装着前) オンボード Intel HD Graphics
f:id:yuzutan_hnk:20161218021142p:plain:w300

↓(装着後) GeForce GTX 1050
f:id:yuzutan_hnk:20161218021141p:plain:w300

結構上がりましたね。

ドラクエ10はそもそも、オンボードでも結構快適に動いていたんですよね。スコアはスコアですが、結果としても「標準」として出ているように、そこまで高スペックを求めなければ十分遊べそうな環境でした。

しかし、やはりグラボを載せると安定さが違いますね。ムービーの見た目はそこまで変わりませんでしたが、たぶん戦闘中などパーティクルが多くなった時のカクツキが少なくなる(というかなくなる)気がします。

PSO2

  • 設定
    • 画質 : 6
    • ゲーム設定-描画設定 : すべてON
    • ゲーム設定-詳細設定 : 50(max)
    • 解像度 : 1920x1080
    • フルスクリーン
  • その他スペック
    • CPU : i7-6700
    • メモリ : 16GB

↓(装着前) オンボード Intel HD Graphics
f:id:yuzutan_hnk:20161218021144j:plain:w300

↓(装着後) GeForce GTX 1050
f:id:yuzutan_hnk:20161218021145j:plain:w300

破壊的に変わってますね

オンボードだとFPSが常時20前後、影や反射などの描画がおかしかったりと、とてもゲームができる環境ではありませんでした。

しかしグラボをつけると、もうカクツキから完璧に解放されたと言えます。すべてのエフェクトをつけてもコマ落ちすることはなく、常にFPS120あたりを推移していました。どうせ今のディスプレイは最高60Hzあたりでしょうから、申し分ありません。

結果

グラフィック性能はものすごく上がりました。これでゲームははかどりそうです。

しかし、実際にInkscapeで年賀状を作るときの処理は、まったくもって変わりませんでした。あれはCPUに描画処理をすべて任せているのでしょうね。

同じように、いつも遊んでいるK-shoot maniaという音ゲーも、DirectXを使ってはいますが肝心の譜面部分がソフトウェア描画になっているので、全く変わりませんでした。

やっぱり、普段からゲームをしない人にとっては不必要なんでしょうかねぇ…。

Windows 8 に Office 2003 をインストールするとたまにクラッシュする話を調査した

サポートされていない組み合わせに対してああだこうだ言うことはしませんが、ただ一つ言えるのは、Office2003が今でも現役だということ。

基本的にフロントエンドの方や個人の方は、絶対にOffice2016かOffice365を買ったほうが得です、というか無茶しないでください。苦しむ目にあいますから。

症状と原因

当方の環境は、Windows 8.1 Pro に Office 2013 がインストールされています。

症状は、.NET製品で「ファイルを開く」ダイアログを開いたときにクラッシュするという現象です。ExcelVisual Studioが落ちます。

気づいたのは、Excel VBAでマクロを作っていたとき。まれにGetOpenFilename関数でダイアログを開いているところで落ちてしまうことがありました。

最初のことろはちょっとしたバグか不具合かと思っていたのですが、10回の起動に1回くらいのペースで落ちるようになりました。

そこでイベントビューワで調べてみたら、MSONSEXT.DLLというライブラリで落ちていることが判明。

さらに、以前から現象があった Visual Studio 2013 で Visual Studio Installer Project 編集する際に使う「ファイルを開く」ダイアログでも不定期に落ちることがある問題も、同じライブラリで落ちていることが判明しました。

Access 2003をインストールしていた

問題のファイルはC:\Program Files (x86)\Common Files\Microsoft Shared\Web Folders\の下にあるもので、更新日時が2003年の7月、バージョンが11.0.5510.0でした。

そしてその付近(同階層)にあるフォルダー作成日時が、私がAccess 2003をインストールした日時と一致していたのです。とある案件でAccessが必要になったため、カスタムインストールしていたものです。

本当はOffice 2013のAccessをインストールするのが正解なのでしょうが、実際に納品する環境で使うものがAccess 2003だったため、互換性を重視してOffice 2003にしたのです。

なお、Office 2003 は Windows 8 をサポートしておらず、互換性はありません。

このファイルについて調べてみると、とくにShare Pointなどに使われているDLLで、Windows Server 2003Windows XP向けのWindows Updateに更新が含まれていました。Windows Vistaも対象のようです。

https://support.microsoft.com/ja-jp/kb/907306

また、Windows 2000Windows Me、Windows 98などにも環境によっては含まれることがあるみたいで、これについてもWindowsUpdateがある情報もありました。Windows MeにWindows Updateなんてあったかなぁ…

https://support.microsoft.com/ja-jp/kb/892211

イベントビューワの情報

すべては写すことができませんでしたが、要約すると以下の通り。

  • エラーを起こしたモジュール:MSONSEXT.DLL(ver. 11.0.5510.0)
  • 例外コード:0xc0000005
    • これ以外は基本的に出てきません
    • 調べてみるとメモリ違反だそうです
  • 障害オフセット:0x00052b570x000534d50x00052b040x0005136cなど

同じ問題をもている方々の情報

同じような症状を訴えている方を、英語圏ですが発見しました。

Microsoft word crashing

ここで3人の方が同じ症状を訴えられています。英語を読めないながら解読しましたが、状況はほぼ同じようです。

  • 1人目(質問者)
    • Windows 8 Pro
    • Office 2010 インストール済み
    • Visio のみ 2003 がインストールされている
    • Word 2010 がMSONSEXT.DLLを原因にしてクラッシュする(ときどき)
    • 体験版の Visio 2013 をインストールしたが改善せず
  • 2人目
    • Windows 8 Pro
    • Office 2010 インストール済み
    • Visio のみ 2003 がインストールされている
    • Word 2010, Excel 2010, Powerpoint 2010 でファイルを開こうとするがMSONSEXT.DLLを原因にしてクラッシュする
  • 3人目
    • Windows XP
    • Office 2002 インストール済み → アンインストール
    • Office 2010 インストール
    • 1人目、2人目と同じ問題で Excel 2010 と Word 2010 がクラッシュする
    • Office 2002 をインストールした時点でMSONSEXT.DLLが配置されることを確認
    • Windows 7 のパソコンに Office 2010 のみインストールすると、MSONSEXT.DLLは配置されないことを確認

要は Office 2003(2002) が悪いんですよね、Office 2010 以降と互換性がないんですよね。

結局

今のところ、解決はしていません。もう案件は終わりましたが、今は別の案件の処理にPCを使っているため、変なことをして環境を壊したくないのです。

情報から察するに、アンインストールしてもファイルは残り、根本的な解決はできないみたいです。

ただ、Office 2003 のインストールで配置されるということは、必要なファイルなのでしょうね。

また、ファイルを開くダイアログでクラッシュするということは、シェル拡張に近いものなのかなとも考えていて、そのまま消してしまってはいけないのかと思ってしまいます。

どのように解決したらいいんでしょうね…。どなたか知っている方がいらっしゃれば教えていただきたいです……。

セキュリティ オプション と サインイン オプション

という名前の画面が、Windowsにはあるらしい。

というか、あった。

メモリが足らないと表示できない画面

今日は本職で使っているWindows 8.1のPCで、事は起きました。

メモリを4GBしか積んでいないPCで、既に使用量が3GB近い中、2.7GBのShiftJISのテキストファイルをSublime Textで開きました。

案の定、フリーズするわけで、まずSublime Textが応答なしになり、次にChromeも応答なしに、さらにExplorerと…まあ、予想通り速いペースでメモリをむしばみました。

で、さすがに無理だなと思いまずCtrl + Shift + Escを押しました。タスクマネージャを直で起動するショートカットです。

まあ、出ませんよね。

なので一応と、Ctrl + Alt + Delを押しました。

しばらくすると画面が暗転し、いつもの「タスクマネージャ」や「ロック」といった文字だけ出てくる画面が表示されるかなと思いきや、

サインイン プロセスで Ctl + Alt + Del キーが押されたときに、セキュリティ オプションとサインイン オプションが表示できませんでした。 Windows が応答していない場合は、ESCキーを押すか、電源スイッチを使って再起動して下さい。

というウィンドウが。

え、あの画面、「セキュリティ オプションとサインイン オプション」なんていう名前だったの?

Windows NT で 変わった Ctrl + Alt + Del

そもそも、このCtrl + Alt + Delなんですが、Windowsだけのキーコンビネーションではないらしい。

Control-Alt-Delete - Wikipedia

かなり昔からあるみたいで、伝統的なキーコンビだそうです。

当初の意味としては、要するに再起動。ソフトリブートとかハードリブートの違いはあれど、押せば再起動するよっていう非常用のコマンドだったらしい。

MS-DOS、その後継であるWindows 9x系もその流れを受けていて、2回押すと再起動する。いやMeで何度も押したけど再起動した試しはありませんでした。

Windows NTは、このキーコンビに「セキュリティオプション」というダイアログが割り当てられるみたい。XPのログイン時に求められたことがあります、このキーコンビ。

Wikipediaの内容から考えるに、winlogon.exeがプロセスID:4で起動していて、Windowsカーネルがそれに対してのみこのキーコンビイベントを発するので、偽装された画面ではこのキーコンビを受け付けることができないから安全だよっていうことなんですね。んなもんサラリーマンにわかるか。

winlogon.exeが画面を出せなかったということ、すなわち

今回のようにCtrl + Alt + Delが押されたとき、winlogon.exeセキュリティ オプションとサインイン オプション画面を出すようです。

ってことでwinlogon.exeも調べてみました。

1週間で学ぶIT基礎の基礎 - Windowsユーザーのためのワンポイント・レッスン 第62回:ITpro

@IT: BOOK Preview:インサイドMicrosoft Windows 第4版 上 第2章 システムアーキテクチャ 2.4.7 システムプロセス/まとめ

Winlogon - Wikipedia

Systemプロセスの子の子であり、さらにログイン画面を出すプロセスということです。手持ちのWindows10でSystemExplorerというソフトを使うと、Systemの子、つまりSystemが直接呼んでいる形になっています。

Systemカーネルとほぼ同義(カーネルの仮想プロセスの一つ)なので、そこから呼ばれるsmss.exewinlogon.exeはかなり上位のプロセスですね。

しかし、カーネルレベルではなく、ユーザレベルのプロセスだそうです。

ここからはあくまで推察ですが、winlogon.exeがユーザレベルのプロセスのため強制的な権限は持っていないのではないか、と考えます。

カーネルから直接呼ばれるとも見て取れる非常に信頼できるプロセスですが、強制的な権限を持っていない以上、同列のユーザレベルプロセスがCPU・ディスク・メモリを独占してしまうと、他と同じように止まってしまうということなんでしょうね。

すると、Ctrl + Alt + Delを押しても処理ができず、止まってしまい最初のようなメッセージが表示される。

……本当にこんな設計だったんでしょうか?間違っていたらご指摘ください。

備忘録として

ちなみに今回はただメモリを食い荒らしただけで、CPUにとんでもない負荷をかけたわけではなかったためすぐになんとかなりました。

しかし次は、無限ループなどで止まってしまうこともあるかもしれない…。そうするとCPUを独占してしまって、本当にwinlogon.exeがびくともしなくなる可能性も、ある。

今度から、Windows NT系のCtrl + Alt + Del非常用と信用しないようにしよう。

そして、あの画面は「セキュリティ オプションとサインイン オプション」という名前の画面だということも、覚えておくことにします。

2016/12/20 タイトルがうろ覚えだったものを修正

初見でPCに引き出しを取り付けた件。

パソコンの中身を一度も確認せずにパーツを買うのはよしましょう、っていう備忘録。

7月ごろに買ったメインのPCなんですが、ATX用の一番大きいサイズのケースにほとんど何も載せず、 CPU:i7-6700 + メモリ:16GB + グラボ:なし + HDD:1TB + ドライブ:DVDという超基本的なスペックなんですね。

というわけでHDDスロットもドライブスロットもがら空きで、3,4枚スペースがあったんです。

前々から5インチドライブベイにつけられる引き出しがパーツショップに出ているのを見て、 机の上に散らかっているUSBメモリなんかを仕舞えたらいいなと思ってたんです。

でもまあ、グラボを載せるときとか一度中をあけて確認してから買おうと考えてはいました。一応。

アイネックス 5インチベイ小物入れ BB-03

この間、サブPC用のHDDを買ったときに、ついノリで買ってしまったんです。サブPCの中しか見ていないのに。

ねじ穴を確認し忘れた。

恥ずかしながら、5インチベイってねじ穴が上段と下段とついているんですね。

今まででもパソコンの中身を見たことはありましたが、5インチベイのねじ穴まではしっかりと確認したことがありませんでした。今回初めて、その事実を知りました。

で、何がいけなかったのかというと、パソコン側に上段のねじ穴しか開いていなかったという問題があったのです。

買ってきた引き出し側には上段と下段、両方の穴は開いているものの、付属のねじ4本に対応するねじ溝が付いている穴が下段のみで、上段の穴にはついておらず、自前でタッピングネジを買ってくださいと箱に書いてありました。

たぶんいいだろうと高を括っていたのですが、いざ開けてみると。

f:id:yuzutan_hnk:20161213011527j:plain:w300f:id:yuzutan_hnk:20161213011536j:plain:w300

下段の穴がない。片面にはあるにはあるけど、前側の穴は細い。

大きいほうの穴に合わせると、カバーがつけられるくらい引っ込んでしまいます。

f:id:yuzutan_hnk:20161213011507j:plain:w300f:id:yuzutan_hnk:20161213011457j:plain:w300

これはさすがにタッピングネジを買わなきゃいけないと思い、近所のホームセンターに走りました。いや歩いたんだけど。

丁度いいねじが売っていない。

ホームセンターについたはいいんですが、街中の小さめのホームセンターでして、スーパーくらいの広さしかない中に園芸品から日用品まで広く浅くおいている店舗だったんです。

目分量で太さ3mm~4mm、長さ5mm~6mmくらいが丁度いいだろうなと考えていたのですが、いざ売り場を見ると丁度いいのがない。

一番近いのは3mm×8mm。いや、長い。さすがに貫通する。

それより短いのは2.3mm×6mm。今度は細すぎる、いくらなんでも細いし、頭も小さい。

とりあえず両方とも購入しましたが、どう考えても後者は貧弱なので使わないことにして、3mm×8mmを使いました。しかし、しっかり泊まるところまで回すと案の定貫通しました……。

最終的にねじを加工した

家に電気工事を仕事にしている人がいるもので、たまたまその人がふらっと通りかかったので事情を話すと、

「ねじの先を切って短くすればいいんじゃないか」

という答えが返ってきました。たしかにタッピングネジが必要とはいえ、引き出しには既にガイド穴が開いているため先がとがる必要性はないので、削ってしまって問題はなかったのです。

ねじの先を切るなんてしたことないし、握力30kg前後の私が鉄の塊を切れるわけがないので、2000円のワインと引き換えにその人にやってもらいました。ペンチで切断するだけですが、相当大変そうでした。

ちなみに、ペンチをしっかりとあてて柄をトンカチで叩けば、非力でも切れるそうです。そりゃそうだ。

完成

というわけでなんとか無事に取り付けられることができました。

f:id:yuzutan_hnk:20161213012902j:plain:w300

いざ使うとめちゃくちゃ便利。もう一つくらいつけてもいいかもしれません。

しかし、次はこんな失敗はしないように、自分の物はしっかり把握するべきですね。はぁ。