ThinkPad X1 Carbon (2016) をポチった

Surface Book か、 X1 Yoga か、それとも・・・って悩んだあげく、ThinkPad X1 Carbon という何の珍しさもないモデルを購入することにしました。
決め手は、軽さですかね。
Surface Pro 3 + キーボードで、1.1kg 程度なので、それ+αぐらいの重さっていうのは非常に良いなぁと。

いや、 2-in-1 は既に持ってるし、タッチ欲しい?って言われると、要らないし、有機ELは良いなぁ・・・でも、夏かぁ。あと焼き付きどうなんだろう?的な感じでいくと、やっぱり X1 Carbon です。

英語キーボード

英語キーボードSurface Book では個人輸入しかなかったのは辛かった部分です。それでもここのところの急激な円高で、国内モデルを買うよりもむしろ安く済む事、そして、MSの補償もどうやら国際保障であることなど、 Surface Book でも良いかなぁと思ったのは事実です。

サポート(補償・修理)のコスト

あとは、なんだかんだ言って、修理とか考えると、 Microsoft は未知数。
レノボの「オンサイト修理」は、やっぱり凄いです。人件費の超安売り。こんな金額で何度も来て貰って悪いなっていうレベルです。
「2年間 翌営業日オンサイト修理+アクシデント・ダメージ・プロテクション」でも、2万円強なんですよ。
高い買い物なんですから、むしろ、これは付けるべきです!
弊社社員が、Mac Book Pro の液晶を割ってしまって、9万円弱の請求書とか持ってきたのを考えると、サポートが安いのは圧倒的に正義です。

価格

あとは、当然ですが、価格ですね。
近年のハイエンドモデルは、30万超えが当たり前的な風潮はちょっと辛いです。
個人的には、PCの個人的減価償却は、2年、つまり、24ヶ月なので、1万円/月でいうと、だいたい、24万円前後だと、妥当なラインです。
Surface Book だと、36万円前後なので、1.5万円/月。それだけの価値があるかどうかの見極めは難しい。
X1 Carbon も高いのですが、レノボは探せばクーポンはいくらでもっていう状況なので、ハイエンドパッケージでも24万ぐらいには収まります。

ポチったモデル

ということで、ポチったモデルは基本的には、全部入り。英語キーボードって所で、あとは、ACアダプタを、65Wのスリムタイプに変えてみたぐらいです。

出荷は、3/4予定との事ですが、レノボさんのこの辺のコントロールはまるで信用ならないので、まぁ、どうなるかなってところです。

プロセッサー インテル Core i7-6600U プロセッサー (2.60GHz, 4MB)
初期導入OS Windows 10 Pro 64bit
導入OS言語 Windows 10 Pro 64bit - 日本語版
ディスプレイ 14.0型WQHD液晶 (2560x1440 IPS)
メモリー 16GB LPDDR3 1866MHz (オンボード)
グラフィックス インテル HD グラフィックス 520
キーボード 英語キーボード (バックライト付)
指紋センサー 内蔵指紋センサー
内蔵 カメラ カメラ(HD 720p対応)あり 、マイクロフォンあり
セキュリティーチップ TPMあり
ハード・ディスク・ドライブ 512GB ソリッドステートドライブ (PCIe-NVMe)
ポインティングデバイス ThinkPadクリックパッド
バッテリー 4セル リチウムイオンバッテリー (52Wh)
電源アダプター 65W スリムACアダプター
ワイヤレス インテル Dual Band Wireless-AC 8260(2x2) + Bluetooth 4.1 vPro対応
ワイヤレスWAN WWANなし
ディスプレイタイプ 14.0型WQHD液晶 (2560x1440 IPS) カメラあり、マイクロフォンあり、WiGig非対応
付属品 日本語
パッケージ リテールパッケージ
Onelink+ アダプター ThinkPad OneLink+ イーサネットアダプター
標準保証 1年間 引き取り修理
2年間 翌営業日オンサイト修理+アクシデント・ダメージ・プロテクション

ファイルに対する画像を取得する(WPF)

WPFで、単にファイルに対する画像を取得しようとしたのですが、たったこれだけのことにも関わらず、
意外と、どこにもソースが落ちていない。

もちろん、 WindowsAPICodePack を使えば、簡単にできるんですが、大なた過ぎて、ちょっと・・・っていう感じだったり、あるいは、サムネイルを生成したいわけで、アイコン画像が欲しいわけじゃ無いとか、そういうカスタマイズ性がないみたいで、ちょっと困る感じ名部分があります。

というか、C++ならまだ良いんですが、C#だと、IExtractThumbnailとの戦いで消耗している人達ばっかりで、???って感じでした。

ということで、 IShellItemImageFactory を使ってサックリと作ってみました。

using System;
using System.IO;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Media.Imaging;

namespace FileThumbnailExtractor
{
  public static class FileThumbnailExtractor
  {
    [StructLayoutAttribute(LayoutKind.Sequential)]
    struct SIZE
    {
      public int cx;
      public int cy;
    }

    [Flags]
    enum SIIGBF
    {
      RESIZETOFIT = 0,
      BIGGERSIZEOK = 1,
      MEMORYONLY = 2,
      ICONONLY = 4,
      THUMBNAILONLY = 8,
      INCACHEONLY = 0x10,
      CROPTOSQUARE = 0x20,
      WIDETHUMBNAILS = 0x40,
      ICONBACKGROUND = 0x80,
      SCALEUP = 0x100,
    }

    [ComImport, Guid("bcc18b79-ba16-442f-80c4-8a59c30c463b"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
    interface IShellItemImageFactory
    {
      [PreserveSig]
      void GetImage(SIZE size, SIIGBF flags, out IntPtr phbm);
    }

    [DllImport("shell32"), PreserveSig]
    extern static void SHCreateItemFromParsingName([MarshalAs(UnmanagedType.LPWStr)] string pszPath, IntPtr pbc, [In] ref Guid riid, [MarshalAs(UnmanagedType.IUnknown)] out object iunk);

    [DllImport("gdi32")]
    extern static int DeleteObject(IntPtr hObject);

    public static BitmapSource GetThumbnail(string fileName, int desiredWidth, int desiredHeight, ThumbnailType type)
    {
      object iunk = null;
      IntPtr hBmp = IntPtr.Zero;
      try
      {
        var IID_IShellItemImageFactory = new Guid("bcc18b79-ba16-442f-80c4-8a59c30c463b");
        SHCreateItemFromParsingName(fileName, IntPtr.Zero, ref IID_IShellItemImageFactory, out iunk);
        var factory = (IShellItemImageFactory)iunk;
        SIIGBF flags = SIIGBF.BIGGERSIZEOK;
        if (type == ThumbnailType.Icon) flags |= SIIGBF.ICONONLY;
        else if (type == ThumbnailType.Thumbnail) flags |= SIIGBF.THUMBNAILONLY;

        factory.GetImage(new SIZE { cx = desiredWidth, cy = desiredHeight }, flags, out hBmp);
        var bmp = System.Windows.Interop.Imaging.CreateBitmapSourceFromHBitmap(hBmp, IntPtr.Zero, Int32Rect.Empty, BitmapSizeOptions.FromEmptyOptions());
        bmp.Freeze();
        return bmp;
      }
      finally
      {
        DeleteObject(hBmp);
        Marshal.ReleaseComObject(iunk);
      }
    }

    public static async Task<BitmapSource> GetThumbnailAsync(string fileName, int desiredWidth, int desiredHeight, ThumbnailType type)
    {
      return await Task.Run(() => GetThumbnail(fileName, desiredWidth, desiredHeight, type));
    }
  }

  [Flags]
  public enum ThumbnailType
  {
    Icon = 1,
    Thumbnail = 2,
    Any = 3,
  }
}

「リンク作成シェル拡張」を他の方にメンテしていただけることになりました

こばやん (@kobayan_tokyo) さんが、私が10年弱放置していた(わけでもないんですが)、「リンク作成シェル拡張」を GitHub でメンテナンスしていただけることになりました。

blog.clock-up.jp

一応、別に作った本人が責任逃れするとか、そういう意図は全然ありませんが、過去の経緯というか、私なりに考えたことだけはおさらいしておきます(僕自身忘れかけています)。

espresso3389.hatenablog.com

espresso3389.hatenablog.com

当時は、まだ、 SourceForge が今のような酷い状態になるとは全然考えてもなく、当時の判断が間違っていたのかどうかは全然分からないのですが、今なら断然、 GitHub ですよね。フォークも簡単ですし、私では想像も及ばないような面白い機能が追加されたりすることもあるのでしょうか?(ハードル上げすぎ)

「リンク作成シェル拡張」は、最初は、「シンボリックリンク作成シェル拡張メニュー」という名前で、昔の黒歴史を自分で引っ張り出したくはないので、ここにリンクは張りませんが、おそらく、作り始めたのは、2000年の頭ぐらいでしょうか(archive.org で探した感じでは、1.22beta が 2000/4/17)。

それが、15年以上も利用され続け、そして、他の方にいまだにソースコードに手を入れていただいている。物を作っている人間としては、こんなに嬉しいことはありません。

この記事をどう締めくくれば良いのか全然分からないんですが、ソースコードのメンテを自ら買って出てくれた、こばやんさんには、お礼をしてもしきれない気持ちですし、何よりも、使ってくださっているユーザーさんがいるんだということが嬉しいですし、何度もおんなじ事を書いてる感じになってきて終われない・・・

いやー、ソースコード公開してて本当に良かった。

Visual Studio 2015 Update 1 で C++ <experimental/generator> を試してみる

Visual Studio 2015 Update 1 のリリースノート関連を見ていたら、 Coroutine が動くぜ!っていう記事があったので試してみました。

blogs.msdn.com

コルーチンっていうのは、まぁ、C#でいう yield return で、C++でも yield っていうそのまんまの名前なんですが、rubyとかだとgeneratorって呼ばれている奴ですね。

乱数のジェネレーターを作ってみる

とりあえず、コードを書いてみます。今回は、無限に乱数を発生し続けるというだけのジェネレータを作ってみました。

#include <cstdio>
#include <random>
#include <experimental/generator>

auto random()
{
  std::mt19937 r;
  for (;;)
    yield r();
}

int main()
{
  for (auto r : random())
    std::printf("%d\n", r);
}

std::random_device を使って、延々と乱数を発生させ、それを yield で返しています。 C# だったら、乱数の範囲など、厳密には違いますが、

IEnumerable<int> random()
{
  var r = new Random();
  for (;;)
    yield return r.Next();
}

っていうのと同じです。C++の方が戻り値にも型推論が使えて auto って書けるので少し楽です。

コンパイル時の注意

上で参照しているページにもあるのですが、現時点(Update 1)では、若干の制約があって、 /SDL, /RTCx との互換性がないので、これらの設定はOFFにしないといけません。また、/await というオプションも必要です。

C/C++ All Options だと、次の3つの部分を調整する必要があります。

f:id:espresso3389:20151203013854p:plain

この、 coroutine っていう単語と、 generator っていう単語、そして、 await という単語が故意にちりばめられている様にも見えて、全部いっぺんに届いてやれることが増えすぎて楽しい感じです。

最適化の具合

f:id:espresso3389:20151203014129p:plain

さて、このプログラムを、とりあえず思いつく限りの最適化全開でコンパイルすると、本当は、次のコード相当ぐらいに最適化されると嬉しいのですが、どうなるでしょうか。

int main()
{
  std::mt19937 r;
  for (;;)
    std::printf("%d\n", r());
}

つまり、コルーチンは、完全に最適化で除去されて、mainにインライン化されたら嬉しいなぁっていう話です。

下のリスティングは、 /FAs で出力された asm に色々と分かりやすいように成形を加えたものですが、残念ながら、コルーチン系の関数はインライン化されないようです。

	push	ebp
	mov	ebp, esp
	push	ecx
	push	esi

	; _Ptr (esi) = [new generator]
	lea	ecx, DWORD PTR _$S1$8[ebp]
	call	?random@@YA?AU?$generator@IV?$allocator@D@std@@@experimental@std@@XZ

	; if (!_Ptr) return;
	mov	esi, DWORD PTR _$S1$8[ebp]
	test	esi, esi
	je	SHORT main_end
	; _coro_resume(_Ptr);
	mov	eax, DWORD PTR [esi]
	push	esi
	call	eax

	mov	esi, DWORD PTR _$S1$8[ebp]
	add	esp, 4

	; if (_Coro.done()) goto loop_end;
	cmp	DWORD PTR [esi+4], 0
	je	SHORT loop_end

	; if (!_Ptr) return;
	test	esi, esi
	je	SHORT main_end
	npad	7

loop:
	mov	eax, DWORD PTR [esi-8]
	push	DWORD PTR [eax]
	push	OFFSET $SG4294967238
	call	_printf

	; _coro_resume(_Ptr);
	mov	eax, DWORD PTR [esi]
	push	esi
	call	eax
	add	esp, 12

	; if (!_Coro.done()) goto loop;
	cmp	DWORD PTR [esi+4], 0
	jne	SHORT loop
	mov	esi, DWORD PTR _$S1$8[ebp]
loop_end:

	; if (_Ptr) _coro_destroy(_Ptr);
	test	esi, esi
	je	SHORT main_end
	or	DWORD PTR [esi+4], 1
	mov	eax, DWORD PTR [esi]
	push	esi
	call	eax
	add	esp, 4
main_end:

	xor	eax, eax
	pop	esi
	mov	esp, ebp
	pop	ebp
	ret	0

初期化の関数はさておき、内部では、

  • _coro_resume()
  • _Coro.done()
  • _coro_destroy()

などが呼び出されているようですね。機能的には、名前の通り、「次の要素を取り出す」、「イテレーションが完了したかどうか」、「コルーチンの終了処理」とまんまですね。

ということで、普通に書ける処理なら、普通に自分でループを書いた方が速いですね。
一方で、C# LINQ も遅いことは理解しつつも、やっぱり、その生産性の高さという部分で利用価値があるわけで、それがC++でも素直な文法で利用できるようになったことは大歓迎です。楽しくてしょうがない感じですね。

Aero-Snap による Window サイズの変更を検出する

GetWindowPlacement では、ウィンドウが通常の状態でのウィンドウ座標と、実際にウィンドウが、最大化されている(SW_MAXIMIZE)のか、最小化されている(SW_MINIMIZE)のか、あるいは、通常の状態(SW_SHOWNORMAL)なのかが取得できます。従って、普通なら、この関数を呼び出せば、特に何の問題もなく、ウィンドウの状態を取得することが出来ます。

ところが、 Windows 8 で導入された Aero-Snap ([Win] + 矢印キー)では、この関数では関知できない状態になります。
具体的には、ウィンドウの状態が、通常の状態(SW_SHOWNORMAL)であると報告されます。これだと、アプリは自分の状況を正しく認識できません。

なので、 Aero-Snap 状態であることを知るためにはもうちょっと小細工をする必要があります。答えをそのまま書くと、 SW_SHOWNORMAL と認識された場合には、 GetWindowRect を使って取得したウィンドウサイズと rcNormalPosition の値を比較すると、 Aero-Snap かどうかを区別できるようです。

bool isResizeByAerosnap(HWND wnd)
{
  // The function works because of the fact that:
  // When the window is resized by Aero-snap, GetWindowPlacement returns
  // SW_SHOWNORMAL but wp.rcNormalPosition is not identical to the size
  // returned by GetWindowRect.
  WINDOWPLACEMENT wp;
  wp.length = sizeof(WINDOWPLACEMENT);
  GetWindowPlacement(wnd, &wp);
  if (wp.showCmd != SW_SHOWNORMAL)
  	return false;
  RECT rc;
  GetWindowRect(wnd, &rc);
  return memcmp(&rc, &wp.rcNormalPosition, sizeof(RECT)) != 0;
}

まぁ、問題はそんなことよりも、 Aero-Snap 状態を再現する API がないことなんですけどねw