C# と VB.NET の質問掲示板

ASP.NET、C++/CLI、Java 何でもどうぞ

C# と VB.NET の入門サイト

Re[10]: IHTMLDocumentオブジェクトの取得に関して


(過去ログ 174 を表示中)

[トピック内 11 記事 (1 - 11 表示)]  << 0 >>

■99815 / inTopicNo.1)  IHTMLDocumentオブジェクトの取得に関して
  
□投稿者/ はなみ (1回)-(2022/06/08(Wed) 18:52:11)

分類:[VB.NET/VB2005 以降] 

Internet Explorer_Serverのウィンドウハンドルから、IHTMLDocumentオブジェクトを
得ようとしています。

以下のような宣言をして、GetHTMLDocument()プロシージャを作成したのですが、
SendMessageTimeout()で lpdwResultに値が入ってきません。
hWnd、msgは正常だと思いますし、retには 1が返ってくるのですが…

まずい点がわかりましたらご教示ください。
また、自分で調べてはみましたが FAQでしたら申し訳ありません。
URLなどお教えいただけると助かります。
---------------------------------------
    Private Enum SMTO
        NORMAL = 0
        BLOCK = 1
        ABORTIFHUNG = 2
        NOTIMEOUTIFNOTHUNG = 8
    End Enum

    <DllImport("user32.dll", EntryPoint:="SendMessageTimeoutW", SetLastError:=True)>
    Private Function SendMessageTimeout(ByVal hWnd As IntPtr, ByVal msg As Integer,
                                        ByVal wParam As IntPtr, ByVal lParam As IntPtr,
                                        ByVal fuFlags As Integer, ByVal uTimeout As Integer,
                                        ByRef lpdwResult As IntPtr) As IntPtr
    End Function

    <DllImport("oleacc.dll", SetLastError:=True)>
    Private Function ObjectFromLresult(ByVal lResult As Int32, ByRef riid As System.Guid,
                                       ByVal wParam As Int32,
                                       <MarshalAs(UnmanagedType.Interface)>
                                       ByRef ppvObject As Object) As Integer
    End Function
---------------------------------------

    Private Function GetHTMLDocument(ByVal hWnd As IntPtr) As Object
        Dim intRet As Integer
        Dim ret As IntPtr
        Dim lpdwResult As IntPtr
        Dim ppvObject As Object
        Const IID_IHTMLDocument = "{332C4425-26CB-11D0-B483-00C04FD90119}"

        Dim msg As Integer = RegisterWindowMessage("WM_HTML_GETOBJECT")

        ret = SendMessageTimeout(hWnd, msg, IntPtr.Zero, IntPtr.Zero, SMTO.ABORTIFHUNG, 1000, lpdwResult)
        If ret = 0 Then
            MessageBox.Show(Marshal.GetLastWin32Error().ToString)
        End If

        If lpdwResult Then

            Dim IID_IHTMLDocumentGuid As New System.Guid(IID_IHTMLDocument)
            intRet = ObjectFromLresult(lpdwResult, IID_IHTMLDocumentGuid, 0, ppvObject)

            If intRet = 0 Then
                Return ppvObject
            End If
        End If

    End Function


引用返信 編集キー/
■99818 / inTopicNo.2)  Re[1]: IHTMLDocumentオブジェクトの取得に関して
□投稿者/ 魔界の仮面弁士 (3393回)-(2022/06/08(Wed) 20:02:13)
No99815 (はなみ さん) に返信
> SendMessageTimeout()で lpdwResultに値が入ってきません。
「lpdwResult に値が入ってこない」とは、具体的にはどういう状態ですか?

呼び出しても lpdwResult の値が書き換わらないと言っているのか、
呼び出すと常に IntPtr.Zero がセットされてしまうと言っているのか、
それとも、常に 非 Zero な特定の値がセットされるという話なのか、
あるいは、毎回想定と異なる値がセットされてくるという状況なのか…。

少なくとも後述するような手直しをした段階で、
手元の環境で ppvObject が得られることを確認済みです。


> まずい点がわかりましたらご教示ください。
API を呼び出している割には、あまりにも暗黙の型変換が多すぎます…。

それに RegisterWindowMessage の宣言も漏れていますよね。
ここの宣言が間違っていると、WM_HTML_GETOBJECT 以外のメッセージになってしまう可能性があるかと。


> Private Function ObjectFromLresult(ByVal lResult As Int32, ByRef riid As System.Guid,
何故 Int32 ?
第一引数は本来 LRESULT 型なのですから、
VB に翻訳するなら As IntPtr とすべきですよね。


> Private Function GetHTMLDocument(ByVal hWnd As IntPtr) As Object
Else 句到達時にも、明示的に Retrun した方が良いですよ。


> Const IID_IHTMLDocument = "{332C4425-26CB-11D0-B483-00C04FD90119}"
その インターフェイス ID は IHTMLDocument2 のものですよ…?

Const IID_IHTMLDocument As String = "{626FC520-A41E-11cf-A731-00A0C9082637}"
Const IID_IHTMLDocument2 As String = "{332c4425-26cb-11d0-b483-00c04fd90119}"
Const IID_IHTMLDocument3 As String = "{3050f485-98b5-11cf-bb82-00aa00bdce0b}"
Const IID_IHTMLDocument4 As String = "{3050f69a-98b5-11cf-bb82-00aa00bdce0b}"
Const IID_IHTMLDocument5 As String = "{3050f80c-98b5-11cf-bb82-00aa00bdce0b}"
Const IID_IHTMLDocument6 As String = "{30510417-98b5-11cf-bb82-00aa00bdce0b}"
Const IID_IHTMLDocument7 As String = "{305104b8-98b5-11cf-bb82-00aa00bdce0b}"
Const IID_IHTMLDocument8 As String = "{305107d0-98b5-11cf-bb82-00aa00bdce0b}"


> If ret = 0 Then
ret 変数を IntPtr 型として宣言したのに、何故か 0 と比較していますね…。
「If ret = IntPtr.Zero Then」とすべきかと。


> If lpdwResult Then
lpdwResult 変数を IntPtr 型として宣言したのに、Boolean のように扱っていますね。
書き換えるとしたら「If lpdwResult <> IntPtr.Zero Then」ですかね。


> intRet = ObjectFromLresult(lpdwResult, IID_IHTMLDocumentGuid, 0, ppvObject)
もしも宣言部を As Integer のままにするなら、x86 ビルドにしたうえで、lpdwResult.ToInt32() を渡します。

とはいえ本来は、呼び出し側を直すのではなく、第一引数を As Integer から As IntPtr にすべきですね。


> Return ppvObject
現状のコードだと BC42105 の警告が生じますよね。

Else 句到達時にも明示的に Return するようにするか、もしくは
メソッド先頭で「GetHTMLDocument = Nothing」などとしておきましょう。
引用返信 編集キー/
■99819 / inTopicNo.3)  Re[2]: IHTMLDocumentオブジェクトの取得に関して
□投稿者/ はなみ (2回)-(2022/06/08(Wed) 20:38:32)
No99818 (魔界の仮面弁士 さん) に返信
どうもありがとうございます。

> 呼び出すと常に IntPtr.Zero がセットされてしまうと言っているのか、

という現象です。

> それに RegisterWindowMessage の宣言も漏れていますよね。

すみません、今その環境がないので以下のようだったと思います。

<DllImport("user32", EntryPoint:="RegisterWindowMessageW", CharSet:=CharSet.Auto)>
Private Function RegisterWindowMessage(ByVal lpString As String) As Integer
End Function

VBAで行なってうまくいったものを、VB.NET書き換えてみてうまくいきませんでした。
その他の点も、ご指摘のことを考慮して書き換えてみます。
結果はまた報告いたします。
こんなに早く返していただけるとは思いませんでした。
ありがとうございました。

引用返信 編集キー/
■99820 / inTopicNo.4)  Re[3]: IHTMLDocumentオブジェクトの取得に関して
□投稿者/ 魔界の仮面弁士 (3394回)-(2022/06/08(Wed) 20:50:45)
No99819 (はなみ さん) に返信
> <DllImport("user32", EntryPoint:="RegisterWindowMessageW", CharSet:=CharSet.Auto)>

試していませんが、W 版のエントリを明示しておいて、
Auto を指定しているのが不自然に見えました…。

Windows 9x 系が駆逐された現在にとっては、
Auto 指定のメリットも無いので、Unicode 固定で良い気がします。

EntryPoint:="RegisterWindowMessageW", ExactSpelling:=True, Charset:=CharSet.Unicode
引用返信 編集キー/
■99822 / inTopicNo.5)  Re[4]: IHTMLDocumentオブジェクトの取得に関して
□投稿者/ はなみ (3回)-(2022/06/11(Sat) 07:30:44)
No99820 (魔界の仮面弁士 さん) に返信
時間ができたため検証することができました。
しっかりと再確認を行ないましたら、引数の hWndが間違っているのが原因でした。
はじめは取得した Internet Explorer_Serverのウィンドウハンドルに再度
GetClassName()して正常に取得できているか確認したのですが、
いろいろと変更しているうちに違ってしまっていました。
ご親切に指摘いただいたのに情けない限りです。

最終的に IID_IHTMLDocument2 のみで目的は達したので以下のようにしました。
(宣言はご指摘いただいたようにUnicode固定にしています。)

それぞれの IID_IHTMLDocumentをどのように ObjectFromLresultに渡せば良いのか
わからず、String配列にして CLSIDFromStringの宣言を変えて lpszにポインタを渡す?
でも StrPtrないし…調べましたら GCHandle.AddrOfPinnedObjectでアドレスを取得?
やってみてもダメでした。
そもそもこの複数のIID_IHTMLDocumentはそれぞれ何を意味するのでしょうか?

ですが、戻り値を初期化しておく必要がある等は思ってもみなかったので
とても勉強になりました。
どうもありがとうございました。

Private Function GetHTMLDocument(ByVal hWnd As IntPtr) As Object
Dim intRet As Integer
Dim ret As IntPtr
Dim lpdwResult As IntPtr
Dim ppvObject As Object = Nothing

Const IID_IHTMLDocument = "{626FC520-A41E-11cf-A731-00A0C9082673}"
Const IID_IHTMLDocument2 = "{332c4425-26cb-11d0-b483-00c04fd90119}"
Const IID_IHTMLDocument3 = "{3050f485-98b5-11cf-bb82-00aa00bdce0b}"
Const IID_IHTMLDocument4 = "{3050f69a-98b5-11cf-bb82-00aa00bdce0b}"
Const IID_IHTMLDocument5 = "{3050f80c-98b5-11cf-bb82-00aa00bdce0b}"
Const IID_IHTMLDocument6 = "{30510417-98b5-11cf-bb82-00aa00bdce0b}"
Const IID_IHTMLDocument7 = "{305104b8-98b5-11cf-bb82-00aa00bdce0b}"
Const IID_IHTMLDocument8 = "{305107d0-98b5-11cf-bb82-00aa00bdce0b}"

'未使用
Dim aryIID_IHTMLDocument() As String = {IID_IHTMLDocument, IID_IHTMLDocument2,
IID_IHTMLDocument3, IID_IHTMLDocument4,
IID_IHTMLDocument5, IID_IHTMLDocument6,
IID_IHTMLDocument7, IID_IHTMLDocument8}

GetHTMLDocument = Nothing

Dim msg As Integer = RegisterWindowMessage("WM_HTML_GETOBJECT")
ret = SendMessageTimeout(hWnd, msg, IntPtr.Zero, IntPtr.Zero, SMTO.ABORTIFHUNG, 1000, lpdwResult)

If ret = IntPtr.Zero Then
'MessageBox.Show(Marshal.GetLastWin32Error().ToString)
Exit Function
End If

If lpdwResult <> IntPtr.Zero Then
Dim IID_IHTMLDocument_GUID As New System.Guid(IID_IHTMLDocument2)
intRet = ObjectFromLresult(lpdwResult, IID_IHTMLDocument_GUID, 0, ppvObject)

Return ppvObject
End If

End Function

引用返信 編集キー/
■99823 / inTopicNo.6)  Re[5]: IHTMLDocumentオブジェクトの取得に関して
□投稿者/ 魔界の仮面弁士 (3396回)-(2022/06/11(Sat) 12:39:34)
No99822 (はなみ さん) に返信
> それぞれの IID_IHTMLDocumentをどのように ObjectFromLresultに渡せば良いのか
128 ビットの値を渡せる型であれば何でもよいですが、
この引数は『[in] REFIID riid,』と定義されているため、
構造体の場合は ByRef、クラスの場合は ByVal で渡すことになります。

> lpszにポインタを渡す?
確かに IntPtr でも渡せますが、一般的には Guid 構造体を ByRef で渡します。
Guid 構造体への変換は Guid.Parse / Guid.ParseExact あるいは New Guid でどうぞ。

> でも StrPtrないし
StrPtr は VB.NET のものではなく、VB5 以降/VBA5 以降の関数ですね。

VBA の場合は、GUIDFromString API を使って、文字列から Byte 配列または
ユーザー定義型(UUID、GUID)へ変換することが多いです。
Access VBA の場合は、Application.GUIDFromString メソッドが使われたりします。
または、GUIDFromString API を使わずとも、
『ReDim x(0 To 1) As Currency』あるいは『ReDim x(0 To 1) As LongLong』で
128bit の領域を直接確保しておき、そこに GUID 値相当のマジックナンバーを
放り込んでも同じ結果が得られます。この場合は、配列の先頭要素 x(0) を参照渡しするか、
または VarPtr(x(0)) を値渡しすれば OK 。

> そもそもこの複数のIID_IHTMLDocumentはそれぞれ何を意味するのでしょうか?
IID は Interface ID のこと。そして IHTMLDocument や IHTMLDocument2 は、
IUnknown や IDispatch と同様、COM インターフェイスの一つです。

実際に取得したいcoclass(コンポーネントクラス)が実装している
インターフェイスを指定すれば受け取れます。乱暴に言えば
 Dim doc2 = DirectCast(target, mshtml.IHTMLDocument2)
みたいな処理だと想像してみてください。

IHTMLDocument2 は IHTMLDocument のメンバーを内包していますし、
IHTMLDocument3 は IHTMLDocument2 を含んでいます。(COM 的な継承関係)
これらはそれぞれ、
 IHTMLDocument8 … IE10 以降
 IHTMLDocument7 … IE9 以降
 IHTMLDocument6 … IE8 以降
のように、IE バージョンに応じて拡張されていったものです。

IHTMLDocument 〜 IHTMLDocument8 の各インターフェイスの定義は、こちらを参照してみてください。
https://docs.microsoft.com/ja-jp/previous-versions/windows/internet-explorer/ie-developer/platform-apis/aa752574%28v%3dvs.85%29


> ですが、戻り値を初期化しておく必要がある等は思ってもみなかった
VB.NET の場合、戻り値の初期値をセットするのはあまり一般的では無いですけれどね。

「GetHTMLDocument への代入」と「Return ppvObject」を併用するのは統一性が無いので、
ローカル変数を用意するなどして、Function が「必ず Return または Throw する」ことを
保証してあげた方が良いでしょう。Function が確実に戻り値を返すようにしておけば、
戻り値を初期値しておかなくてとも、BC42105 の警告には至りません。

言語仕様としては、戻り値の型が Integer であれ Object であれ String であれ、
値を何も指定しなければ、初期値として Nothing がセットされた状態でコンパイルされることが保証されるため、
動作的には問題がありません。単に警告が出るだけです。

とはいえ API を使う以上は、警告ゼロのコードを目指した方が望ましいですね。
できれば Option Strict On 状態で。
引用返信 編集キー/
■99825 / inTopicNo.7)  Re[6]: IHTMLDocumentオブジェクトの取得に関して
□投稿者/ はなみ (4回)-(2022/06/12(Sun) 08:36:36)
No99823 (魔界の仮面弁士 さん) に返信
どうもありがとうございます。
IID_IHTMLDocumentの何たるかは非常によくわかりました。
継承されていて下位互換であれば、IID_IHTMLDocument8 のみを使えば良いとの
認識でよろしいでしょうか。

> 確かに IntPtr でも渡せますが、一般的には Guid 構造体を ByRef で渡します。
> Guid 構造体への変換は Guid.Parse / Guid.ParseExact あるいは New Guid でどうぞ。

下のように CLSIDFromStringを IntPtr受けにして、GCHandle.Allocで取得した
ガベージコレクションのハンドルからアドレスを取得してやってみました。
これでも取得することができました。(あまり意味は無いようですが)

----------------------------------------

<DllImport("ole32.dll")>
Private Function CLSIDFromString(ByVal lpsz As IntPtr, <Out> ByRef pclsid As Guid) As Integer
End Function

----------------------------------------

If lpdwResult <> IntPtr.Zero Then

'ポインタの値がガベージコレクションにより変化することを回避
Dim gch As GCHandle = GCHandle.Alloc(IID_IHTMLDocument2, GCHandleType.Pinned)

'アドレスを取得
Dim pAddress As IntPtr = gch.AddrOfPinnedObject().ToInt32

'GUIDのインスタンス作成
Dim IID_IHTMLDocument_GUID As New System.Guid()

'GUID変換
Dim int As Integer = CLSIDFromString(pAddress, IID_IHTMLDocument_GUID)

If int = 0 Then
intRet = ObjectFromLresult(lpdwResult, IID_IHTMLDocument_GUID, 0, ppvObject)
End If

'ガベージコレクションのハンドルを解放
gch.Free()

Return ppvObject

End If

----------------------------------------

> 『ReDim x(0 To 1) As Currency』あるいは『ReDim x(0 To 1) As LongLong』で
> 128bit の領域を直接確保しておき、そこに GUID 値相当のマジックナンバーを

64bit×2で128bit!…このような領域確保のしかたもあるんですね!

それぞれのIHTMLDocumentのバージョンを指定する場合はどのように実装すれば良いのでしょう?
ひとつひとつ確認するのでしょうか?

> できれば Option Strict On 状態で。

以前自分で書いたVBSとVBAから移植する際に、あまりに多くの赤波に襲われてしまい…
今後注意いたします。
引用返信 編集キー/
■99847 / inTopicNo.8)  Re[7]: IHTMLDocumentオブジェクトの取得に関して
□投稿者/ 魔界の仮面弁士 (3398回)-(2022/06/13(Mon) 13:19:39)
No99825 (はなみ さん) に返信
> IID_IHTMLDocumentの何たるかは非常によくわかりました。
> 継承されていて下位互換であれば、IID_IHTMLDocument8 のみを使えば良いとの
> 認識でよろしいでしょうか。

それでも良いですが、vtable 指定で直接呼び出す場合でもない限り、IHTMLDocument8 の
直接指定が必要になる場面はあまり無いかと思います。

そもそも IHTMLDocument8 の出番はさほど多くありません。MSHTML を参照設定する場合でも、
GAC にインストールされている Microsoft.mshtml.dll を使う場合には、
 As IHTMLDocument6
 As IHTMLDocument7
 As IHTMLDocument8
は宣言できないからです。というのも、
C:\Windows\assembly\GAC\Microsoft.mshtml\7.0.3300.0__b03f5f7f11d50a3a\Microsoft.mshtml.dll
には IHTMLDocument 〜 IHTMLDocument5 までしか定義されていないためです。

このフォルダー名にある 7.0.3300 というバージョン番号は、2002 年登場の .NET Framework 1.0 世代を意味します。
※時代背景としては:IE5.5 リリースが 2000年夏/IE6 リリースが 2001年夏/IE7 が 2006年秋

そして GAC (グローバル アセンブリ キャッシュ)に登録されている
PIA (プライマリ相互運用アセンブリ)の Microsoft.mshtml.dll の中身は
それ以降更新されていない模様( DLL へのデジタル署名の更新は行われていますが)。

なので、どうしても IHTMLDocument6〜IHTMLDocument8 を用いたい場合には、
 "C:\Windows\System32\mshtml.tlb"
 "C:\Windows\SysWOW64\mshtml.tlb"
のタイプライブラリから相互運用アセンブリを生成してそれを用いるか…
もしくは同等の COM Interface を自前で宣言しておく必要が
あったりするのですが…それは今回関係ないことなので置いといて:


今回のケースでは、COM オブジェクトを得られれば良いだけなので、
対象のオブジェクトで実装されている COM インターフェイスであれば何でも構いません。

何でも良いとはいえ、今回のように "Internet Explorer_Server" から辿ってきていて、
しかもそのオブジェクトが HTML 文書であることも初めから分かっているようなケースでは、
 Const IID_DispHTMLDocument As String = "{3050F55F-98B5-11CF-BB82-00AA00BDCE0B}"
 Const IID_IHTMLDocument2 As String = "{332c4425-26cb-11d0-b483-00c04fd90119}"
 Const IID_IHTMLDocument As String = "{626FC520-A41E-11cf-A731-00A0C9082637}"
あたりのいずれかを明示しておけばよいでしょう。

なお、IE4 の時点では既に IHTMLDocument2 が使われていたと記憶しています。
IHTMLDocument は IE3.02 あたりの古代技術だったかな…? (当時の資料が手元に無いので未確認)


ちなみに InternetExplorer オブジェクトや WebBrowser オブジェクトの
Document プロパティから受けとる場合、そこから得られるのは必ずしも HTML Document とは限りません。
状況によっては PDF や Word などの「ActiveX ドキュメント」のインスタンスが返されることもありえます。
そのような状況では、デュアルインターフェイスを期待して
IDispatch ({00020400-0000-0000-C000-000000000046}) で受けたり、あるいは
すべての COM オブジェクトが実装している
IUnknown ({00000000-0000-0000-C000-000000000046}) でも得たりしてから
事後判定で分岐処理したりすることもありますが…こちらも今回は気にしなくて良いでしょう。



もしも ObjectFromLresult を呼ぶ際に、無関係のインターフェイス ID が指定された場合は、
最後のオブジェクト引数に Nothing がセットされます。その場合、API の戻り値としては
Const E_NOINTERFACE As Integer = &H80004002 'インターフェイスがサポートされていません
が返されることになります。

なお手元の環境では、IHTMLDocument2 等で取得した後の Object に対して
 S1 = TypeName(doc) '"HTMLDocumentClass"
 S2 = doc.GetType().FullName '"mshtml.HTMLDocumentClass"
 S3 = doc.GetType().GUID '"{25336920-03F9-11cf-8FD0-00AA00686F13}"
といった結果が得られました。これは HTML Document の COM クラスだということですね。
(HKEY_CLASSES_ROOT\CLSID\{25336920-03F9-11cf-8FD0-00AA00686F13})


> 'アドレスを取得
> Dim pAddress As IntPtr = gch.AddrOfPinnedObject().ToInt32
…ドウシテ…?

AddrOfPinnedObject メソッドの戻り値は IntPtr 型ですよね。。。
それを ToInt32 メソッドで Integer 型へと明示的に変換してから、
さらに暗黙の型変換で、IntPtr 型へ復元した上で代入させている…?? 何故???


> これでも取得することができました。(あまり意味は無いようですが)
> 'GUID変換
> Dim int As Integer = CLSIDFromString(pAddress, IID_IHTMLDocument_GUID)
> If int = 0 Then
>  intRet = ObjectFromLresult(lpdwResult, IID_IHTMLDocument_GUID, 0, ppvObject)
> End If

混乱の元なので、『Dim int As Integer』は避けた方がよろしいかと。

文法的には正しいのですが、"int" という名は、VB 組み込みの
Int 関数(Microsoft.VisualBasic.Conversion.Int メソッド)と混同しやすいですから。

それに CLSIDFromString の戻り値は【BOOL】型です。
https://docs.microsoft.com/ja-jp/windows/win32/shell/guidfromstring

BOOL なので 32bit 整数型であるのは確かですが、API 宣言側は「As Integer」よりも
「As <MarshalAs(UnmanagedType.Bool)> Boolean」の方が直接的な宣言と言えます。

これが VBA だと、As Boolean (これは 16bit の型) とは書けずに As Long で宣言されますが、
それに引っ張られて、VB.NET でも As Integer で宣言されてしまうケースはありますけれどね。

で…一番の問題は、その後の「If int = 0 Then」の行。
CLSIDFromString の戻り値は
  GUID が正常に作成された場合は ≠ 0 (すなわち TRUE)
  GUID を生成できなかった場合は = 0 (すなわち FALSE)
と返す仕様なので、むしろ真偽が逆になっているように見えました。
でも、はなみさんはそれで取得できているんですね…?


> If lpdwResult <> IntPtr.Zero Then
>   …中略…
>   Dim int As Integer = CLSIDFromString(pAddress, IID_IHTMLDocument_GUID)
>   If int = 0 Then
>     intRet = ObjectFromLresult(lpdwResult, IID_IHTMLDocument_GUID, 0, ppvObject)
>   End If
>   …中略…
>   Return ppvObject
> End If

それだと、Else 句到達時に Return を通らないため、
エラーにはならないものの、BC42105 の警告が生じます。

先に述べたように、あらかじめ戻り値の初期値として
 GetHTMLDocument = Nothing
をセットしておけば警告は出ませんが、それよりも、

 Dim ppvObject As Object = Nothing
 …中略…
 If lpdwResult <> IntPtr.Zero Then
  …中略…
  If CLSIDFromString(pAddress, IID_IHTMLDocument_GUID) Then
    Dim hResult = ObjectFromLresult(lpdwResult, IID_IHTMLDocument_GUID, 0, ppvObject)
    If hResult <> 0 Then
     ' Marshal.ThrowExceptionForHR(hResult)
    End If
  End If
  …中略…
 End If
 Return ppvObject

のようにして、最後の「Return ppvObject」を通るようにしておいた方が統一感があるかと思います。
実際には CLSIDFromString を使う必要も無いので、もう少し簡略化できますけれども。



>>『ReDim x(0 To 1) As Currency』あるいは『ReDim x(0 To 1) As LongLong』で
>>128bit の領域を直接確保しておき、そこに GUID 値相当のマジックナンバーを
> 64bit×2で128bit!…このような領域確保のしかたもあるんですね!
> それぞれのIHTMLDocumentのバージョンを指定する場合はどのように実装すれば良いのでしょう?

…? 質問の意味がよく分かりませんでした。

IHTMLDocument8 とか IHTMLDocument2 とかの選び方の事だとしたら、
今回の回答の冒頭で概要だけ述べてみたので、そちらを読んでください。
VB.NET 向けの話として書いてはいますが、VBA でも基本は一緒です。


それとも、VBA で 128bit の変数に IHTMLDocument の IID を指定する実装手順のことでしょう。

VBA だと 128bit サイズの組み込み型が無いので、ユーザー定義型もしくは配列で代用されます。
Byte×16 でも Integer×8 でも Long×4 でも、いずれも 128bit。
http://www.hanatyan.sakura.ne.jp/logbbs1/wforum.cgi?no=3912&reno=3911&oya=3901&mode=msgview

(0 To 3) As Long にマジックナンバーを直接投入するパターンならこんな感じ。
https://www.petitmonte.com/bbs/answers?question_id=19776

(0 To 1) As Currency にマジックナンバーを直接投入するパターンもあります。
https://gist.github.com/Benshi/2057bbea3825b64b017dbc9e5dd54589

String → CLSIDFromString API → (0 To 1) As Currency パターンや
ユーザー定義型(Type UUID)にマジックナンバーを直接投入するパターンならこんな感じ。
https://social.msdn.microsoft.com/Forums/vstudio/en-US/c0765a67-b8ba-40dc-ac52-aac7be9f1d6a/ie123981246912509125401248812364202224180626376152608512395320662?forum=vbajp
引用返信 編集キー/
■99864 / inTopicNo.9)  Re[8]: IHTMLDocumentオブジェクトの取得に関して
□投稿者/ はなみ (5回)-(2022/06/13(Mon) 17:10:40)
No99847 (魔界の仮面弁士 さん) に返信
詳しくご説明いただきありがとうございます。
非常に博識でいらっしゃることに、ただただ驚いております。

> あたりのいずれかを明示しておけばよいでしょう。

はじめに使っておりました IID_IHTMLDocument2 とすることにします。
ですが、意味を理解しないで使用するのとは大違いです。
ありがとうございます。

> そのような状況では、デュアルインターフェイスを期待して
> IDispatch ({00020400-0000-0000-C000-000000000046}) で受けたり、あるいは
> すべての COM オブジェクトが実装している
> IUnknown ({00000000-0000-0000-C000-000000000046}) でも得たりしてから

先日ご紹介いただいた docs.microsoft.comのページを見ていた際に、
IHTMLDocument8のみが IUnknownから継承されていて、他は IDispatchから継承と
書かれており、IDispatchは IUnknownから継承とあったので「逆なのでは?」と
疑問に感じました。

> AddrOfPinnedObject メソッドの戻り値は IntPtr 型ですよね。。。
> それを ToInt32 メソッドで Integer 型へと明示的に変換してから、

そうですね。
https://dobon.net/vb/dotnet/vb6/objptr.html
これを見て、AddrOfPinnedObjectを調べましたら「Pinnedハンドル内の
オブジェクトデータのアドレスを取得」とありましたので、Integer受けでは
ないのでは?と思い、宣言の方だけ変えていました (>_<)

> と返す仕様なので、むしろ真偽が逆になっているように見えました。

ご指摘の通りです。
実際には If int = 0 Then は入っておらず、コードをテキストコピーしてから
「戻り値を評価していないとまた指摘されてしまう」と思って書き足しました。
…完全に見通されてしまっていますね。

> String → CLSIDFromString API → (0 To 1) As Currency パターンや
> ユーザー定義型(Type UUID)にマジックナンバーを直接投入するパターンならこんな感じ。

これです! このイメージでした!
…ただ StrPtr(IID_IHTMLDocumentX) と指定している部分を AddrOfPinnedObjectで
取得したアドレスに置き換えようとしましたが、GCHandle.Allocには String配列を
指定することができないのですね。
ここはもう少し考えてみることにします。

なんにせよ、多くのお時間割いていただき、とてもご丁寧にお教えいただき
本当に感謝いたします。
もう少ししっかりと勉強してから質問するべきと反省いたしました。
どうもありがとうございました。
解決済み
引用返信 編集キー/
■99868 / inTopicNo.10)  Re[9]: IHTMLDocumentオブジェクトの取得に関して
□投稿者/ 魔界の仮面弁士 (3401回)-(2022/06/13(Mon) 20:19:43)
以下、蛇足情報。
解決済みチェックは付けたままにしておきます。

■No99864 (はなみ さん) に返信
>> IHTMLDocument 〜 IHTMLDocument8 の各インターフェイスの定義は、こちらを参照してみてください。
>> https://docs.microsoft.com/ja-jp/previous-versions/windows/internet-explorer/ie-developer/platform-apis/aa752574%28v%3dvs.85%29
> 
> 先日ご紹介いただいた docs.microsoft.comのページを見ていた際に、
> IHTMLDocument8のみが IUnknownから継承されていて、他は IDispatchから継承と
> 書かれており、IDispatchは IUnknownから継承とあったので

すべての COM インターフェイスは IUnknown を継承しています。
IHTMLDocument8 もそうですし、IDispatch や IDispatchEx もそうです。

《OAIdl.h》より抜粋
MIDL_INTERFACE("00020400-0000-0000-C000-000000000046")
IDispatch : public IUnknown

《DispEx.h》より抜粋
MIDL_INTERFACE("A6EF9860-C720-11d0-9337-00A0C90DCAA9")
IDispatchEx : public IDispatch


> 「逆なのでは?」と疑問に感じました。
いずれもデュアルインターフェイスですね。
IHTMLDocument8 もまた、他と同様にディスパッチインターフェイスを持ちます。
宣言を見る限り、IHTMLDocument2 だけは IHTMLDocument から引き継がれるようですが。

ちなみにマネージの System.Windows.Forms.HtmlDocument クラスも、
内部では COM の IHTMLDocument2 インターフェイスを利用しています。


《MsHTML.h》もしくは《Mshtmlc.h》より抜粋

MIDL_INTERFACE("626FC520-A41E-11cf-A731-00A0C9082637")
IHTMLDocument : public IDispatch

MIDL_INTERFACE("332c4425-26cb-11d0-b483-00c04fd90119")
IHTMLDocument2 : public IHTMLDocument

MIDL_INTERFACE("3050f485-98b5-11cf-bb82-00aa00bdce0b")
IHTMLDocument3 : public IDispatch

MIDL_INTERFACE("3050f69a-98b5-11cf-bb82-00aa00bdce0b")
IHTMLDocument4 : public IDispatch

MIDL_INTERFACE("3050f80c-98b5-11cf-bb82-00aa00bdce0b")
IHTMLDocument5 : public IDispatch

MIDL_INTERFACE("30510417-98b5-11cf-bb82-00aa00bdce0b")
IHTMLDocument6 : public IDispatch

MIDL_INTERFACE("305104b8-98b5-11cf-bb82-00aa00bdce0b")
IHTMLDocument7 : public IDispatch

MIDL_INTERFACE("305107d0-98b5-11cf-bb82-00aa00bdce0b")
IHTMLDocument8 : public IDispatch


完全に蛇足ですが、 VB.NET の CallByName は DispId 呼び出しに対応しています。

 Dim doc As Object = GetHTMLDocument(hWnd_IE_Server)

 'Option Strict Off なら、オブジェクトのメンバーに実行時にアクセスできる
 Debug.WriteLine( doc.location.href )

 'Option Strict Off では、IHTMLLocation の Default Method を呼び出すために、このような書き方ができる
 Debug.WriteLine( CObj(doc.location)() )

 'Option Strict On でも、CallByName を使えばディスパッチインターフェイスで呼び出せる
 Debug.WriteLine(CallByName(CallByName(doc, "location", CallType.Get), "href", CallType.Get))
 Debug.WriteLine(CallByName(CallByName(doc, "location", CallType.Get), "", CallType.Get))

 'DispId で呼び出すこともできる
 Debug.WriteLine(CallByName(CallByName(doc, "[DispID=1026]", CallType.Get), "[DispId=0]", CallType.Get))


そして上記の DispID は《MsHtmdid.h》にて得ることができます。

#define DISPID_NORMAL_FIRST                     1000
#define DISPID_OMDOCUMENT                       DISPID_NORMAL_FIRST
#define DISPID_IHTMLDOCUMENT2_LOCATION          DISPID_OMDOCUMENT+26
#define DISPID_IHTMLLOCATION_HREF               DISPID_VALUE



> これです! このイメージでした!
> …ただ StrPtr(IID_IHTMLDocumentX) と指定している部分を AddrOfPinnedObjectで
> 取得したアドレスに置き換えようとしましたが、

その対応が間違っていると思います。

VBA における VarStr と StrPtr の違いは御存知なのですよね。
LPCSTR と LPCOLESTR の違いは把握されていますか?
http://hanatyan.sakura.ne.jp/logbbs1/wforum.cgi?mode=allread&no=1785&page=0#1805


TextOutA API などの LPCSTR であれば、VBA からは ByVal As String で渡せば良いですが、
CLSIDFromString が求める LPCOLESTR の場合は、StrPtr を使って渡すことになります。


さて、.NET から呼び出す場合はどうかというと…属性指定するのが一般的です。

http://www5b.biglobe.ne.jp/~yone-ken/VBNET/special/sp06_GetPrivateProfileString.html
https://docs.microsoft.com/en-us/dotnet/framework/interop/marshalling-strings
https://docs.microsoft.com/en-us/dotnet/framework/interop/default-marshalling-for-strings
https://docs.microsoft.com/en-us/dotnet/standard/native-interop/best-practices#string-parameters

というわけで、CLSIDFromString の VB.NET からの宣言例はこんな感じになります。

<DllImport("ole32.dll")>
Private Function CLSIDFromString(<MarshalAs(UnmanagedType.BStr)> ByVal lpsz As String, <Out> ByRef guid As Guid) As Integer
    ' 実際には Guid.ParseExact / Guid.Parse メソッドを使った方が手っ取り早い
End Function


どうしても明示的に BSTR を IntPtr として扱う必要がある場合は、
GCHandle クラスではなく Marshal クラスを使ってみてください。

BSTR の場合はこのあたりです。
 Marshal.StringToBSTR メソッド
 Marshal.PtrToStringBSTR メソッド
 Marshal.FreeBSTR メソッド

C 形式の文字列の場合はこのあたりです。
 Marshal.StringToHGlobalAnsi/Auto/Uni メソッド
 Marshal.StringToCoTaskMemAnsi/Auto/Uni/UTF8 メソッド
 Marshal.PtrToStringAnsi/Auto/Uni/UTF8 メソッド
 Marshal.FreeHGlobal メソッド
 Marshal.FreeCoTaskMem メソッド

注).NET Framework は UTF8 系メソッドをサポートしていません。

解決済み
引用返信 編集キー/
■99917 / inTopicNo.11)  Re[10]: IHTMLDocumentオブジェクトの取得に関して
□投稿者/ はなみ (6回)-(2022/06/19(Sun) 06:25:35)
No99868 (魔界の仮面弁士 さん) に返信
ありがとうございました。
遅くなってしまって失礼いたしました。

> 以下、蛇足情報。

蛇足と言いますか…深すぎます。

> そして上記の DispID は《MsHtmdid.h》にて得ることができます。

MsHtmdid.h を見てみましたが、「DISPID_HTMLDOCUMENTは DISPID_NORMAL_FIRST
なんだー」程度しか…

> VBA における VarStr と StrPtr の違いは御存知なのですよね。

あまり考えたことがなかったですが、VarPtr関数は、変数の先頭のメモリアドレス
StrPtrは VarPtrで得られるアドレスに格納されている値 ですか?

> 実際には Guid.ParseExact / Guid.Parse メソッドを使った方が手っ取り早い

はい。
あまり余計なことをするのではないとのご忠告ですね。

貴重な情報のリンクもありがとうございます。
とても勉強になります。
また、いろいろな場で高度なご発言をされているのも拝見いたしました。
貴重なお時間割いていただき恐縮いたします。
失礼ありましたらご容赦ください。

ありがとうございました。
引用返信 編集キー/


トピック内ページ移動 / << 0 >>

このトピックに書きこむ

過去ログには書き込み不可

管理者用

- Child Tree -