<?xml version="1.0" encoding="UTF-8"?>
<rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:media="http://search.yahoo.com/mrss/"><channel><title><![CDATA[てくにかるむ]]></title><description><![CDATA[「エラーをなくすことは非常に有益で時には新しい真実や事実を作り上げるよりも勝る」
ー チャールズ・ダーウィン]]></description><link>http://multix.jp/</link><generator>Ghost 0.6</generator><lastBuildDate>Thu, 16 Jul 2026 01:18:09 GMT</lastBuildDate><atom:link href="http://multix.jp/tag/windows/rss/" rel="self" type="application/rss+xml"/><ttl>60</ttl><item><title><![CDATA[WebBrowserコンポーネントいろいろ]]></title><description><![CDATA[<p>WebBrowserコンポーネントを .NET Framework であれこれ料理したときのいろいろ。</p>

<ul class="index"></ul>

<hr>

<h3 id="">レンダリングバージョンを変更する</h3>

<p>WebBrowserコンポーネント<sup id="fnref:1"><a href="http://multix.jp/webbrowser-component-etc/#fn:1" rel="footnote">1</a></sup>は、何もせずにそのまま使用すると必ず IE7相当のレンダリングエンジンと JavaScriptエンジンになってしまう。これは <code>&lt;meta http-equiv="x-ua-compatible" content="IE=Edge" /&gt;</code> などを食わせても変わらない。<sup id="fnref:2"><a href="http://multix.jp/webbrowser-component-etc/#fn:2" rel="footnote">2</a></sup> この問題を解決するには、レジストリを触るしか方法がない。</p>

<pre><code class="language-brush:vb ">Try  
    Dim RendererVersion As Integer = 11000
    Dim ExecName As String = Path.GetFileName(Environment.GetCommandLineArgs()(0))
    Dim Regkey As RegistryKey = _
        Registry.CurrentUser.CreateSubKey( _
            "SOFTWARE\Microsoft\Internet Explorer\</code></pre>]]></description><link>http://multix.jp/webbrowser-component-etc/</link><guid isPermaLink="false">54302723-107c-4a26-93fd-27768651728a</guid><category><![CDATA[てくにかるむ]]></category><category><![CDATA[Windows]]></category><category><![CDATA[VB.NET]]></category><dc:creator><![CDATA[朝日薫]]></dc:creator><pubDate>Mon, 17 Apr 2017 09:22:12 GMT</pubDate><content:encoded><![CDATA[<p>WebBrowserコンポーネントを .NET Framework であれこれ料理したときのいろいろ。</p>

<ul class="index"></ul>

<hr>

<h3 id="">レンダリングバージョンを変更する</h3>

<p>WebBrowserコンポーネント<sup id="fnref:1"><a href="http://multix.jp/webbrowser-component-etc/#fn:1" rel="footnote">1</a></sup>は、何もせずにそのまま使用すると必ず IE7相当のレンダリングエンジンと JavaScriptエンジンになってしまう。これは <code>&lt;meta http-equiv="x-ua-compatible" content="IE=Edge" /&gt;</code> などを食わせても変わらない。<sup id="fnref:2"><a href="http://multix.jp/webbrowser-component-etc/#fn:2" rel="footnote">2</a></sup> この問題を解決するには、レジストリを触るしか方法がない。</p>

<pre><code class="language-brush:vb ">Try  
    Dim RendererVersion As Integer = 11000
    Dim ExecName As String = Path.GetFileName(Environment.GetCommandLineArgs()(0))
    Dim Regkey As RegistryKey = _
        Registry.CurrentUser.CreateSubKey( _
            "SOFTWARE\Microsoft\Internet Explorer\Main\FeatureControl\FEATURE_BROWSER_EMULATION")
    Regkey.SetValue(ExecName, RendererVersion)
    Regkey.Close()
Catch ex As Exception  
    Console.WriteLine("CreateSubKey: " &amp; ex.StackTrace)
End Try

WebBrowser1 = New WebBrowser()  
WebBrowser1.ObjectForScripting = Me  
</code></pre>

<p>レジストリには、キー＝実行ファイル(EXE)名、値＝IEバージョンの1000倍のDWORD値 を登録する。存在しない時の既定値は 7000 と見做される。またこのとき Visual Studio のデバッグRunでは .EXE名が <code>〜.vhost.exe</code> になるのを考慮する必要がある。結局のところ、プログラム実行毎に毎回自分自身の .EXE名を新規／上書き登録してしまうのが手堅い。またレジストリの登録タイミングは初めて <code>New WebBrowser()</code> する以前でなければならない。</p>

<hr>

<h3 id="vbnet">マウスイベントを VB.NET側で使いたい</h3>

<p>WebBrowserコンポーネント内で発生したイベントはほぼすべて JavaScript内で完結して外部（Windows.Forms）に伝播しない。必要なら JavaScriptから ObjectForScripting機能経由で Formクラスを呼んで RaiseEvent しろということになっている。だがそういう毎回使うような定形処理はサブクラス化してまとめといたほうが良いに決まっている。</p>

<pre><code class="language-brush:vb title: WebBrowserEx.vb">Imports System.Windows.Forms  
Public Class WebBrowserEx  
    Inherits WebBrowser

    Private WithEvents Body As HtmlElement

    Public Shadows Event MouseDown(ByVal sender As Object, ByVal e As MouseEventArgs)
    Public Shadows Event MouseLeave(ByVal sender As Object, ByVal e As MouseEventArgs)

    Protected Overrides Sub OnDocumentCompleted(ByVal e As WebBrowserDocumentCompletedEventArgs)
        Me.Body = Me.Document.Body
        MyBase.OnDocumentCompleted(e)
    End Sub

    Private Sub Body_MouseDown(ByVal sender As Object, _
                               ByVal e As HtmlElementEventArgs) _
                           Handles Body.MouseDown
        RaiseEvent MouseDown(sender, New MouseEventArgs(e.MouseButtonsPressed, 1, _
                                                        e.ClientMousePosition.X, _
                                                        e.ClientMousePosition.Y, 0))
    End Sub

    Private Sub Body_MouseLeave(ByVal sender As Object, _
                                ByVal e As HtmlElementEventArgs) _
                            Handles Body.MouseLeave
        RaiseEvent MouseLeave(sender, New MouseEventArgs(MouseButtons.None, 0, _
                                                         e.ClientMousePosition.X, _
                                                         e.ClientMousePosition.Y, 0))
End Class

' WebBrowser1 = New WebBrowserEx()  
' WebBrowser1.Navigate("about:blank")  
</code></pre>

<p>ここでは一部しか書いてないが、Up/Enter/Move/Over も同様に実装<sup id="fnref:3"><a href="http://multix.jp/webbrowser-component-etc/#fn:3" rel="footnote">3</a></sup>できる。</p>

<p>ただこの実装においては、WebBrowserコンポーネント内で Refresh() する、あるいはF5(Ctrl+R)キーを押してのページリロードに対応できない。というのも .NET版の WebBrowserコンポーネントはリロード時に一切イベントを発火しないからだ。ページ内のJavaScriptでは通常通り onload等が発火するので ObjectForScriptingで補完する（Me.Document.Body を読み直す）必要がある。</p>

<hr>

<h3 id="jscripttypeinfonet">JScriptTypeInfo と .NET オブジェクトの相互変換</h3>

<p>ObjectForScriptingと InvokeScriptを使えば .NETと JavaScriptのユーザ関数を相互に呼び出し合うことが出来る。この時確実に引き渡せる引数は、数値・文字列・真偽値・null の4種類しかない。配列や連想配列（ハッシュ）はシリアライズする必要がある。だが実際には次のコードを試せば解るが、JavaScriptで生成された複雑なデータ構造は .NETのなかを素通りさせることができる。</p>

<pre><code class="language-brush:javascript title:JavaScript">var r = window.external.foo({a:[123, 456], h:{s:789}, s:"Test"});  
function bar (data) {  
    return {b:data.a[0] + data.h.s};
}
console.log(r.b); // 912  
</code></pre>

<pre><code class="language-brush:vb title:VB.NET">Public Function foo (ByVal d As Object) As Object  
    Console.log(d.s)    ' Test
    Console.log(d.a(1)) ' 456
    Console.log(d.h.s)  ' 789
    Return WebBrowser1.Document.InvokeScript("bar", d)
End Function  
</code></pre>

<p>WebBrowserコンポーネントにはこのJavaScriptネイティブのデータ構造＝JScriptTypeInfo型オブジェクトを解釈する機能（オブジェクト定義）がない。単純な文字列や数値は ToString()や ToInt32()メソッドで変換できるし、ハッシュの場合は Item() メソッドこそ使えるものの Count() も Containes() もないので「静的な構造体」以外は直接扱えないのだ。</p>

<pre><code class="language-brush:vb">Console.log(d.s)               ' sメンバーがあれば読み出せるがなければ例外発生  
Console.log(d.Item("s"))       ' これもメンバーがなければ例外発生  
Console.log(d.Conatines("s"))  ' これもメンバーがなければ例外発生  
</code></pre>

<p>.NETのほうでは3.5以降で JSON文字列をシリアライズ／デシリアライズ出来るため、引数も返値も JSON文字列化する取り決めにすれば良い話なのだが、ネイティブにデータ構造を受け渡しできたほうが（JavaScript側の視点では）利便性が良い。もっぱら融通が効かないのは静的型言語である .NETの側なので、こちらをもう少し頑張ってみよう。</p>

<p>取り敢えず、JScriptTypeInfoを読んで比較的扱いやすい Dictionary型にまるごと変換するなら次のようなコードが書ける。いったん IExpando型にキャストすればメンバーのプロパティリストを得られるので、CallByName()で実体を得ることができる。これを再帰的に適用すればいちおう全体にアクセスすることが出来る。ただし配列については手を抜いてハッシュ化してしまっているが。</p>

<pre><code class="language-brush:vb title:JScriptTypeInfo型を Dictionary型に変換する（暫定）">' Imports System.Reflection  
' Imports System.Runtime.InteropServices.Expando

Private Function JscInfoToDict(ByVal jscinfo As Object) As Dictionary(Of String, Object)  
    Dim dict = New Dictionary(Of String, Object)
    Dim keys() As String = DirectCast(jscinfo, IExpando).GetProperties( _
        BindingFlags.Default).Select(Function(p) p.Name).ToArray()
    For Each Key In keys
        Dim Var As Object = CallByName(jscinfo, Key, CallType.Get)
        ' Console.WriteLine("Key:" &amp; Key &amp; ", Var:" &amp; Var.ToString &amp; ", Type:" &amp; Var.GetType.ToString)
        If Var.GetType.ToString = "System.__ComObject" Then  ' これだけでは配列とハッシュの区別がつけられない
            dict.Add(Key, JscInfoToDict(Var))
        Else
            dict.Add(Key, Var)
        End If
    Next
    Return dict
End Function  
</code></pre>

<p>これとは逆に、Dictionary型を JScriptTypeInfo型に変換するのは、JScriptTypeInfo型の定義がないので不可能だ。ならば「JScriptTypeInfo型を知っている」言語に変換を委託してしまえば良い。そもそも相互変換をしたい場面において WebBrowserコンポーネントを使っていないということは滅多にないだろうから、InvokeScriptで JSONクラスメソッドを呼び出してしまえば良い。<sup id="fnref:4"><a href="http://multix.jp/webbrowser-component-etc/#fn:4" rel="footnote">4</a></sup></p>

<pre><code class="language-brush:vb title:相互変換">' Imports System.Web.Script.Serialization

' Friend WithEvents Converter As WebBrowser  
' Converter = New WebBrowser()  
' Converter.Navigate(New Uri("about:blank"))

Private Sub Converter_DocumentCompleted(ByVal sender As Object, _  
                                        ByVal e As EventArgs) _
                                    Handles Converter.DocumentCompleted
    sender.Document.InvokeScript("eval", New Object() {"function __j2s(j){return JSON.stringify(j)};"})
    sender.Document.InvokeScript("eval", New Object() {"function __s2j(j){return JSON.parse(j)};"})
End Sub

Public Function jobject_decoder(ByVal jscinfo As Object) As Dictionary(Of String, Object)  
    Dim jss As JavaScriptSerializer = New JavaScriptSerializer()
    Dim jstring As String = Converter.Document.InvokeScript("__j2s", New Object() {jscinfo})
    Return jss.Deserialize(Of Dictionary(Of String, Object))(If(jstring, "{}"))
End Function

Public Function jobject_encoder(ByVal dict As Dictionary(Of String, Object)) As Object  
    Dim jss As JavaScriptSerializer = New JavaScriptSerializer()
    Dim jstring As String = jss.Serialize(dict)
    Return Converter.Document.InvokeScript("__s2j", New Object() {jstring})
End Function  
</code></pre>

<p>.NET版の InvokeScript() は COM版のそれとは異なり、ルート要素のユーザ関数しか呼び出せず、クラスメソッドを直接叩くことが出来ない。本来なら <code>JSON.stringify</code> <code>JSON.parse</code> を直接使いたいが使えないので、DocumentCompleted を待ってから変換用ユーザ関数を eval で登録する方法を取った。なお JSONクラスは IE7ではそもそも存在しないので、前述した IEバージョン変更のレジストリ登録が事前に必要になる。<sup id="fnref:5"><a href="http://multix.jp/webbrowser-component-etc/#fn:5" rel="footnote">5</a></sup></p>

<hr>

<h3 id="cscriptexejson">余談：cscript.exeでの JSON変換</h3>

<p>Windows標準でかつ標準入出力が使える汎用インタプリタとして、cscript.exe は貴重な存在だ。うまく使えば grep/sed/awk あたりの代用にもなる。だが標準では JSON変換機能を持っていないなど基本的な言語仕様が少々古い。しかし COMオブジェクトとして HTMLファイルにアクセスすると、その中の最新 JavaScriptエンジンから欲しい機能をアドオン的に持ってくることが出来る。</p>

<pre><code class="language-brush:javascript title:jconvert.js">// Ex) cscript.exe //Nologo //U jconvert.js &lt;STDIN &gt;STDOUT
var htmlfile = WScript.CreateObject('htmlfile'), JSON;  
htmlfile.write('&lt;meta http-equiv="x-ua-compatible" content="IE=9" /&gt;');  
htmlfile.close(JSON = htmlfile.parentWindow.JSON);

while (!WScript.StdIn.AtEndOfStream) {  
    var json = JSON.parse(WScript.StdIn.ReadLine()),
    // user code
    WScript.StdOut.WriteLine(JSON.stringify(json));
}
</code></pre>

<p>このテクニックは色々と応用が効く。同じようにして（役に立つかどうかはともかく）jQueryをインポートすることが出来るし、include／require文代わりに使うことも出来る。</p>

<p>なお cscript.exe の標準入出力は、素で起動すると ANSI すなわち日本語では CP932 になる。このため JSON文字列中のマルチバイト文字はエスケープされていなければ正しく扱えない。そこで //U オプション付きで起動すると UTF-16LE でマルチバイト文字を扱えるようになるが、改行コードも2バイト文字（ワード幅）にしなければならない罠がある。更に余談となるが、cscript.exe のスクリプトコードは、ANSI(CP932)または UTF-16LE（BOM付き）で記述されていなければならない。一般的な UTF-8N では文字化けが発生する。<sup id="fnref:6"><a href="http://multix.jp/webbrowser-component-etc/#fn:6" rel="footnote">6</a></sup></p>

<hr>

<div class="footnotes"><ol><li class="footnote" id="fn:1"><p>ここでのサンプルコードは VB.NET だが、C#NETでも JScript.NET でも基本は変わらない。.NETではどの言語でコードを記述しようと、結果的に生成されるのは同一の共通言語オブジェクトだ。なお .NET Frameworkのバージョンは 4.0以降を前提とする。いまさら Vista 以前を動作対象にすることもないし、Win7でも 4.0 Runtime を入れれば済むことだ。 <a href="http://multix.jp/webbrowser-component-etc/#fnref:1" title="return to article">↩</a></p></li>
<li class="footnote" id="fn:2"><p>x-ua-compatible が実装されたのはIE8以降なのだから当然なのだが。 <a href="http://multix.jp/webbrowser-component-etc/#fnref:2" title="return to article">↩</a></p></li>
<li class="footnote" id="fn:3"><p>UpはDownと、Enter/Move/OverはLeaveとおなじ MouseEventArgs(...) 宣言を書く。 <a href="http://multix.jp/webbrowser-component-etc/#fnref:3" title="return to article">↩</a></p></li>
<li class="footnote" id="fn:4"><p>ここで使っている Converterはコントロール表示には使わないので Windows.Forms.Controls に Add する必要はない。 <a href="http://multix.jp/webbrowser-component-etc/#fnref:4" title="return to article">↩</a></p></li>
<li class="footnote" id="fn:5"><p>IEバージョンを変えないならば、Navigate() で読み込むページのなかのほうに、JSON変換関数を JavaScriptで自作して記述することになる。もっともそこまでの手間を掛けるなら、.NET側で頑張るよりも JavaScriptの方で普通に JSON変換して受け渡したほうがコード記述量は増えるものの、単純だろう。 <a href="http://multix.jp/webbrowser-component-etc/#fnref:5" title="return to article">↩</a></p></li>
<li class="footnote" id="fn:6"><p>これに気付いておらず「wscript／cscript は日本語が扱えない」とする発言や書籍がままあるので注意を要する。Windows本来のネイティブ文字コードは結構な昔から「ANSIおよびUnicode(16)」と決まっている。「メモ帳」が UTF-8Nファイルをそのまま保存せず、おせっかいにもBOMを挿入したりする所以もまたこれである。 <a href="http://multix.jp/webbrowser-component-etc/#fnref:6" title="return to article">↩</a></p></li></ol></div>]]></content:encoded></item><item><title><![CDATA[ActivePerlのオフライン構築]]></title><description><![CDATA[<p>ActivePerl 本体は実行インストーラで何時でもインストールできるが、ppm や cpan で組み込む追加モジュールはオンライン構築が前提になっている。だが世間から断絶された秘匿ネットワークや、そもそもインターネットに接続するという概念のない PCやサーバに納品したり、機材リプレースで開発環境を再構築しなければならない場面では、USBメモリや DVD-Rからのオフラインインストールが出来なければ話にならない。そもそもバージョンが数世代古くなるとダウンロード元の ppmレポジトリが有料プラン専用に切り替えられて、いつもで気軽に再ダウンロードすることができなくなってしまう。それでは10年以上メンテ契約が続くような案件では、開発当時の状況保存は結構重要な問題になるので予防線を張っておくことは必要なのだ。</p>

<hr>

<h3 id="ppmx">.ppmxファイルの保存</h3>

<p>ppm install を実行すると、レポジトリから .ppmxファイルが tempディレクトリにダウンロードされてインストールされる。だがこのファイルはコマンド終了時に全削除されるため、普段目にすることがない。.ppmxファイル自体は ppm install の引数に渡すことでオフラインインストールできるため、消される前にファイルのコピーを別の場所に取っておけば、あとでおなじ環境を再構築することが容易くなる。</p>

<pre><code class="language-brush:perl gutter:true title:Client.patch">--- C:/Perl64/lib/ActivePerl/PPM/Client.pm      Tue Feb 16 06:02:11 2016
+++ D:/Perl64/lib/</code></pre>]]></description><link>http://multix.jp/activeperl-offline-installation/</link><guid isPermaLink="false">db8a724c-e2cd-49a8-9217-2a6cf62cc337</guid><category><![CDATA[てくにかるむ]]></category><category><![CDATA[Windows]]></category><category><![CDATA[ActivePerl]]></category><dc:creator><![CDATA[朝日薫]]></dc:creator><pubDate>Fri, 10 Mar 2017 04:08:26 GMT</pubDate><content:encoded><![CDATA[<p>ActivePerl 本体は実行インストーラで何時でもインストールできるが、ppm や cpan で組み込む追加モジュールはオンライン構築が前提になっている。だが世間から断絶された秘匿ネットワークや、そもそもインターネットに接続するという概念のない PCやサーバに納品したり、機材リプレースで開発環境を再構築しなければならない場面では、USBメモリや DVD-Rからのオフラインインストールが出来なければ話にならない。そもそもバージョンが数世代古くなるとダウンロード元の ppmレポジトリが有料プラン専用に切り替えられて、いつもで気軽に再ダウンロードすることができなくなってしまう。それでは10年以上メンテ契約が続くような案件では、開発当時の状況保存は結構重要な問題になるので予防線を張っておくことは必要なのだ。</p>

<hr>

<h3 id="ppmx">.ppmxファイルの保存</h3>

<p>ppm install を実行すると、レポジトリから .ppmxファイルが tempディレクトリにダウンロードされてインストールされる。だがこのファイルはコマンド終了時に全削除されるため、普段目にすることがない。.ppmxファイル自体は ppm install の引数に渡すことでオフラインインストールできるため、消される前にファイルのコピーを別の場所に取っておけば、あとでおなじ環境を再構築することが容易くなる。</p>

<pre><code class="language-brush:perl gutter:true title:Client.patch">--- C:/Perl64/lib/ActivePerl/PPM/Client.pm      Tue Feb 16 06:02:11 2016
+++ D:/Perl64/lib/ActivePerl/PPM/Client.pm      Fri Mar 10 12:12:03 2017
@@ -1328,6 +1328,9 @@
                    if ($save_len != $len) {
                        die "Aborted download ($len bytes expected, got $save_len).\n";
                    }
+use File::Copy;
+my $File = File::Basename::basename($save, '.tgz');
+File::Copy::copy($save, $File.'.ppmx');
                }
                # XXX An MD5 checksum for the tarball would be a good thing
            }
</code></pre>

<p>5.18以降に通用するこのパッチを当てると、カレントディレクトリにダウンロードされた .ppmxファイルが残されるようになる。依存関係で複数のモジュールが取得された場合はその全てが残される。このコードを見れば解るが、 .ppmxファイルの実態は単なる .tgzアーカイブだ。わざわざ拡張子を代えているのは、そうしないと ppmコマンドがオフラインインストール用ファイル名として認識しないからである。</p>

<p>なお ppmコマンドは自前のデータベースとキャッシュでダウンロード履歴を管理しており、毎回必ずしもファイルダウンロードが発生するわけではない。情報は以下のフォルダに保存されているので、作業前にこれを空にしてやればよい。</p>

<pre><code>%LOCALAPPDATA%\ActiveState\ActivePerl
</code></pre>

<p>保存しておいた .ppmxファイルは、ppm install でインストールすることが出来るが、依存関係にあるサブモジュールを先にインストールしておかないと、オンラインで取りに行こうとしてしまう。そこで複数の .ppmxファイルから .ppdファイルを取り出し、そこに書かれた依存関係リストを読み出し、優先順位を付けて順次処理することになる。</p>

<pre><code class="language-brush:perl gutter:true title:LocalUpdate.pl">#!C:/Perl/bin/perl.exe
use utf8;  
use strict;  
use Archive::Tar;  
use File::Basename;  
use File::Find;  
use Cwd;

my $cwd = $ARGV[0] // getcwd;  
print "Search directory: ", $cwd, "\n";  
my(@ppmx_list, %ppmx_hash, %ppmx_path);  
find(sub {  
    if (m/\.ppmx$/io) {
        $ppmx_path{$_} = $File::Find::name;
        push @ppmx_list, $_;
    }
}, $cwd);
foreach my $ppmx (@ppmx_list) {  
    $ppmx_hash{$ppmx} = 9999;
}
foreach my $ppmx (@ppmx_list) {  
    my $tar = Archive::Tar-&gt;new();
    $tar-&gt;read($ppmx_path{$ppmx}, 1);
    foreach my $file ($tar-&gt;get_files()) {
        if ($file-&gt;{name} =~ /\.ppd$/io) {
            foreach my $line (split /\n/, $file-&gt;{data}) {
                if (my $require = ($line =~ m/REQUIRE NAME="([^\"]+)"/o)[0]) {
                    $require =~ s/::/-/go;
                    foreach my $hash (keys %ppmx_hash) {
                        $ppmx_hash{$hash}-- if 0 == index lc($hash), lc($require);
                    }
                }
            }
        }
    }
}
undef @ppmx_list;  
while (my($key, $var) = each %ppmx_hash) {  
    push @ppmx_list, sprintf "%04u %s", $var, $key;
}
foreach my $ppmx (sort {lc $a cmp lc $b} @ppmx_list) {  
    $ppmx =~ s/^\d+ //o;
    my $command = "ppm install ".$ppmx_path{$ppmx}." --nodeps";
    print $cwd, "&gt; ", $command, "\n";
    system $command and die "$!";
    print "\n";
}

1;  
__END__  
</code></pre>

<p>このコードは .ppmxファイルを含む親ディレクトリを引数に取る。引数は複数でもよく、省略されればカレントディレクトリを起点にする。-nオプションは ppmコマンドに --nodeps を付加する。-n オプションなしで起動すると依存解決に失敗した場合（依存する .ppmxが見つからない場合）レポジトリからのダウンロードが生じる。</p>

<hr>

<h3 id="ppmx">.ppmxファイルの作成</h3>

<p>.ppmxファイルの実態は .tgzで固められた .ppdファイルと blibディレクトリだ。cpanでソースからビルドしたものを .ppmxファイルにして保存しておくと、再ビルドする手間を減らせる。cpanの作業ワークは <code>C:\Perl64\cpan\build</code> または <code>C:\Perl\cpan\build</code> にある。</p>

<p>例えば既に cpan install に成功した <code>Foo::Bar</code> モジュールに対応する <code>Foo-Bar-0.1010-4UofsT</code> というディレクトリがあったなら、まずそこに cd して <code>dmake ppd</code> を実行<sup id="fnref:1"><a href="http://multix.jp/activeperl-offline-installation/#fn:1" rel="footnote">1</a></sup>する。すると <code>Foo-Bar.ppd</code> というファイルが作成されるだろう。</p>

<pre><code class="language-brush:bash">&gt; cd C:\Perl64\cpan\build\Foo-Bar-0.1010-4UofsT
&gt; dmake ppd
&gt; type Foo-Bar.ppd
&lt;SOFTPKG NAME="Foo-Bar" VERSION="0.1010"&gt;  
    &lt;ABSTRACT&gt;foobar foobar&lt;/ABSTRACT&gt;
    &lt;AUTHOR&gt;Charlie Root &amp;lt;foobar@example.com&amp;gt;&lt;/AUTHOR&gt;
    &lt;IMPLEMENTATION&gt;
        &lt;PERLCORE VERSION="5,006,0,0" /&gt;
        &lt;REQUIRE NAME="Carp::" /&gt;
        &lt;REQUIRE NAME="Cwd::" VERSION="3.16" /&gt;
        &lt;REQUIRE NAME="Exporter::" /&gt;
        &lt;REQUIRE NAME="strict::" /&gt;
        &lt;REQUIRE NAME="vars::" /&gt;
        &lt;ARCHITECTURE NAME="MSWin32-x64-multi-thread-5.24" /&gt;
        &lt;CODEBASE HREF="" /&gt;
    &lt;/IMPLEMENTATION&gt;
&lt;/SOFTPKG&gt;  
</code></pre>

<p>REQUIRE 行は依存をあらわす。ARCHITECTURE 行はインストール先のプラットフォームを限定するものだが、ピュアPerlで書かれた環境非依存モジュールの場合は <code>noarch</code> に書き換えることでインストール先を選ばないようにすることもできる。</p>

<pre><code class="language-brush:xml ">&lt;ARCHITECTURE NAME="noarch" /&gt;  
</code></pre>

<p>.ppmx(.tgz)ファイルを作るための tar+gzアーカイバは、C:\Perl64\bin に <code>ptar.bat</code>としてインストールされている。すでにパスは通っているので次のようにすれば .ppmxファイルができあがる。</p>

<pre><code class="language-brush:bash ">&gt; ptar -v -c -C -z -f foo-bar.ppmx blib foo-bar.ppd
</code></pre>

<p>この際注意が必要なのは、.ppmxと .ppdのファイル名は一致させておかなければならない点だ。.ppmxファイル名にバージョン番号を付加する一般的なルールに従う場合は、.ppdファイル名にもバージョン番号を付加しておく。もちろんそのバージョン番号は SOFTPKG行の VERSIONと一致しているべきだ。</p>

<pre><code class="language-brush:bash ">&gt; ren foo-bar.ppd foo-bar-0.1010.ppd
&gt; ptar -v -c -C -z -f foo-bar-0.1010.ppmx blib foo-bar-0.1010.ppd
</code></pre>

<hr>

<div class="footnotes"><ol><li class="footnote" id="fn:1"><p>dmake.exe は cpan初期化時に C:\Perl64\sit\bin にインストールされているはずだ。 <a href="http://multix.jp/activeperl-offline-installation/#fnref:1" title="return to article">↩</a></p></li></ol></div>]]></content:encoded></item><item><title><![CDATA[windowsで名前付きパイプを作る]]></title><description><![CDATA[<p>ActivePerl で Win32::Pipe が正常動作しなかった（AutoLoaderが実行時に異常終了する）ので Win32::API を直接叩いてみた。</p>

<hr>

<p>クライアント側は普通に <em>open/close</em> で読み書きできるので、サーバ側で namedpipe を作る部分を書いてみる。</p>

<pre><code class="language-brush:perl gutter:true title:win32pipeserver.pl">use strict;  
use warnings;  
use Win32::API;  
use Fcntl;  
use constant {  
    PIPE_ACCESS_INBOUND     =&gt; 0x00000001,  # O_RDONLY相当
    PIPE_ACCESS_OUTBOUND    =&gt; 0x00000002,  # O_WRONLY相当
    PIPE_ACCESS_DUPLEX      =&gt; 0x00000003,  # O_RDWR相当

    PIPE_</code></pre>]]></description><link>http://multix.jp/win32-create-namedpipe/</link><guid isPermaLink="false">50f30e5a-4293-4576-84e3-6753b5ef8ec4</guid><category><![CDATA[てくにかるむ]]></category><category><![CDATA[Windows]]></category><dc:creator><![CDATA[朝日薫]]></dc:creator><pubDate>Mon, 20 Feb 2017 09:33:48 GMT</pubDate><content:encoded><![CDATA[<p>ActivePerl で Win32::Pipe が正常動作しなかった（AutoLoaderが実行時に異常終了する）ので Win32::API を直接叩いてみた。</p>

<hr>

<p>クライアント側は普通に <em>open/close</em> で読み書きできるので、サーバ側で namedpipe を作る部分を書いてみる。</p>

<pre><code class="language-brush:perl gutter:true title:win32pipeserver.pl">use strict;  
use warnings;  
use Win32::API;  
use Fcntl;  
use constant {  
    PIPE_ACCESS_INBOUND     =&gt; 0x00000001,  # O_RDONLY相当
    PIPE_ACCESS_OUTBOUND    =&gt; 0x00000002,  # O_WRONLY相当
    PIPE_ACCESS_DUPLEX      =&gt; 0x00000003,  # O_RDWR相当

    PIPE_WAIT               =&gt; 0x00000000,
    FILE_FLAG_OVERLAPPED    =&gt; 0x40000000,
};

*STDOUT-&gt;autoflush;

my $PIPE_NAME = "\\\\.\\pipe\\pipesv";

my $CreateNamedPipe = Win32::API-&gt;new( "kernel32", "CreateNamedPipe", "PNNNNNNP", "N" ) or die;  
my $PIPE = $CreateNamedPipe-&gt;Call(  
    $PIPE_NAME,                #  LPCTSTR lpName,                             // パイプ名
    PIPE_ACCESS_DUPLEX,     #  DWORD dwOpenMode,                           // パイプを開くモード
    PIPE_WAIT,              #  DWORD dwPipeMode,                           // パイプ固有のモード
    255,                    #  DWORD nMaxInstances,                        // インスタンスの最大数
    0,                      #  DWORD nOutBufferSize,                       // 出力バッファのサイズ
    0,                      #  DWORD nInBufferSize,                        // 入力バッファのサイズ
    10_000,                 #  DWORD nDefaultTimeOut,                      // タイムアウト(msec)の間隔
    0,                      #  LPSECURITY_ATTRIBUTES lpSecurityAttributes  // セキュリティ記述子
);
my $CloseHandle = Win32::API-&gt;new( "kernel32", "CloseHandle", "N", "N" ) or die;

my $ReadFile = Win32::API-&gt;new( "kernel32", "ReadFile", "NPNPP", "N" ) or die;  
sub ReadFile {  
    my $hFile = shift;
    my $lpBuffer = "\0" x 512;
    my $nNumberOfBytesToRead = 512;
    my $lpNumberOfBytesRead = "\0" x 8;
    my $result = $ReadFile-&gt;Call(
        $hFile,
        $lpBuffer,
        $nNumberOfBytesToRead,
        $lpNumberOfBytesRead,
        0);
    return undef unless $result;
    my $length = unpack "V", $lpNumberOfBytesRead;
    return undef unless $length;
    return substr $lpBuffer, 0, $length;
}

my $WriteFile = Win32::API-&gt;new( "kernel32", "WriteFile", "NPNPP", "N" ) or die;  
sub WriteFile {  
    my $hFile = shift;
    my $lpBuffer = shift;
    while (my $nNumberOfBytesToWrite = defined $lpBuffer &amp;&amp; length $lpBuffer) {
        my $lpNumberOfBytesWritten = "\0" x 8;
        my $result = $WriteFile-&gt;Call(
            $hFile,
            $lpBuffer,
            $nNumberOfBytesToWrite,
            $lpNumberOfBytesWritten,
            0);
        return undef unless $result;
        my $length = unpack "V", $lpNumberOfBytesWritten;
        $lpBuffer = substr $lpBuffer, $length;
    }
    return undef;
}

my $ConnectNamedPipe = Win32::API-&gt;new( "kernel32", "ConnectNamedPipe", "NP", "N" ) or die;  
my $DisconnectNamedPipe = Win32::API-&gt;new( "kernel32", "DisconnectNamedPipe", "N", "N" ) or die;

while ($ConnectNamedPipe-&gt;Call($PIPE, 0)) {  
    WriteFile($PIPE, "Welcome\n");
    print ReadFile($PIPE);         # client from "Hello"
    $DisconnectNamedPipe-&gt;Call($PIPE);
    #last;      # no loop
}
$CloseHandle-&gt;Call($PIPE);

1;  
__END__  
</code></pre>

<p>本来はセキュリティ記述子を指定すべきだが、本件は動作サンプルなので省略<sup id="fnref:1"><a href="http://multix.jp/win32-create-namedpipe/#fn:1" rel="footnote">1</a></sup>する。バッファサイズについてはダミーなので 0 と書いて良い。
こうして出来た PIPEハンドル（単なるint値）は、PerlIOの感知するところではないため<sup id="fnref:2"><a href="http://multix.jp/win32-create-namedpipe/#fn:2" rel="footnote">2</a></sup> 通常の sysread/syswrite では扱えない。従って CloseHandle/WriteFile/ReadFile についても Win32API で実装する。</p>

<p><code>ConnectNamedPipe()</code> はソケット通信の accept() に相当する関数で、クライアント側が同名パイプを開くまでI/Oブロックする。<code>DisconnectNamedPipe()</code>はこのセッションを閉じてクライアントを切断する。</p>

<p>いっぽう、クライアント側は普通のファイルアクセスとなんら変わるところはない。</p>

<pre><code class="language-brush:perl gutter:true title:win32pipeclient.pl">use strict;  
use warnings;

*STDOUT-&gt;autoflush;

my $PIPE_NAME = "\\\\.\\pipe\\pipesv";

open my $FH, "+&lt;", $PIPE_NAME or die "$!";  
$FH-&gt;binmode(":raw");
print scalar &lt;$FH&gt;;                # server from "Welcome"  
$FH-&gt;write("hello\n");
$FH-&gt;close;

1;  
__END__  
</code></pre>

<p>マルチクライアントサーバを実装する場合、基本的には ConnectNamedPipe のあとで CreateThread すればよいが、CreateNamedPipe に FILE_FLAG_OVERLAPPED の指定と、ConnectNamedPipe に lpOverlapped の指定が必要になる。<sup id="fnref:3"><a href="http://multix.jp/win32-create-namedpipe/#fn:3" rel="footnote">3</a></sup></p>

<hr>

<div class="footnotes"><ol><li class="footnote" id="fn:1"><p>従って誰でもどこからでも無制限に読み書きできてしまう。その場合何が起こり得るかについては<a href="http://eternalwindows.jp/windevelop/service/service06.html">こちら</a>が詳しい。 <a href="http://multix.jp/win32-create-namedpipe/#fnref:1" title="return to article">↩</a></p></li>
<li class="footnote" id="fn:2"><p>open($DUP, "&lt;&amp;=", $fd) といった構文では認識できない。 <a href="http://multix.jp/win32-create-namedpipe/#fnref:2" title="return to article">↩</a></p></li>
<li class="footnote" id="fn:3"><p><a href="https://msdn.microsoft.com/ja-jp/library/cc429611.aspx">https://msdn.microsoft.com/ja-jp/library/cc429611.aspx</a> <a href="http://multix.jp/win32-create-namedpipe/#fnref:3" title="return to article">↩</a></p></li></ol></div>]]></content:encoded></item><item><title><![CDATA[IIS用FastCGI簡易ラッパー]]></title><description><![CDATA[<p><a href="http://multix.jp/iis8-fastcgi-mojolicious-lite/">前項</a>でIIS用FastCGIについて述べた件の続き。</p>

<hr>

<p>モジュールマップ登録では個々の.fcgiファイルについて1エントリが必要とされるため、拡張子.fcgi全体をひとつのモジュールマップで済ませるための簡単なラッパーを書いてみた。ものすごく単純だが用法を誤らなければ相応に使える。</p>

<pre><code class="language-brush:perl gutter:true title:fcgi.pl">use strict;  
use warnings;  
use FCGI;  
use File::Spec;  
open STDERR, "&gt;&amp;", STDOUT;  
my $request = FCGI::Request or die;  
while ( $request-&gt;Accept &gt;= 0 ) {  
    my $stderr = '';
    eval {
        local $SIG{__WARN__} = sub { $stderr .= shift };
        local $SIG{__DIE__} = sub { die($stderr = shift)</code></pre>]]></description><link>http://multix.jp/iis-fastcgi-easy-wrapper/</link><guid isPermaLink="false">d447898a-80b8-401a-aab0-e76227fcb7f8</guid><category><![CDATA[Windows]]></category><category><![CDATA[Web]]></category><dc:creator><![CDATA[朝日薫]]></dc:creator><pubDate>Mon, 20 Feb 2017 09:25:55 GMT</pubDate><content:encoded><![CDATA[<p><a href="http://multix.jp/iis8-fastcgi-mojolicious-lite/">前項</a>でIIS用FastCGIについて述べた件の続き。</p>

<hr>

<p>モジュールマップ登録では個々の.fcgiファイルについて1エントリが必要とされるため、拡張子.fcgi全体をひとつのモジュールマップで済ませるための簡単なラッパーを書いてみた。ものすごく単純だが用法を誤らなければ相応に使える。</p>

<pre><code class="language-brush:perl gutter:true title:fcgi.pl">use strict;  
use warnings;  
use FCGI;  
use File::Spec;  
open STDERR, "&gt;&amp;", STDOUT;  
my $request = FCGI::Request or die;  
while ( $request-&gt;Accept &gt;= 0 ) {  
    my $stderr = '';
    eval {
        local $SIG{__WARN__} = sub { $stderr .= shift };
        local $SIG{__DIE__} = sub { die($stderr = shift) };
        my $file = File::Spec-&gt;catfile(
            $ENV{DOCUMENT_ROOT} // 'Undefined',
            $ENV{SCRIPT_NAME} // 'index.fcgi'
        );
        return print "Status: 404\n\n" unless -f $file;
        do $file;
    };
    $stderr = "$@" if $@;
    if ($stderr) {
        print "Status: 403\nContent-Type: text/plain\n\n", $stderr;
    }
}
1;  
__END__  
</code></pre>

<p>このラッパーを要求パス *.fcgi に対して適用する<sup id="fnref:1"><a href="http://multix.jp/iis-fastcgi-easy-wrapper/#fn:1" rel="footnote">1</a></sup>と、ドキュメントルート以下の FastCGIファイルについて <strong>半常駐化</strong>状態で実行されるようになる。PATH_INFO は期待どうり動作するし、 <em>既定のドキュメント</em> に index.fcgi を追加した場合にも対応する。ただしあくまでも簡易的に、だ。</p>

<p>このラッパーがなぜ半常駐化なのかというと、常駐対象になるのが、特殊変数および <code>our</code> 宣言<sup id="fnref:2"><a href="http://multix.jp/iis-fastcgi-easy-wrapper/#fn:2" rel="footnote">2</a></sup>されたパッケージ変数と <code>use</code> または <code>require</code> で読み込まれたモジュールに限定されているからだ。.fcgi ファイル自体は <code>do $file</code> の形式で読込実行されているが、これはこの命令が実行される度にファイルアクセスが発生し、コードが再コンパイル<sup id="fnref:3"><a href="http://multix.jp/iis-fastcgi-easy-wrapper/#fn:3" rel="footnote">3</a></sup>されることによる。このとき <code>do $file</code> は <code>package main</code> 名前空間内で実行されているため、複数の .fcgi で同名のグローバル変数や関数宣言が共有されることになる。</p>

<p>.fcgi 自体は毎回再コンパイルされるが、その中で使われる use モジュールについてはコンパイル済コードが再利用されるため、その使用比率が大きいコードほどメリットがある。一方で名前空間のファイル別隔離処理を端折っているため同名関数は後続のコード実行で上書きされてしまうため注意を要する。このあたりが簡易版と称する所以だ。</p>

<p>エラー処理について言えば、FastCGI 起動前に STDERR を STDOUT にリダイレクトすることで、<code>do $file</code> 実行時の構文エラーが極力 IIS Worker Process へ渡らないように細工している。<code>$SIG{}</code> の定義も同様に STDERR を漏らさないためだ。これを怠ると IIS が <em>503 Service Unavailable</em> を返すようになり、かつ、サーバ自体を再起動しなければ回復できない<sup id="fnref:4"><a href="http://multix.jp/iis-fastcgi-easy-wrapper/#fn:4" rel="footnote">4</a></sup>ことがままある。</p>

<hr>

<div class="footnotes"><ol><li class="footnote" id="fn:1"><p>このラッパーそのものはドキュメントルートの外に配置する。また拡張子も.fcgi以外（.pl）とするべきだ。またこのラッパー自体は完全常駐状態となるため、リロードするためには IIS Worker Process を終了させなければならない。 <a href="http://multix.jp/iis-fastcgi-easy-wrapper/#fnref:1" title="return to article">↩</a></p></li>
<li class="footnote" id="fn:2"><p>our $count //= 0; などと書けば、未定義状態の初回ロード時だけ初期化される。レキシカル変数については eval $EXPR と違って異なるスコープとなる。よって my 宣言は重複しても影響がない。 <a href="http://multix.jp/iis-fastcgi-easy-wrapper/#fnref:2" title="return to article">↩</a></p></li>
<li class="footnote" id="fn:3"><p>require は再ロードも再コンパイルもしないが、再実行もしない。再コンパイルせずに再実行だけ行う関数はない。 <a href="http://multix.jp/iis-fastcgi-easy-wrapper/#fnref:3" title="return to article">↩</a></p></li>
<li class="footnote" id="fn:4"><p>IISサービス（W3SVC）を再起動するだけでは回復しない。$stderr の出力時に <em>Status: 500</em> としていないのも同じ理由による。これだけ念を入れても不用意な実行時エラー（よくあるのは再入不能モジュールの実行時エラー。例えば binmode Encoding関係や Win32関係など）でサービス停止に追い込まれる当たり、IIS の FastCGI 環境は結構繊細なので、エラーに無頓着なタイプの開発者には全く向いていない。 <a href="http://multix.jp/iis-fastcgi-easy-wrapper/#fnref:4" title="return to article">↩</a></p></li></ol></div>]]></content:encoded></item><item><title><![CDATA[IIS8+FastCGI+ActivePerl+Mojolicious::Lite]]></title><description><![CDATA[<p>最近のWindows IISでMojolicious::Liteを動かそうとすると、普通はCGIモードしか使えなくて全く実用的なパフォーマンスが確保できない。これは双方が互いを全くサポートしていないが故だが、これはそれをどうにかしてしまおうというメモ。</p>

<ul class="index"></ul>

<h3 id="iiscgi">IISのCGIセットアップ</h3>

<p>まず普通にIISでPerl CGIを動かすための手順。一応 Windows 10/Windows Server 2012 と ActivePerl 5.20(64bit) を前提にしているが、他の組み合わせでも大差はない。</p>

<p><strong>コントロールパネル＞プログラム＞プログラムと機能＞Windowsの機能の有効化または無効化</strong>と辿ってダイアログを開き、<strong>インターネットインフォメーションサービス</strong>にチェックを付ける。更にそのサブメニューを開いて<strong>World Wide Webサービス＞アプリケーション開発機能>CGI</strong>にチェックを付ける。最低限これだけを有効化すれば要は足りるからOKを押して変更を確定する。初回導入時についてはサーバ再起動は必要ない。<sup id="fnref:1"><a href="http://multix.jp/iis8-fastcgi-mojolicious-lite/#fn:1" rel="footnote">1</a></sup></p>

<p><img src="http://multix.jp/content/images/2015/12/2015-12-27-10-48-22.png" alt=""></p>

<p>ActivePerl(64bit)が既にインストールされており、パスが通っているならコマンドプロンプトを管理者権限で開いて以下のコマンドをタイプする。</p>

<pre><code class="language-brush:plain">ap-iis-config add all --site 1 --cgi  
</code></pre>

<p>するとIISマネージャーの<strong></strong></p>]]></description><link>http://multix.jp/iis8-fastcgi-mojolicious-lite/</link><guid isPermaLink="false">51d12cb6-9839-4b14-b74d-53400cfcb7e2</guid><category><![CDATA[Windows]]></category><category><![CDATA[Web]]></category><dc:creator><![CDATA[朝日薫]]></dc:creator><pubDate>Sun, 27 Dec 2015 04:33:31 GMT</pubDate><content:encoded><![CDATA[<p>最近のWindows IISでMojolicious::Liteを動かそうとすると、普通はCGIモードしか使えなくて全く実用的なパフォーマンスが確保できない。これは双方が互いを全くサポートしていないが故だが、これはそれをどうにかしてしまおうというメモ。</p>

<ul class="index"></ul>

<h3 id="iiscgi">IISのCGIセットアップ</h3>

<p>まず普通にIISでPerl CGIを動かすための手順。一応 Windows 10/Windows Server 2012 と ActivePerl 5.20(64bit) を前提にしているが、他の組み合わせでも大差はない。</p>

<p><strong>コントロールパネル＞プログラム＞プログラムと機能＞Windowsの機能の有効化または無効化</strong>と辿ってダイアログを開き、<strong>インターネットインフォメーションサービス</strong>にチェックを付ける。更にそのサブメニューを開いて<strong>World Wide Webサービス＞アプリケーション開発機能>CGI</strong>にチェックを付ける。最低限これだけを有効化すれば要は足りるからOKを押して変更を確定する。初回導入時についてはサーバ再起動は必要ない。<sup id="fnref:1"><a href="http://multix.jp/iis8-fastcgi-mojolicious-lite/#fn:1" rel="footnote">1</a></sup></p>

<p><img src="http://multix.jp/content/images/2015/12/2015-12-27-10-48-22.png" alt=""></p>

<p>ActivePerl(64bit)が既にインストールされており、パスが通っているならコマンドプロンプトを管理者権限で開いて以下のコマンドをタイプする。</p>

<pre><code class="language-brush:plain">ap-iis-config add all --site 1 --cgi  
</code></pre>

<p>するとIISマネージャーの<strong>サイト＞Default Web Site＞機能ビュー＞ハンドラーマッピング</strong>に以下のスクリプトマップが登録されているはずだ。</p>

<p><img src="http://multix.jp/content/images/2015/12/2015-12-27-12-17-27.png" alt=""></p>

<p>復数のサイトが作られている場合、個別にこのスクリプトマップが必要になる。前述の<mark>--site 1</mark>の数字がサイトの登録番号を示しているので、これを変更しながら必要なだけ繰り返す。あるいはこのダイアログ画像を参考に手動でスクリプトマップを追加する。</p>

<p>なお32bit版のActivePerlでは、ISAPIを使用することもできる。Windows機能ダイアログで<strong>ISAPIフィルター</strong>と<strong>ISAPI拡張</strong>にもチェックをつけていれば、以下のコマンドでISAPIのハンドラーが作成できる。<sup id="fnref:2"><a href="http://multix.jp/iis8-fastcgi-mojolicious-lite/#fn:2" rel="footnote">2</a></sup></p>

<pre><code class="language-brush:plain">ap-iis-config add all --site 1 --cgi --isapi  
</code></pre>

<p>以上で <em>.pl ファイルはCGIとして認識されるようになる。</em>.cgi ファイルを扱いたい場合は手動でスクリプトハンドラーを追加しよう。<sup id="fnref:3"><a href="http://multix.jp/iis8-fastcgi-mojolicious-lite/#fn:3" rel="footnote">3</a></sup></p>

<h4 id="fastcgi">FastCGIを有効化する</h4>

<p>FastCGIも同様にハンドラーマッピングを追加することで有効化できるが、ひとつのスクリプトに対してひとつのモジュールマップエントリが必要になる。例えば以下のスクリプトを登録してみよう。<sup id="fnref:4"><a href="http://multix.jp/iis8-fastcgi-mojolicious-lite/#fn:4" rel="footnote">4</a></sup></p>

<pre><code class="language-brush:perl gutter:true title:count.fcgi">use strict;  
use warnings;  
use FCGI;

my $count = 0;  
my $request = FCGI::Request();  
while ( $request-&gt;Accept &gt;= 0 ) {  
    print "Content-type: text/html\r\n\r\n", ++$count;
}
</code></pre>

<p>このファイルは通常のURIで辿れる場所に存在しなければならない。つまり<em>C:¥inetpub¥wwwroot</em>以下以外にあるのならば、仮想ディレクトリパスが通っている必要がある。そうした上で次のようにモジュールマップを登録する。</p>

<p><img src="http://multix.jp/content/images/2015/12/2015-12-27-12-44-28.png" alt=""></p>

<p>要求パス欄は実ファイル名と一致<sup id="fnref:5"><a href="http://multix.jp/iis8-fastcgi-mojolicious-lite/#fn:5" rel="footnote">5</a></sup>させなければならない。モジュール欄はドロップダウンからFastCgiModuleを選択する。名前欄は同一サイト内で重複しなければ自由だ。そして実行可能ファイル欄に<strong>Perlの実行フルパス名</strong>と<strong>スクリプトのフルパス名"</strong>を半角パイプ記号で続けて記入する。なぜパイプで繋げるのかは解らないがこのように書かなければならない。これで<em>OK</em>を押すとハンドラを登録してよいか警告ダイアログが出るので<em>はい</em>で応答する。</p>

<p>正しくFastCGIハンドラが登録されていれば、このスクリプトをブラウザで開けば（例えば localhost/test/count.fcgi）カウンタが表示されるだろう。Ctrl+RやF5でリロードすればカウントアップが進むはずだ。</p>

<p>なおスクリプトを書き換えたあとは、実行中のFastCGIハンドラを再起動しなければならない。手っ取り早いのはタスクマネージャのプロセス一覧から、<em>IIS Worker Process</em> を探して <em>タスクの終了</em> をさせてしまう方法だ。こうすればすぐに再度1からカウントをやり直す。</p>

<h4 id="mojoliciouslitefastcgi">Mojolicious::LiteアプリをFastCGIで実行する</h4>

<p>Mojoliciousは、その開発当初はともかく現在はIISをサポートしていない。だが前述のもっとも単純な FastCGIスクリプトを見ればわかるように、Requestループの中で普通にCGIスクリプトを利用すれば、そのままFastCGIプロセス <em>(IIS Worker Process)</em> として永続化される。かといってループ内で Mojolicious を呼び出してもそのままでは正常に動作しない。app->start が IIS/CGIであるかどうかを正しく認識できないためだ。そこで次のようなコードにする。<sup id="fnref:6"><a href="http://multix.jp/iis8-fastcgi-mojolicious-lite/#fn:6" rel="footnote">6</a></sup></p>

<pre><code class="language-brush:perl gutter:true title:apps.fcgi">use strict;  
use warnings;

use Mojolicious::Lite;

plugin 'basic_auth';

my $count = 0;

get '/' =&gt; sub { $_[0]-&gt;render(text =&gt; 'Non restricted area' . ++$count); };

under sub {  
    my ($c) = shift;
    return $c-&gt;basic_auth(
        realm =&gt; sub {
            if ( @_ &amp;&amp; ( "@_" eq 'foo bar' ) ) {
                $c-&gt;req-&gt;env-&gt;{REMOTE_USER} = $_[0];
                return 1;
            }
        }
    );
};

get '/rest' =&gt; sub {  
    $_[0]-&gt;render(text =&gt; 'Hello ' . $_[0]-&gt;req-&gt;env-&gt;{REMOTE_USER} . ++$count . join(",", @ARGV));
};

if ($ENV{APP_POOL_CONFIG}) {  
    use FCGI;
    my $request = FCGI::Request();
    while ( $request-&gt;Accept &gt;= 0 ) {
        app-&gt;start("cgi");
    }
}
else {  
    app-&gt;start();
}
</code></pre>

<p>これはIIS固有の環境変数が存在するならば、FCGI::Requestループ実行に切り替えるようにしている。そうでなければ、つまりコマンドプロンプトで実行すれば、従来通りの挙動となる。こうしておいたうえで、FastCGIハンドラには次のように指定する。</p>

<pre><code class="language-brush:plain">C:¥Perl64¥bin¥perl.exe|C:¥test¥apps.fcgi cgi -m production  
</code></pre>

<p>ここでCGI形式で実行することを強要し、かつ