TeXで覗くシステムプログラミングの世界（えっ）

>   これは「[TeX ＆ LaTeX Advent Caleandar 2017](https://adventar.org/calendars/2229)」の25日目の記事です。
>   （24日目は [golden_lucky](http://note.golden-lucky.net/2017/12/texmarkdown.html) さん です。）


## 例によって前フリ

アドベントカレンダーもいよいよ今日が最終日ですが、何だか超絶アレなタイトルです。「**システムプログラミング**」って何なのでしょうか。チョットこちらの記事を見てみましょう。

  * [Goで覗くシステムプログラミングの世界]（プログラミング＋）

>   本連載では、一番最後の「OSの提供する機能を使ったプログラミング」をシステムプログラミングの定義として話をすすめます。

なるほど。そういうわけで、本記事では、**「TeXで、OSの機能を直接利用する」**という超絶アレな世界をチョット覗いてみます。ただし、対象とするOSはWindowsに限定します。つまり、**TeXでWindows APIを呼び出す**、という話です。

※本記事で扱ったソースコードは以下のリポジトリに収録されています。

  * [GitHub:zr-tex8r/tex-sysprog](https://github.com/zr-tex8r/tex-sysprog)

### TeX言語でWindows APIの呼出ができる？

**できません。**TeX言語はTeXのプリミティブしか呼び出せません。

アレ、じゃあ記事が成立しない……、いえ、心配ありません。この記事は「**TeX言語で**覗く～」ではなくて「**TeXで**覗く～」です。なので、**LuaTeX**でLua言語を使うことにすればよいのです（強弁）。

### LuaTeXでWindows APIの呼出ができる？

Luaでは、外部の（機械語の）実行ファイルを起動したり、機械語の拡張モジュールを読み込むことができます。過去（2012年）のアドベントカレンダーにこんなネタがありましたね[^tcshutwindown]。

  * [TeX で Windows をシャットダウンする件について]（マクロツイーター）  

ここで使われている拡張モジュール（shutwindown.dll）の実装では実際に「WindowsをシャットダウンするAPI」（`ExitWindowsEx`）を呼び出しています。

えっ、「そのshutwindown.dllはTeXで実装したのでもLuaで実装したのでもない（実際、C言語による実装です）ので、『LuaTeXでWindows APIを呼び出している』とは到底いえない」、ですか。確かにそうですね……。

### LuaTeXでWindows APIの呼出ができる！

それはそうとして、このLuaLaTeX文書を見てください。

```tex:hello.tex
% LuaLaTeX document
\documentclass{article}
\begin{document}
\directlua{
  local ffi = require("ffi")
  ffi.cdef [[
  int MessageBoxA(void *hWnd, const char *lpText,
      const char *lpCaption, unsigned uType);
  ]]
  ffi.C.MessageBoxA(nil, "Hello LuaTeX world!", "Test", 0)
}
\end{document}
```

  * ソース：[hello.tex](https://github.com/zr-tex8r/tex-sysprog/blob/master/hello.tex)

これを、`-shell-escape`付きで`lualatex`でコンパイルします。  
※プロンプトを`>`で表します。

```console
>lualatex -shell-escape hello
```

![sysprog-1.png](https://qiita-image-store.s3.amazonaws.com/0/14550/5383dcff-7b56-b69d-9f79-e2435413e48e.png)

おおっ、なんと、TeXなのにシステムのダイアログが出現しました。スゴイ！

何が起こったのでしょう。`-shell-escape`付きなので、何か外部コマンドを実行した？ でも`\directlua`の中のLuaコードをみてもコマンド行らしきものは見当たりません。代わりに、何やらC言語のようなコードがあって、そこにAPI名`MessageBoxA`が書いてあります。

### FFIライブラリ、スゴイ

これは、[FFI]という仕組を利用しています。LuaFFIは元々は[LuaJIT]​[^luajit]で用意されているライブラリで、LuaからのCの関数を呼び出す際の手続きを簡略化するためのものです。

通常、Luaから呼び出せるCの関数は「Luaの関数の実装であるもの」に限られます。従って、例えばWindows APIの`MessageBoxA`を呼び出すためには、あらかじめ「`MessageBoxA`を呼び出す『Luaの関数の実装』となるCの関数」を実装する必要があります。ところが、FFIの仕組を用いると、“橋渡し”のためのCの実装が不要になり、単に「関数の宣言のコード」をLuaコード中で指定するだけでCの関数が直接呼び出せるようになるのです。

本記事では、FFIの仕組を利用して、LuaTeXエンジン[^luatex]からWindows APIを呼び出す事例を紹介します。ただし以降では、LuaTeXのTeXエンジン（組版エンジン）としての実行ではなく**Luaインタプリタとしての実行**（[いわゆる“texlua”]）**に焦点を当てます**。

その理由は「FFIによるWindows API呼出」という手法について、“texlua”での利用についてより実用性があると考えているからです。TeX Liveに含まれるTeX関連の補助ツールは“texlua”で実装されています。TeX関連ツールはクロスプラットフォームである必要がありますが、LuaTeXの機能だと「Windowsだけ機能が足りない」という事例が時々生じます。「Windows API呼出」はそういう場合の解決になりえる手法だと考えているのです。

### 前提知識

  * フツーのLuaの知識。
  * フツーのC言語の知識。
  * ほんのチョットのWindows APIの知識（大丈夫です、私もほとんど知りません[^handaxe]）。
  * TeX言語の知識は全然サッパリ全く不要。
  * LaTeXの知識もほぼ不要（えっ）

## はじめてのFFI

まずは単純な呼出規約をもつWindows APIを利用して、FFIライブラリの使い方を説明していきます。

### 事例①：LuaだってプロセスIDしたい

PerlやRubyでは`$$`で現在プロセスのIDを取得できます。これをLuaTeXでやってみましょう。

```lua:pid.lua
local ffi = require("ffi")
-- 使用するAPIの宣言を行う
ffi.cdef[[//C言語(C99)ソース文字列
//// 型宣言(typedef等)
typedef unsigned long DWORD;
//// プロトタイプ宣言
//現在プロセスのプロセスIDを取得
DWORD GetCurrentProcessId(void); //戻り値:プロセスID
]]

-------- 使用例
-- 宣言したAPI関数を呼び出す
local pid = ffi.C.GetCurrentProcessId()
-- pidにはプロセスIDの数値が返る
print("Process Id = "..tostring(pid))
-- EOF
```

  * ソース：[pid.lua](https://github.com/zr-tex8r/tex-sysprog/blob/master/pid.lua)

このLuaプログラムを“texlua”で実行すると、現在プロセスIDが出力されます。

```console:実行結果（例）
>texlua pid.lua
Process Id = 9924
```

#### プロセスIDを得るAPI

ここでは[GetCurrentProcessId]というAPIを利用しています。このAPIの関数プロトタイプは次の通りです[^void]。

```c:C言語
DWORD GetCurrentProcessId(void);
```

Windowsプログラミングでは基本的な型（数値型、ポインタ型）について、独特な別名（typedef）を使う習慣があり、`DWORD`もその一つです。`DWORD`型の定義は以下の通りです。

```c:C言語
typedef unsigned long DWORD;
```

つまり、普通にC言語のプログラムを書く場合、`GetCurrentProcessId`のAPIを使うためには上記の2行を前もって宣言する必要があるわけです。（もちろんこの記述はヘッダファイルとして提供されているのでそれを読み込むのが通例でしょう。）

#### FFIの利用の手順

LuaTeXではFFIライブラリは`ffi`という名前のモジュールとしてプリロード[^loaded]されているので、これをロードします。

```lua:Lua
local ffi = require("ffi")
```

そして、先述の「`GetCurrentProcessId`を使うために必要なC言語の宣言のコード」の文字列を引数としてFFIの関数`ffi.cdef()`を実行します。これで実際に`GetCurrentProcessId`を使う準備が行われます。（つまり、FFIはC言語の“宣言”だけを解釈するパーザを備えているわけです。）

```lua:Lua
ffi.cdef[[//この中にC言語(C99)の"宣言"を書く
typedef unsigned long DWORD;
DWORD GetCurrentProcessId(void);
]]
```

kernel32およびuser32に属するAPI（`GetCurrentProcessId`も該当する）は`ffi.C`という名前空間（テーブル）の下に配置されます。従って、あとは`ffi.C.GetCurrentProcessId()`を普通のLua関数のように呼び出せば、Windows APIの`GetCurrentProcessId`の呼出が行われます。

```lua:Lua
local pid = ffi.C.GetCurrentProcessId()
```

ここで、`GetCurrentProcessId`APIの返した値が（`DWORD`の）9924だったとすると、Luaの`ffi.C.GetCurrentProcessId()`の戻り値は「Luaの数値型[^number-type]の9924」になります。このようにC言語のデータ型とLuaのデータ型の間は可能な限り“自然な形で”自動的に変換が行われます。従って、この後は変数`pid`が「C言語の関数から返った値」であることを意識する必要はありません。

### 事例②：五秒間待ってやる

Luaにはいわゆる“sleep”の機能がありません。これをWindows APIの`Sleep`で実現してみましょう。

```lua:sleep.lua
local ffi = require("ffi")
ffi.cdef[[
//// 型宣言(typedef等)
typedef unsigned long DWORD;
//// プロトタイプ宣言
//指定時間だけプロセス実行を中断
void Sleep(
    DWORD dwMilliseconds);      //入力:待機時間(ミリ秒)
]]

-------- 使用例
for count = 5, 1, -1 do
  print(count)
  ffi.C.Sleep(1000) -- 1秒待つ
end
print("Finish!")
-- EOF
```

  * ソース：[sleep.lua](https://github.com/zr-tex8r/tex-sysprog/blob/master/sleep.lua)

実行すると、5から始まるカウントダウンが1秒ごとに表示されます。

```console:実行結果
>texlua sleep.lua
5
4
3
2
1
Finish!
```

#### 引数をもつAPIを扱う

`Sleep`の関数プロトタイプは次の通りです。

```c:C言語
void Sleep(DWORD dwMilliseconds);
```

必要な宣言のコードを記した文字列を引数にして`ffi.cdef()`を呼ぶと、`ffi.C.Sleep()`が配置されます。ここまで先の例と同じです。ここで、「1秒待機する」には、`Sleep`APIの`DWORD`型の引数に1000を指定する必要がありますが、どうすればよいでしょうか。

先の例では関数の戻り値について「データ型は“自然に”自動変換される」ことを見ましたが、これは引数についても同様です。つまり、「Luaの数値1000」を与えれば、それが自動的に`DWORD`型に変換されます。簡単ですね。

```lua:Lua
ffi.C.Sleep(1000)
```

### 事例③：とあるOSの短形式名（ショートネーム）

Luaは現代のスクリプト言語の中では珍しく、言語自体ではUnicode文字を扱わず、文字列を常にバイト列として扱います。この性質のため、Windowsでの非ASCIIのファイル名の取扱は非常に厄介です。

#### ソレの名は。

※以下、ANSIコードページは932（日本語）であると仮定します。

WindowsのGUI操作ではファイル名に任意のUnicode文字を含めることができるため、コードページ932（CP932）にない文字のファイルは普通に存在しえます。例えば、ディレクトリ`C:\tmp\temp1`に次の名前のファイルがあったとします。

![sysprog-2.png](https://qiita-image-store.s3.amazonaws.com/0/14550/86218a5d-2019-47a0-9983-80d89026a66a.png)

  * `test file.txt`（ASCII文字のみ）
  * `test ☆彡.txt`（非ASCIIだがCP932にある文字を含む）
  * `test ☃♪.txt`（非ASCIIでCP932にない文字`☃`を含む）

ファイル名を引数にとるLuaの関数を使った場合、「Luaの文字列はバイト列」という性質上[^bytes]、ANSI版のAPIが使われることになります。従って、もしLuaスクリプトをCP932で書いているのであれば、非ASCIIのファイル名も期待通り取り扱えます。しかしこの場合、CP932にない文字のファイル名`test ☃♪.txt`はスクリプト中に書くことすらできません。

```lua:Luaスクリプト
--文字コードはCP932
lfs = require("lfs")
-- lfs.attributes()でファイルサイズを調べる
print((lfs.attributes("test file.txt", "size")))-->42
print((lfs.attributes("test ☆彡.txt", "size")))-->56
--3つ目のファイルの名前は書けない！
```

クロスプラットフォームでLuaスクリプトを作る場合は、スクリプト本体も、それに対する入力データ（を記したファイルなど）もUTF-8で書かれるでしょう。その場合は、非ASCII文字を含むファイルは（バイト列が食い違うため）全く見つけられなくなります。

```lua:Luaスクリプト
--文字コードはUTF-8
lfs = require("lfs")
print((lfs.attributes("test file.txt", "size")))-->42
print((lfs.attributes("test ☆彡.txt", "size")))-->nil (見つからない)
print((lfs.attributes("test ☃♪.txt", "size")))-->nil (見つからない)
```

要するに、CP932外の文字を含む`test ☃♪.txt`のようなファイル名はLuaの機能ではどうやっても扱えない、という困った事態になっているわけです。 

※ちなみに、RubyやPythonはCP932外のファイル名を扱うことができます。Perlはアレ（？）なようです。

#### 困ったときのショートネーム

この問題の（チョットアレな）回避策として、**ショートネームを使う**というものがあります。ショートネームはただ短いだけでなく、「ANSIコードページにある文字のみからなる」という性質があるからです。

  * `test file.txt`→`TESTFI~1.TXT`
  * `test ☆彡.txt`→`TEST☆~1.TXT`
  * `test ☃♪.txt`→`TEST♪~1.TXT`（CP932にない`☃`は入らない）

従って、ショートネームの`TEST♪~1.TXT`であれば、Luaの関数（ANSI版のAPIに帰着する）で扱えないという問題は避けられそうです[^perl-shortname]。

前置きが長くなりましたが、ここではWindows APIを使って、ファイルのショートネームを取得してみます。

#### 実際にショートネームしてみた

ファイルのショートネームを取得するAPIは、それ自身、ANSI版`GetShortPathNameA`とUnicode版`GetShortPathNameW`があります[^ansi-unicode]。先述の通り、Luaの文字列はバイト列なので、`GetShortPathNameA`を使う方が理に適ってそうです。

```lua:shortname-1.lua
local ffi = require("ffi")
ffi.cdef[[
//// 型宣言
typedef unsigned long DWORD;
typedef const char *LPCSTR;
typedef char *LPSTR;
//// プロトタイプ宣言
//短形式パス名を取得
DWORD GetShortPathNameA(        //戻り値:出力パス名のサイズ,0=失敗
    LPCSTR lpszLongPath,        //入力:長形式パス名
    LPSTR lpszShortPath,        //出力:短形式パス名
    DWORD cchBuffer);           //出力:lpszShortPathのサイズ
]]

--- 短形式パス名を取得.
-- @param lpath 長形式パス名
-- @return 短形式パス名
function short_path_name(lpath)
  local spath = ffi.new("char[?]", 512)
  local splen = ffi.C.GetShortPathNameA(lpath, spath, 512)
  if splen == 0 then return nil end -- 失敗
  return ffi.string(spath, splen)
end

-------- 使用例
print(short_path_name("test file.txt"))
-- EOF
```

  * ソース：[shortname-1.lua](https://github.com/zr-tex8r/tex-sysprog/blob/master/shortname-1.lua)

先ほどの`C:\tmp\temp1`ディレクトリで実行すると、正しいショートネームが表示されました。

```console:実行結果
>texlua shortname-1.lua
TESTFI~1.TXT
```

#### バイト文字列の扱い

`GetShortPathNameA`の関数プロトタイプは次のようになっています。

```c:C言語
DWORD GetShortPathNameA(LPCSTR lpszLongPath,
    LPSTR lpszShortPath, DWORD cchBuffer);
```

この中の`LPCSTR`と`LPSTR`は`char`のポインタの型であり、バイト文字列（CP932の場合は“マルチバイト文字列”）を表しています。このAPIに対応するLua関数`ffi.C.GetShortPathNameA()`の引数はどうすればよいでしょうか？

入力用の引数`lpszLongPath`（対象のロングファイルネーム）の方は簡単で、「Luaの文字列`lpath`」を与えれば済みます。これで、Cの関数では当該のバイト列が置かれたメモリ領域の先頭のポインタが渡されます[^luastring]。

これに対して出力用の引数`lpszShortPath`（結果のショートファイルネーム）については、内容が可変となるバイトのバッファを与える必要があります。つまり「普通のLuaの値」ではなく「FFI専用の特殊な値」が要るわけです。そのような特殊な値を生成するためのFFIの機能が`ffi.new()`関数です。

```lua:Lua
  local spath = ffi.new("char[?]", 512)
```

これで、512要素の`char`の“Cの配列”（512バイト）が確保[^destruct]され、その配列の先頭のポインタ（`char *`型）を表すユーザデータ[^userdata]が変数`spath`に代入されます。この`spath`を`lpszShortPath`引数（`char *`型）に渡せばよいわけです。

`ffi.C.GetShortPathNameA()`の実行が正常に完了すると、配列`spath`には結果のショートネームが格納された状態になっています。“Cの配列”はLuaで値が読み出せる[^carray]ので、ここから「Luaの文字列」を作り出すのは難しくありません。FFIではこの機能がライブラリ関数`ffi.string()`として用意されています。実際の使われ方を見ればその仕様は明らかでしょう[^ffi-string]。

```lua:Lua
  -- 配列spathの先頭のsplenバイトからなるLua文字列
  return ffi.string(spath, splen)
```

#### これで一件落着…しない

ところで、この節の最初で、ショートネームを取得する目的は「非ASCIIのファイル名を扱うため」としました。この目的は果たせているでしょうか？ 実装した`short_path_name()`を非ASCIIのファイル名に適用してみましょう。

```lua:Lua
--文字コードはUTF-8
print(short_path_name("test ☆彡.txt"))-->nil
print(short_path_name("test ☃♪.txt"))-->nil
-- EOF
```

うまくいきません！ よく考えてみると、「ANSI版のAPIが使われるから失敗する」という理屈なのに、独自に`GetShortPathName`のAPIを呼ぶときにANSI版を使ったのでは何の回避にもなっていません[^lfs-shortname]。ここはUnicode版の`GetShortPathNameW`を使うべきでした。でもUTF-8バイト列しか扱えないLuaでどうやって「Unicode文字列（ワイド文字列）」を作ればいいのでしょうか。やはり文字コード関連のAPIも検討しないといけないようです。

## もっともっとFFI

なんとなくFFIのキホンがつかめてきました。ここからはホンキで「FFIでシステムプログラミング」に取り組みましょう。

### 事例④：UTF-8は正義

クロスプラットフォームのためUTF-8で書かれたスクリプトでは、当然文字列はUTF-8のバイト列となります。これをそのまま`print()`等で出力するとWindowsでは文字化けしてしまうため、Windows用に端末出力コードページ（日本語Windowsだと通常こちらもCP932）に文字コード変換する関数を作成します。

※「Windows以外では端末はUTF-8に決まってるでしょ」という前提です。

```lua:console-1.lua
-- 文字コードはUTF-8
local ffi = require("ffi")
require("ffi_typedef")
ffi.cdef[[
//// プロトタイプ宣言
//端末出力コードページを取得
UINT GetConsoleOutputCP(void);  //戻り値:端末出力コードページ
//多バイト文字列をワイド文字列に変換
int MultiByteToWideChar(        //戻り値:変換後文字列の長さ,0=失敗
    UINT CodePage,              //入力:コードページ
    DWORD dwFlags,              //入力:文字種別フラグ
    LPCSTR lpMultiByteStr,      //入力:変換元文字列
    int cbMultiByte,            //入力:変換元文字列の長さ
    LPWSTR lpWideCharStr,       //出力:変換後文字列
    int cchWideChar);           //出力:lpWideCharStrのサイズ
//ワイド文字列を多バイト文字列に変換
int WideCharToMultiByte(        //戻り値:変換後文字列の長さ,0=失敗
    UINT CodePage,              //入力:コードページ
    DWORD dwFlags,              //入力:文字種別フラグ
    LPCWSTR lpWideCharStr,      //入力:変換元文字列
    int cchWideChar,            //入力:変換元文字列の長さ
    LPSTR lpMultiByteStr,       //出力:変換後文字列
    int cbMultiByte,            //出力:lpMultiByteStrのサイズ
    LPCSTR lpDefaultChar,       //入力:代替文字
    LPBOOL lpUsedDefaultChar);  // 出力:代替文字が使われたか
]]
Ccst = { -- 定数定義
    CP_UTF8 = 65001;            -- UTF-8のコードページ
}

--- 文字列を端末出力コードページに変換
-- @param str UTF-8文字列
-- @return 端末出力CP文字列(nil=失敗)
function costr(str)
  local istr = tostring(str)
  if #istr == 0 or ffi.os ~= "Windows" then return istr end
  -- UTF-8文字列→ワイド文字列
  local ustr = ffi.new("WCHAR[?]", #istr)
  local ulen = ffi.C.MultiByteToWideChar(Ccst.CP_UTF8, 0,
      istr, #istr, ustr, #istr)
  if ulen == 0 then return nil end -- 失敗
  -- ワイド文字列→端末出力文字列
  local cocp = ffi.C.GetConsoleOutputCP()
  local ostr = ffi.new("char[?]", #istr)
  local olen = ffi.C.WideCharToMultiByte(cocp, 0,
      ustr, ulen, ostr, #istr, nil, nil)
  if ulen == 0 then return nil end -- 失敗
  return ffi.string(ostr, olen)
end

-------- 使用例
print(costr("TeX言語危険、ダメゼッタイ！"))
-- EOF
```

  * ソース：[console-1.lua](https://github.com/zr-tex8r/tex-sysprog/blob/master/console-1.lua)

実行結果は以下の通りです。スクリプト中でUTF-8で与えた文字列が化けずに正常に出力されています。

```console:実行結果
>texlua console-1.lua
TeX言語危険、ダメゼッタイ！
```

#### 解説してみる

次のような方針をとっています。

  * `MultiByteToWideChar`を使って、UTF-8（コードページ65001）のマルチバイト文字列をUnicode文字列（ワイド文字列）に変換。
  * `WideCharToMultiByte`を使って、Unicode文字列を端末出力コードページ（これは`GetConsoleOutputCP`で取得する）に変換。

前段の変換では「ワイド文字（`WCHAR`型）の配列」を用意する必要がありますが、これは先と同様に`ffi.new()`で作成できます。

```lua
  local ustr = ffi.new("WCHAR[?]", #istr)
```

#### 注意事項

  * 「Windows特有のtypedef」を都度書くのは面倒なので、当該のtypedef全部を定義するためのモジュール`ffi_typedef`を作りました（ソース：[ffi_typedef.lua](https://github.com/zr-tex8r/tex-sysprog/blob/master/ffi_typedef.lua)）。取りあえず、カレントディレクトリに置いておけばよいでしょう。
  * `Ccst`はFFIとは無関係の単なるテーブルです。Windowsプログラミングで使われる定数を収めています[^constants]。
  * Unicode文字列の長さ[^unicode-char]はそれを表すUTF-8のバイト列の長さを超えないので、`ustr`の要素数は`istr`の長さとしています。
  * UTF-8よりもバイト列が長くなる文字コードはなさそうなので、`ostr`の要素数も同様にしています。
  * `ffi.C.WideCharToMultiByte()`の最後の2引数に`nil`を渡していますが、これはC言語側ではNULLポインタと解釈されます。
  * `ffi.os`はプラットフォームの種別を表す文字列です。これが`Windows`でない場合は入力バイト列を変換せずにそのまま返すことにすれば、「Windows以外ではUTF-8でそのまま出力」することになります。

#### 蛇足事項

入力の文字列に端末出力コードページ（CP932）にない文字が含まれる場合、この実装では当然そういう文字は正常に出力できません（代替文字`?`に化けます）。端末出力の部分をLuaの機能（`print()`）で行う限りはこれは避けられません。ところが、端末出力自体をWindowsのAPIで行うと任意のUnicode文字が出力できます。参考としてこれの実装例を挙げておきます。

  * ソース：[console-2.lua](https://github.com/zr-tex8r/tex-sysprog/blob/master/console-2.lua)

![sysprog-3.png](https://qiita-image-store.s3.amazonaws.com/0/14550/cf89160c-6eb0-2db9-163d-b67f4c69008e.png)

### 事例⑤：メイク・ファイル名・ショート・アゲイン

文字コード変換とワイド文字列の扱いが判ったので、もう一度、「ファイルのショートネームを取得する」ことに挑戦してみます。

```lua:shortname-2.lua
-- 文字コードはUTF-8
local ffi = require("ffi")
require("ffi_typedef")
ffi.cdef[[
//// プロトタイプ宣言
//短形式パス名を取得(Unicode版)
DWORD GetShortPathNameW(        //戻り値:出力パス名のサイズ,0=失敗
    LPCWSTR lpszLongPath,       //入力:長形式パス名
    LPWSTR lpszShortPath,       //出力:短形式パス名
    DWORD cchBuffer);           //出力:lpszShortPathのサイズ
//ANSIコードページを取得
UINT GetACP(void);              //戻り値:ANSIコードページ
//多バイト文字列をワイド文字列に変換
int MultiByteToWideChar(        //戻り値:変換後文字列の長さ,0=失敗
    UINT CodePage,              //入力:コードページ
    DWORD dwFlags,              //入力:文字種別フラグ
    LPCSTR lpMultiByteStr,      //入力:変換元文字列
    int cbMultiByte,            //入力:変換元文字列の長さ
    LPWSTR lpWideCharStr,       //出力:変換後文字列
    int cchWideChar);           //出力:lpWideCharStrのサイズ
//ワイド文字列を多バイト文字列に変換
int WideCharToMultiByte(        //戻り値:変換後文字列の長さ,0=失敗
    UINT CodePage,              //入力:コードページ
    DWORD dwFlags,              //入力:文字種別フラグ
    LPCWSTR lpWideCharStr,      //入力:変換元文字列
    int cchWideChar,            //入力:変換元文字列の長さ
    LPSTR lpMultiByteStr,       //出力:変換後文字列
    int cbMultiByte,            //出力:lpMultiByteStrのサイズ
    LPCSTR lpDefaultChar,       //入力:代替文字
    LPBOOL lpUsedDefaultChar);  // 出力:代替文字が使われたか
]]
Ccst = { -- 定数定義
    CP_UTF8 = 65001;            -- UTF-8のコードページ
}

--- 短形式パス名を取得.
-- @param lpath 長形式パス名
-- @return 短形式パス名(nil=失敗)
function short_path_name(lpath)
  if type(lpath) ~= "string" then return nil end
  if ffi.os ~= "Windows" then return lpath end
  -- UTF-8文字列→ワイド文字列
  local ulpath = ffi.new("WCHAR[?]", #lpath + 1)
  local ulplen = ffi.C.MultiByteToWideChar(Ccst.CP_UTF8, 0,
      lpath, #lpath + 1, ulpath, #lpath + 1)
  if ulplen == 0 then return nil end -- 失敗
  -- 長形式→短形式の変換
  local usplen = ffi.C.GetShortPathNameW(ulpath, nil, 0)
  if usplen == 0 then return nil end -- 失敗
  local uspath = ffi.new("WCHAR[?]", usplen + 1)
  usplen = ffi.C.GetShortPathNameW(ulpath, uspath, usplen + 1)
  if usplen == 0 then return nil end -- 失敗
  -- ワイド文字列→ANSI文字列
  local acp = ffi.C.GetACP()
  local spath = ffi.new("char[?]", usplen * 3)
  local dflt = ffi.new("BOOL[1]")
  local splen = ffi.C.WideCharToMultiByte(acp, 0,
      uspath, usplen, spath, usplen * 3, nil, dflt)
  if splen == 0 or dflt[0] ~= 0 then return nil end -- 失敗
  return ffi.string(spath, splen)
end

-------- 使用例
print(short_path_name("test file.txt"))
print(short_path_name("test ☆彡.txt"))
print(short_path_name("test ☃♪.txt"))
-- EOF
```

  * ソース：[shortname-2.lua](https://github.com/zr-tex8r/tex-sysprog/blob/master/shortname-2.lua)

先と同じく`C:\tmp\temp1`で実行してみます。今度は非ASCIIのファイル名も正常に扱うことができました！

```console:実行結果
>texlua shortname-2.lua
TESTFI~1.TXT
TEST☆~1.TXT
TEST♪~1.TXT
```

ショートネームが取得できたので、これを使って実際にLuaでファイルが扱えることを確かめてみます。`shortname-2.lua`の末尾に次のコードを追加します。

```Lua:shortname-2.lua（追加）
--- ファイルサイズの情報を表示する.
-- @param no 説明用の番号
-- @param path パス名
function show_file_size(no, path)
  local size = lfs.attributes(path, "size")
  print("("..no..") "..
      ((size) and "size = "..size or "not found"))
end

-- テスト
show_file_size("1", "test file.txt")
show_file_size("2", "test ☆彡.txt")
show_file_size("3", "test ☃♪.txt")
show_file_size("4", short_path_name("test file.txt"))
show_file_size("5", short_path_name("test ☆彡.txt"))
show_file_size("6", short_path_name("test ☃♪.txt"))
```

```console:実行結果
TESTFI~1.TXT
TEST☆~1.TXT
TEST♪~1.TXT
(1) size = 42
(2) not found
(3) not found
(4) size = 42
(5) size = 56
(6) size = 888
```

元のファイル名を使った(1)～(3)では非ASCIIのファイル名の時に失敗しますが、一度ショートネームに変換した(4)～(6)では全て成功しています。当初の目的通り、CP932にない文字を含む名前のファイルをLuaで取り扱うことに成功しました。

#### 解説してみる

要するに、事例③と事例④で得た知識を組み合わせて、以下の手順で「CP932のショートネーム」を取得しています。

```text
UTF-8バイト列のロングネーム
↓ (MultiByteToWideChar)
ワイド文字列のロングネーム
↓ (GetShortPathNameW)
ワイド文字列のショートネーム
↓ (WideCharToMultiByte)
ANSI(CP932)バイト列のショートネーム
```

最後の`ffi.C.WideCharToMultiByte()`の呼出は少し注意が必要です。ここでは、CP932に変換できない文字があったかどうかを調べる（あった場合は結果の文字列はファイル名として使えないので失敗と見なす）ために、`WideCharToMultiByte`APIの引数`lpUsedDefaultChar`を利用しています。これは`BOOL`[^bool]のポインタ渡しの引数であるため、通常のC言語のコードでは次のように書かれるはずです。

```c:C言語
BOOL dflt = FALSE;
// 変換不能な文字があった場合、dfltが真に設定される
WideCharToMultiByte(acp, 0, uspath, usplen, spath, usplen * 3,
    NULL, &dflt);
```

しかし、FFIではC言語のアドレス演算子（`&`）に相当するものがありません。そこで、代わりに次のようなC言語のコードを想定して、これをFFIに翻訳します。

```c:C言語
BOOL dflt[1] = { FALSE }; // dfltはBOOL*型
// 変換不能な文字があった場合、dflt[0]が真に設定される
WideCharToMultiByte(acp, 0, uspath, usplen, spath, usplen * 3,
    NULL, dflt);
```

つまり、`BOOL`の1要素の配列を`ffi.new()`で用意すればよいわけです[^vla]。

```lua:Lua
  local dflt = ffi.new("BOOL[1]") -- ゼロクリアされる
  -- 変換不能な文字があった場合、dflt[0]が非ゼロになる
  local splen = ffi.C.WideCharToMultiByte(acp, 0,
      uspath, usplen, spath, usplen * 3, nil, dflt)
```

その他の注意事項を挙げておきます。

  * `GetShortPathNameW`は文字列をナル終端として扱います。このため、前段の`MultiByteToWideChar`ではナル文字を結果のワイド文字列に含めるために長さを1増やしています。なお、Lua文字列を`char *`型の値として使う場合、末尾にナル文字があることは保証されます。
  * `GetShortPathNameW`を一度`cchBuffer`を0として読んでいるのは、ショートネームの長さを取得するためです。
  * 「プラットフォームがWindowsでない場合は入力の文字列をそのまま返す」という仕様にしています。

### 事例⑥：Windowsだってreadlinkしたい

LuaTeX組込のLuaFileSystem（`lfs`）モジュールには、機能拡張として「シンボリックリンクの参照先を取得する」ための関数`lfs.readlink()`が追加されています。ところがこの関数はWindowsではサポートされていません。そこで、Windowsのデバイス入出力APIを利用して同等の機能のものを実装してみましょう。

例えば、ディレクトリ`C:\tmp\temp2`に次の名前のファイルがあったとします。

![sysprog-4.png](https://qiita-image-store.s3.amazonaws.com/0/14550/2b79641b-5b5d-e52c-e632-18a27f39bb88.png)

  * `original.txt`（リンクでない普通のファイル）
  * `symlink.txt`（`original.txt`へのシンボリックリンク）

#### 実際にreadlinkしてみた

```lua:readlink.lua
local ffi = require("ffi")
require("ffi_typedef")
ffi.cdef[[
//// 型宣言
typedef void *HANDLE;
typedef struct {                        //再解析ポイント情報
    ULONG  ReparseTag;
    USHORT ReparseDataLength;
    USHORT Reserved;
    //※VLSとして扱いたいので、不要な共用体要素を省く
    USHORT SubstituteNameOffset;
    USHORT SubstituteNameLength;
    USHORT PrintNameOffset;
    USHORT PrintNameLength;
    ULONG  Flags;
    //可変長配列(VLA)の要素
    WCHAR  PathBuffer[?];
} REPARSE_DATA_BUFFER, *PREPARSE_DATA_BUFFER;
//※以下はダミー定義
typedef void *LPSECURITY_ATTRIBUTES;
typedef void *LPOVERLAPPED;
//// プロトタイプ宣言
//ファイル・ディレクトリの属性を取得する
DWORD GetFileAttributesW(       //戻り値:ファイルの属性
    LPCWSTR lpFileName);        //入力:ファイルパス名
//ファイルを開いてハンドルを作成する
HANDLE CreateFileW(             //戻り値:ファイルハンドル
    LPCWSTR lpFileName,         //入力:ファイルパス名
    DWORD dwDesiredAccess,      //入力:アクセスモード
    DWORD dwShareMode,          //入力:共有モード
    LPSECURITY_ATTRIBUTES lpSecurityAttributes, //入力:セキュリティ記述子
    DWORD dwCreationDisposition, //入力:ファイル作成の指定
    DWORD dwFlagsAndAttributes, //入力:ファイル作成時の属性
    HANDLE hTemplateFile);      //入力:テンプレートファイルハンドル
//デバイスの直接入出力制御
BOOL DeviceIoControl(          //戻り値:成功したか
    HANDLE hDevice,            //入力:デバイスハンドル
    DWORD dwIoControlCode,     //入力:制御コード
    LPVOID lpInBuffer,         //入力:入力データ
    DWORD nInBufferSize,       //入力:lpInBufferのサイズ
    LPVOID lpOutBuffer,        //出力:出力データのバッファ
    DWORD nOutBufferSize,      //入力:lpOutBufferのサイズ
    LPDWORD lpBytesReturned,   //出力:実際の出力データのサイズ
    LPOVERLAPPED lpOverlapped); //入力:非同期動作の指定
//オブジェクトハンドルを閉じる
BOOL CloseHandle(              //戻り値:成功したか
    HANDLE hObject);           //入力:オブジェクトハンドル
//多バイト文字列をワイド文字列に変換
int MultiByteToWideChar(        //戻り値:変換後文字列の長さ,0=失敗
    UINT CodePage,              //入力:コードページ
    DWORD dwFlags,              //入力:文字種別フラグ
    LPCSTR lpMultiByteStr,      //入力:変換元文字列
    int cbMultiByte,            //入力:変換元文字列の長さ
    LPWSTR lpWideCharStr,       //出力:変換後文字列
    int cchWideChar);           //出力:lpWideCharStrのサイズ
//ワイド文字列を多バイト文字列に変換
int WideCharToMultiByte(        //戻り値:変換後文字列の長さ,0=失敗
    UINT CodePage,              //入力:コードページ
    DWORD dwFlags,              //入力:文字種別フラグ
    LPCWSTR lpWideCharStr,      //入力:変換元文字列
    int cchWideChar,            //入力:変換元文字列の長さ
    LPSTR lpMultiByteStr,       //出力:変換後文字列
    int cbMultiByte,            //出力:lpMultiByteStrのサイズ
    LPCSTR lpDefaultChar,       //入力:代替文字
    LPBOOL lpUsedDefaultChar);  // 出力:代替文字が使われたか
]]
Ccst = { -- 定数定義
  CP_UTF8 = 65001;
  FILE_ATTRIBUTE_REPARSE_POINT = 0x00000400;
  GENERIC_READ = 0x80000000;
  FILE_SHARE_READ = 0x00000001;
  FILE_SHARE_WRITE = 0x00000002;
  FILE_SHARE_DELETE = 0x00000004;
  OPEN_EXISTING = 3;
  FILE_FLAG_BACKUP_SEMANTICS = 0x2000000;
  FILE_FLAG_OPEN_REPARSE_POINT = 0x200000;
  INVALID_HANDLE_VALUE = ffi.cast("HANDLE", -1);
  FSCTL_GET_REPARSE_POINT = 0x900A8;
  IO_REPARSE_TAG_SYMLINK = 0xA000000C;
}

-- cf. http://www.wabiapp.com/WabiSampleSource/windows/get_reparse_point.html

--- シンボリックリンクの参照先を取得する
-- @param path シンボリックリンクのパス名
-- @returns 参照先のパス名(nil=失敗)
function readlink(path)
  if type(path) ~= "string" then return nil end
  if ffi.os ~= "Windows" then return lfs.readlink(path) end
  -- UTF-8文字列→ワイド文字列
  local upath = ffi.new("WCHAR[?]", #path + 1)
  local uplen = ffi.C.MultiByteToWideChar(Ccst.CP_UTF8, 0,
      path, #path + 1, upath, #path + 1)
  if uplen == 0 then return nil end -- 失敗
  -- ファイルの属性を調べる
  local fatr = ffi.C.GetFileAttributesW(upath)
  if fatr == -1 or -- APIが失敗
     -- ファイルが再解析ポイントでない
     not bit32.btest(fatr, Ccst.FILE_ATTRIBUTE_REPARSE_POINT) then
    return nil -- 失敗
  end

  local ret, hfile
  repeat -- break可能なブロック
    -- ファイルハンドル取得
    hfile = ffi.C.CreateFileW(upath, Ccst.GENERIC_READ,
        Ccst.FILE_SHARE_READ + Ccst.FILE_SHARE_WRITE + Ccst.FILE_SHARE_DELETE,
        nil, Ccst.OPEN_EXISTING,
        Ccst.FILE_FLAG_BACKUP_SEMANTICS + Ccst.FILE_FLAG_OPEN_REPARSE_POINT,
        nil)
    if hfile == Ccst.INVALID_HANDLE_VALUE then break end -- 失敗
    -- 再解析ポイントの情報を取得
    local uoplen = 32768
    local rdata = ffi.new("REPARSE_DATA_BUFFER", uoplen)
    local rdlen = ffi.new("DWORD[1]")
    local ok = ffi.C.DeviceIoControl(hfile, Ccst.FSCTL_GET_REPARSE_POINT,
        nil, 0, rdata, ffi.sizeof(rdata), rdlen, nil)
    if ok == 0 then break end -- 失敗
    local rtag = bit32.bor(rdata.ReparseTag)
        -- 再解析ポイントがシンボリックリンクではない
    if rtag ~= Ccst.IO_REPARSE_TAG_SYMLINK then break end -- 失敗
        -- SubstituteName～ はバイト単位なので文字単位の値を得る
    local sso = rdata.SubstituteNameOffset / 2 -- オフセット
    local ssl = rdata.SubstituteNameLength / 2 -- 文字数
        -- 文字数が整数でない
    if sso % 1 ~= 0 or ssl % 1 ~= 0 then break end -- 失敗
    local uopath = rdata.PathBuffer + sso --※ポインタ演算
    -- ワイド文字列→ANSI文字列
    local opath = ffi.new("char[?]", ssl * 4 + 1)
    local oplen = ffi.C.WideCharToMultiByte(Ccst.CP_UTF8, 0,
      uopath, ssl, opath, ssl * 4, nil, nil)
    if oplen == 0 then break end -- 失敗
    ret = ffi.string(opath, oplen)
  until true

  -- ファイルハンドルを閉じる
  if hfile ~= nil then
    ffi.C.CloseHandle(hfile)
  end

  return ret
end

-------- 使用例
print("symlink.txt links to: "..readlink("symlink.txt"));
-- EOF
```

  * ソース：[readlink.lua](https://github.com/zr-tex8r/tex-sysprog/blob/master/readlink.lua)

先ほどの`C:\tmp\temp2`ディレクトリで実行した結果です。リンク先のファイル名が正しく取得できています。

```console:実行結果
>texlua readlink.lua
symlink.txt links to: original.txt
```

#### 解説してみる

`DecideIoControl`で使用している構造体`REPARSE_DATA_BUFFER`は、実際には共用体のメンバを含む次のような複雑なものです。

```c:C言語
typedef struct { //再解析ポイント情報
  ULONG  ReparseTag;
  USHORT ReparseDataLength;
  USHORT Reserved;
  union { //(共用体)
    struct { //シンボリックリンク用
      USHORT SubstituteNameOffset;
      USHORT SubstituteNameLength;
      USHORT PrintNameOffset;
      USHORT PrintNameLength;
      ULONG  Flags;
      WCHAR  PathBuffer[1];
    } SymbolicLinkReparseBuffer;
    struct { //マウントポイント用
      USHORT SubstituteNameOffset;
      USHORT SubstituteNameLength;
      USHORT PrintNameOffset;
      USHORT PrintNameLength;
      WCHAR  PathBuffer[1];
    } MountPointReparseBuffer;
    struct { //一般用
      UCHAR DataBuffer[1];
    } GenericReparseBuffer;
  };
} REPARSE_DATA_BUFFER, *PREPARSE_DATA_BUFFER;
```

`ffi.cdef()`はこのような複雑な定義にも対応しています。しかし、共用体の部分については、今のプログラムでは`SymbolicLinkReparseBuffer`しか利用していません。そこで共用体の`SymbolicLinkReparseBuffer`の部分を展開（残りのメンバは削除）することにします。

```c:C言語
typedef struct { //再解析ポイント情報
  ULONG  ReparseTag;
  USHORT ReparseDataLength;
  USHORT Reserved;
  USHORT SubstituteNameOffset;
  USHORT SubstituteNameLength;
  USHORT PrintNameOffset;
  USHORT PrintNameLength;
  ULONG  Flags;
  WCHAR  PathBuffer[1]; //可変長配列のメンバ
} REPARSE_DATA_BUFFER, *PREPARSE_DATA_BUFFER;
```

この“簡略版”の`REPARSE_DATA_BUFFER`はいわゆる“可変長構造体”――最後のメンバが“実際の使用上は可変長”の配列である構造体――になっています。C言語で可変長構造体を扱うのは結構面倒ですが、FFIではこれを簡単に扱う仕組みがあります。上掲のプログラムのtypedefを見ると、可変長配列のメンバ`PathBuffer`のサイズ指定が`1`ではなく`?`となっています。

```c:Lua(cdef内)
    //可変長配列(VLA)の要素
    WCHAR  PathBuffer[?];
```

このように宣言しておくと、実際に`REPARSE_DATA_BUFFER`型の値を`ffi.new()`で作成する際に、`?`に入る値を引数で別途指定できます。

```lua:Lua
    local uoplen = 32768
    local rdata = ffi.new("REPARSE_DATA_BUFFER", uoplen)
```

つまり、上掲のコードは`PathBuffer`の`[?]`（あるいは元の`[1]`）を`[32768]`​[^path-buffer]に置き換えた固定長の構造体を実際には生成します。この`rdata`のバイトサイズ（＝65576）は通常通り`ffi.sizeof(rdata)`で取得できます。直観的ですね。

※ただし、本来の`REPARSE_DATA_BUFFER`のような「“可変長共用体”を含む可変長構造体」はさすがに対応できないようです。“横着”をした一番の理由は、この辺りの困難を避けるためだったりします。

#### 注意事項

  * 例によって、Windows以外では、`lfs.readlink()`にフォールバックします。
  * `bit32`はLua5.2標準のビット演算用モジュールです。（LuaTeXとLuaJITTeXの両方で使用できます。）
  * `LPSECURITY_ATTRIBUTES`と`LPOVERLAPPED`は本当は特定の構造体へのポインタ型です。しかしこのプログラムではNULLポインタとしてしか使っていないので、横着をして`void*`にtypedefしています。
  * Windowsの定数マクロ`INVALID_HANDLE_VALUE`の定義は`(HANDLE)-1`で、`HANDLE`はポインタ型です。このように“FFI特有の値”となる型にキャストする場合は、`ffi.cast()`関数を使います。`ffi.C.CreateFileW()`の`HANDLE`型の戻り値と`Ccst.INVALID_HANDLE_VALUE`のポインタ値比較は普通に`==`で行えます。
  * `rdata.PathBuffer + sso`はポインタ演算です（C言語での同じコードと同じ意味）[^array-userdata]。なので、オフセット値`sso`はバイト単位でなく`WCHAR`単位です。
  * `ffi.sizeof()`は「C言語の値（FFIのユーザデータ）」についてバイトサイズを求めます。
  * （細かい話ですが）構造体Sに対して、「S型のFFIのユーザデータ」は`S`型の引数にも`S*`型の引数にも渡すことができて期待通り動作します[^array-compat]。
  * `rdata.ReparseTag`が`IO_REPARSE_TAG_SYMLINK`（=`0xA000000C`）と等しいことを判定するところで難儀しました。実際に`0xA000000C`である場合に`rdata.ReparseTag`の値を読み出すと、`ULONG`型（=`uint32_t`）であるはずなのに何故か負の値（つまり`(int32_t)0xA000000C`）が返ってきてしまいます（FFIのバグ？）[^ulong-cast]。仕方がないので、`bit32.bor()`で無理やり正の値に変えることで回避しています。

### 事例⑦：帰ってきたshutwindown

本記事の前フリの中で、「WindowsをシャットダウンするLaTeX文書」（を作るためのtcshutwindownパッケージ）というネタを紹介しました。しかしこのパッケージはかなり昔に作ったものなので現在の版のLuaTeXでは動作しません。本記事の締めくくりのネタとして、FFIの機能を使って“shutwindown”パッケージを実装してみましょう！

#### shutwindownなLuaプログラム

まずはFFIでシャットダウンの処理を実装してみます。

```lua:shutwindown.lua
local ffi = require("ffi")
require("ffi_typedef")
ffi.cdef[[
//// 型宣言
typedef void *HANDLE;
typedef HANDLE *PHANDLE;
typedef struct {                //ローカル一意識別子
    DWORD LowPart;
    LONG HighPart;
} LUID, *PLUID;
#pragma pack(4)
typedef struct {                //LUIDとその属性
    LUID Luid;
    DWORD Attributes;
} LUID_AND_ATTRIBUTES;
#pragma pack(8)
typedef struct {                //トークンの特権セット
    DWORD PrivilegeCount;       //特権の要素数
    LUID_AND_ATTRIBUTES Privileges[?]; //特権
} TOKEN_PRIVILEGES, *PTOKEN_PRIVILEGES;
//// プロトタイプ
//現在プロセスの擬似ハンドルを取得
HANDLE GetCurrentProcess(       //戻り値:現在プロセスのハンドル
    void);
//プロセスのアクセストークンを開く
BOOL OpenProcessToken(          //戻り値:成功したか
    HANDLE ProcessHandle,       //入力:プロセスハンドル
    DWORD DesiredAccess,        //入力:アクセス権
    PHANDLE TokenHandle);       //出力:トークンハンドル
//特権名からローカル一意識別子を探す
BOOL LookupPrivilegeValueA(     //戻り値:成功したか
    LPCSTR lpSystemName,        //入力:システムの名前
    LPCSTR lpName,              //入力:特権の名前
    PLUID lpLuid);              //出力:ローカル一意識別子
//アクセストークンを更新する [advapi32]
BOOL AdjustTokenPrivileges(     //戻り値:成功したか
    HANDLE TokenHandle,         //入力:特権トークンハンドル
    BOOL DisableAllPrivileges,  //入力:全特権を無効化するか
    PTOKEN_PRIVILEGES NewState, //出力:更新内容の特権情報
    DWORD BufferLength,         //入力:更新前のPreviousStateのサイズ
    PTOKEN_PRIVILEGES PreviousState, //入出力:更新対象の特権情報
    PDWORD ReturnLength);       //出力:更新後のPreviousStateのサイズ
//直近のエラーコードを取得
DWORD GetLastError(             //戻り値:直近のエラーコード
    void);
//システムのシャットダウンを行う
BOOL ExitWindowsEx(             //戻り値:成功したか
    UINT uFlags,                //入力:シャットダウン操作
    DWORD dwReserved);
]]
Ccst = { -- 定数定義
  TOKEN_ADJUST_PRIVILEGES = 0x0020;
  TOKEN_QUERY = 0x0008;
  SE_PRIVILEGE_ENABLED = 0x00000002;
  SE_SHUTDOWN_NAME = "SeShutdownPrivilege";
  ERROR_SUCCESS = 0;
  EWX_SHUTDOWN = 0x00000001;
  EWX_REBOOT = 0x00000002;
}
advapi32 = ffi.load("advapi32")

--- システムのシャットダウンを開始する
-- @return 成功したか
function shutdown_system()
  -- プロセストークンの取得
  local TokenHandle = ffi.new("HANDLE[1]")
  local ok = ffi.C.OpenProcessToken(ffi.C.GetCurrentProcess(),
      Ccst.TOKEN_ADJUST_PRIVILEGES + Ccst.TOKEN_QUERY, TokenHandle)
  if ok == 0 then return false end -- 失敗
  -- 必要な権限を取得する
  local Privileges = ffi.new("TOKEN_PRIVILEGES", 1)
  Privileges.PrivilegeCount = 1
  Privileges.Privileges[0].Attributes = Ccst.SE_PRIVILEGE_ENABLED
  ok = advapi32.LookupPrivilegeValueA(nil, Ccst.SE_SHUTDOWN_NAME,
      Privileges.Privileges[0].Luid)
  if ok == 0 then return false end -- 失敗
  advapi32.AdjustTokenPrivileges(TokenHandle[0], 0, Privileges,
      0, nil, nil)
    --※AdjustTokenPrivilegesはGetLastErrorの確認が必要
  if ffi.C.GetLastError() ~= Ccst.ERROR_SUCCESS then return false end -- 失敗
  -- シャットダウンを開始する
  ok = ffi.C.ExitWindowsEx(Ccst.EWX_SHUTDOWN, 0);
  return (ok ~= 0) -- 成功したか
end

-------- 使用例
print(shutdown_system())
-- EOF
```

  * ソース：[shutwindown.lua](https://github.com/zr-tex8r/tex-sysprog/blob/master/shutwindown.lua)

そして実行結果は……、アッ、実行すると本当にシャットダウンしてしまうのでチョット待ってください。

#### 解説してみる

大部分は今までに見てきた事柄の復習なので説明は省略しますが、一点だけ説明すべきことがあります。

このプログラムでは`AdjustTokenPrivileges`というWindows APIを使っていますが、これは今までに出てきたAPIとは異なり、advapi32というライブラリに属します。advapi32はLuaTeXにリンクされたライブラリではないため、`ffi.C`の下に配置されておらず既定では呼び出せません。

`AdjustTokenPrivileges`を使うために、動的ライブラリ（DLL）の読込が必要です。これを行うのが`ffi.load()`関数です。

```lua:Lua
advapi32 = ffi.load("advapi32")
```

Windowsの場合[^posix-dll]、これでadvapi32.dllが動的にリンクされ、そこに属するAPIが名前空間`advapi32`の下に配置されます。つまり、Lua関数`advapi32.AdjustTokenPrivileges()`を実行すれば`AdjustTokenPrivileges`が呼び出されるわけです。

#### shutwindownなパッケージ

Luaのコードが完成したので、早速「WindowsをシャットダウンするLaTeXパッケージ」を作りましょう。

この辺りは純粋にTeX言語（TeX on LaTeX）の話になるため、ここでは詳細は一切省略します。TeX言語中で一般のLuaのコードを実行することと比べて、FFIの使用について特に注意すべき事項はありません。

完成したshutwindownパッケージのソースは以下のようになります。

  * ソース：[shutwindown.sty](https://github.com/zr-tex8r/tex-sysprog/blob/master/shutwindown.sty)

shutwindownパッケージの使用法は、かつてのtcshutwindownパッケージと全く同じで、猶予時間を指定してパッケージを読み込みます。

```tex:LaTeX
% <整数>は猶予時間(秒単位)
\usepackage[grace=<整数>]{shutwindown}
```

#### shutwindownな文書

Luaのコードが完成したので、早速「WindowsをシャットダウンするLaTeX文書」を作りましょう。

※FFIの機能を使うと本当に何でもできてしまうため（外部コマンド実行許可以上に）悪用の危険があります。このため、（TeXモードの）LuaTeXでFFIを利用するには無制限シェルエスケープ許可（`-shell-escape` オプションを付ける）の状態でエンジンを起動する必要があります。

```tex:test-shutwindown.tex
% LuaLaTeX文書; UTF-8
\documentclass[a4paper]{article}
\usepackage[grace=30]{shutwindown}% 30秒後にシャットダウン
\begin{document}
Hello Windows!
\end{document}
```

これで準備は整いました。素敵なシャットダウンで記事を締めくくりましょう！

```console:実行結果
>lualatex -shell-escape test-shutwindown.tex
This is LuaTeX, Version 1.0.4 (TeX Live 2017/W32TeX)
 system commands enabled.
(./test-shutwindown.tex
LaTeX2e <2017-04-15>
……(中略)……
(./shutwindown.sty (c:/texlive/2017/texmf-dist/tex/latex/graphics/keyval.sty)
!!!!!!!!WARNING!!!!!!!!
System will be shutdown in 30 seconds...
System will be shutdown in 20 seconds...
System will be shutdown in 15 seconds...
System will be shutdown in 10 seconds...
System will be shutdown in 5 seconds...
System will be shutdown in 4 seconds...
System will be shutdown in 3 seconds...
System will be shutdown in 2 seconds...
System will be shutdown in 1 second...
System will be shutdown right now...
FAREWELL!

>
```

![sysprog-5.png](https://qiita-image-store.s3.amazonaws.com/0/14550/8e531900-30cf-c517-e755-9f3a87ea9b56.png)

## まとめ

**LuaTeXはマジで万能です！** LuaTeXでイロイロなものをつくって人生を楽しく過ごしましょう！！

[Goで覗くシステムプログラミングの世界]: http://ascii.jp/elem/000/001/234/1234843/
[TeX で Windows をシャットダウンする件について]: http://d.hatena.ne.jp/zrbabbler/20121218/1355840883
[^tcshutwindown]: 残念ながら、このtcshutwindownパッケージは“Lua 5.1に基づく古いLuaTeX”を前提としたものなので、Lua 5.2に基づく現在のLuaTeXエンジンには対応していません。
[FFI]: http://luajit.org/ext_ffi.html
[LuaJIT]: http://luajit.org/
[^luajit]: Lua処理系の別実装の一つで、JITコンパイラによる高速化を特徴としています。
[^lua53]: Lua5.3版を組み込んだLuaTeXの開発も進められているようです。
[いわゆる“texlua”]: http://d.hatena.ne.jp/zrbabbler/20151011/1444578663
[^luatex]: FFIはLuaJIT専用のライブラリであり、本家のLuaエンジンでは使えません。LuaTeXのLuaエンジンは本家Lua（5.2版[^lua53]）なのですが、なんと、LuaTeXチームが独自にFFIを本家Luaに移植して使えるようにしました。従って、LuaTeXでFFIライブラリを用いることができます。（なお、LuaTeXチームはLuaJITを組み込んだ“LuaJITTeX”エンジンも開発していて、これもTeX Liveで配布されています。当然、LuaJITTeXでもFFIを利用できます。）
[^handaxe]: マサカリこわい……。
[GetCurrentProcessId]: https://msdn.microsoft.com/ja-jp/library/cc429112.aspx
[^void]: 実際のAPIの解説では、`void`ではなく`VOID`となっています。この`VOID`はtypedefではなく`void`に展開されるマクロです（void型に対するtypedefは不可）。`ffi.cdef()`はプリプロセッサの機能は持たないので、同じように`VOID`マクロの定義することはできません。仕方ないので、ここでは直接`void`を用いています。
[^loaded]: 実際にはLuaTeXでは`ffi`モジュールは`ffi`というグローバル変数に最初からロードされています（LuaJITTeXではそうではない）が、この挙動には依拠しない方がよいでしょう。
[^number-type]: LuaTeXのLuaは5.2版（LuaJITは5.1版相当）なので、Luaの数値型に整数型と実数型の区別（5.3版で導入された）はないことに注意してください。
[^bytes]: 言語仕様上で文字列がバイト列であって“Unicode文字列”とは見なせない以上、“Unicode版のファイルAPIを使うLua”という概念は規定できないように思えます。
[^perl-shortname]: ちなみに、PerlではWin32モジュールの`GetShortPathName()`関数でショートネームを取得できます。
[^ansi-unicode]: “ANSI版のAPI”とは「文字列とはANSIコードページ（CP932）のバイト列（`char *`型）だ」と見なすAPIのことで、対して“Unicode版のAPI”とは「文字列とは“Unicode文字”（`WCHAR`型整数）の列（ワイド文字列、`WCHAR *`型）だ」と見なすAPIのことです。`WCHAR`型はJavaの`char`型みたいなものです。
[^luastring]: 恐らくバイト列は一旦コピーしてコピー先のアドレスを渡しているのだと思われます。constでない`char *`の引数として「Luaの文字列」を渡すこともできて、その場合にCの関数の処理で当該のバイト列が書き換えられたとしても、元の「Luaの文字列」には影響は及ばないようです。
[^userdata]: Luaの**ユーザデータ**（userdata）とは（Luaコードでなく）ホストプログラムによってのみ生成される特殊な値のことです。
[^carray]: “Cの配列”は、Luaのテーブルと同様に、添字（0起点）で値の読み書きができます。例えば、`spath[0] = spath[1] + 1`のような代入文が書けます。
[^ffi-string]: `a`が“Cの配列”（または他の`char *`相当のもの）、`n`を整数として、`ffi.string(a,n)`は`a`の先頭`n`バイトの列（`n`を省略した場合はナル終端までのバイト列）からなるLua文字列を返します。
[^unicode-char]: 実は、Unicode版APIにおける“Unicode文字”とは「Unicodeの符号位置の整数（高々21ビット）」のことではなく「UTF-16のコードユニット」のこと（つまり`WCHAR`は16ビット整数）だったりします。（これもJavaの`char`と同じ！）
[^constants]: ヘッダファイルではマクロで定義されていることが多いのですが、先述の通り、プリプロセッサはないので自前の“名前空間”を用意しました。宣言コードの中で`const`付きの変数（定数）にしたり`enum`にしたりする方法もあるようですが、なぜか正常に動作しない場合があったので回避しています……。
[^destruct]: ちなみに、FFIのユーザデータの値は、普通のLuaの値と同様にGCの対象となります。
[^lfs-shortname]: 実は、LuaTeXに組み込まれたLuaFileSystem（`lfs`）モジュールは独自拡張がされていて、その中にはショートネームを取得する`lfs.shortname()`という関数があります。しかし、その実装でもANSI版の`GetShortPathName`を使っているため、今の例と全く同じ問題を抱えているようです。
[^bool]: `BOOL`は単に`int`のtypedefであり、通常のC言語の慣習に従い、0が偽でそれ以外が真を表します。`FALSE`は`0`に展開されるマクロです。
[^vla]: ところで、これは疑問なんですが、`ffi.new("BOOL[1]")`と`ffi.new("BOOL[?]", 1)`は何が違うんでしょうかね。FFIのマニュアルを見てもよく解らない……。
[^path-buffer]: なお、この32768という値は、Windowsの拡張パス名の最大長（32767文字）に対応するものです。
[^array-userdata]: ただし、前述の通りFFIには「配列型のユーザデータ」という概念があり、`rdata.PathBuffer`は「`WCHAR[32768]`型のユーザデータ」であるのに対し、ポインタ演算の結果の`uopath`は「`WCHAR *`型のユーザデータ」となるようです。
[^array-compat]: 既に述べましたが、同様に、型Tに対して「“Tの配列型”のFFIのユーザデータ」は`T*`型と互換になります。
[^ulong-cast]: FFIのマニュアルの説明では「`uint32_t`の値をLuaで読む場合には`uint32_t`→`double`のキャストが起こる」ことになり、この通りなら負の値にはならないはずなんですが……。比較対象の方を`ffi.cast("ULONG", 0xA000000C)`にすることを試しましたが、こちらは正の数になるので、結局ダメでした。
[^posix-dll]: POSIXシステムの場合、例えば`ffi.load("z")`とすると`libz.so`が読み込まれます。