<?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>Tue, 10 Mar 2026 19:45:32 GMT</lastBuildDate><atom:link href="http://multix.jp/tag/vb-net/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></channel></rss>