質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

ただいまの
回答率

89.12%

VBのFindWindow関数を繰り返すたびに第2引数の文字列が削れて減っていく

解決済

回答 3

投稿 編集

  • 評価
  • クリップ 0
  • VIEW 5,201

ot2os

score 22

自動で[プリンターのプロパティ]を呼び出し、[ノズルチェックパターン印刷]を実施するプログラムをVBで制作したいと思います。

途中、FindWindow関数実施時、2番目の引数(ウィンドウ名)の値が実施するたびに勝手に減っていきます。
ましてByVal参照なのに勝手に引数の値が変わる理由がわからず、アドバイスをお願いいたします。

ちなみに、プログラムを実行すると[プリンターのプロパティ]ウィンドウまでは出ます。
手動でのマウス操作は正常にタブ操作、ボタン操作できます。

●ソースコード(Form1.vb):Form1にはButton1のみ配置

Public Class Form1

    Declare Function FindWindow Lib "user32.dll" Alias "FindWindowA" _
    (ByVal lpClassName As String, ByVal lpWindowName As String) As Integer

    Declare Function FindWindowEx Lib "user32.dll" Alias "FindWindowExA" _
    (ByVal hwndParent As Integer, ByVal hwndChildAfter As Integer, _
    ByVal lpszClass As String, ByVal lpszWindow As String) As Integer


    'SendMessage用
    Declare Function SendMessage Lib "user32.dll" Alias "SendMessageA" _
    (ByVal hWnd As Integer, ByVal MSG As Integer, _
    ByVal wParam As Integer, ByVal lParam As Integer) As Integer


    Public Const BM_CLICK = &HF5
    Public Const CB_SETCURSEL = &H14E&

    Public Const WM_LBUTTONDOWN = &H201
    Public Const WM_LBUTTONUP = &H202

    Const MK_LBUTTON = &H1

    Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
        Dim Printer_Name As String = "【ダミー】Canon Inkjet MP630 series"

        'Processオブジェクトを作成
        Dim p As New System.Diagnostics.Process()

        'ComSpec(cmd.exe)のパスを取得して、FileNameプロパティに指定
        p.StartInfo.FileName = System.Environment.GetEnvironmentVariable("ComSpec")

        'ウィンドウを表示しないようにする
        p.StartInfo.CreateNoWindow = True
        'コマンドラインを指定("/c"は実行後閉じるために必要)
        p.StartInfo.Arguments = "/c start rundll32 printui.dll, PrintUIEntry /e /n """ & Printer_Name & """"

        '起動
        p.Start()

        'プロセス終了まで待機する
        'WaitForExitはReadToEndの後である必要がある
        '(親プロセス、子プロセスでブロック防止のため)
        p.WaitForExit()
        p.Close()


        Dim hwnd1 As Integer

        'Text: 【ダミー】Canon Inkjet MP630 series 印刷設定
        hwnd1 = FW("#32770", Printer_Name & " 印刷設定")
        Call FWex(hwnd1, 0, "SysTabControl32", "")

        'とりあえずここまで、この後、タブ切り替え、ボタン操作にてノズルチェックパターン印刷を実施

    End Sub

    Private Function FW(ByVal Class_Name As String, ByVal Window_Name As String)
        Dim hwnd As Integer = 0
        Do Until hwnd > 0
            Debug.Print("Window_Name:" & Window_Name)     '←●今回のImmidiate Window出力
            hwnd = FindWindow(Class_Name, Window_Name)
            System.Threading.Thread.Sleep(200)     '念のため0.2秒停止
            My.Application.DoEvents() '描画フリーズ防止
        Loop
        FW = hwnd
        Debug.Print(Window_Name & ":" & hwnd)
    End Function

    Private Function FWex(ByVal hwnd1 As Integer, ByVal Arg As Integer, ByVal Class_Name As String, ByVal Obj_Name As String)
        Dim hwnd2 As Integer = 0
        Do Until hwnd2 > 0
            hwnd2 = FindWindowEx(hwnd1, 0, Class_Name, Obj_Name)
            System.Threading.Thread.Sleep(200)     '念のため0.2秒停止
            My.Application.DoEvents() '描画フリーズ防止
        Loop
        FWex = hwnd2
        Debug.Print(Obj_Name & "、" & Class_Name & ":" & hwnd2)
    End Function

End Class

●Immediate Window の出力
#######
Window_Name:【ダミー】Canon Inkjet MP630 series 印刷設定
Window_Name:【ダミー】Canon Inkjet MP630 series
Window_Name:【ダミー】Canon Inkjet MP630 s
Window_Name:【ダミー】Canon Inkjet MP
Window_Name:【ダミー】Canon Inkj
Window_Name:【ダミー】Canon
Window_Name:【ダミー】
Window_Name:【ダ・
Window_Name:【・
Window_Name:【
Window_Name:・
Window_Name:・
Window_Name:・
Window_Name:・
#######

ちなみに、FindWindowの処理をFunction内ではなく、Private Sub Button1_Clickのまま実施すると、引数の値は変化せず、正常に該当hwndがヒットして処理が進みます。

        'hwnd1 = FW("#32770", Printer_Name & " 印刷設定")
        Do Until hwnd1 > 0
            Debug.Print("Window_Name:" & Printer_Name & " 印刷設定")
            hwnd1 = FindWindow("#32770", Printer_Name & " 印刷設定")
            System.Threading.Thread.Sleep(200)     '念のため0.2秒停止
            My.Application.DoEvents() '描画フリーズ防止
        Loop

開発環境:
OS:Windows10x86 1703
Visual Studio 2013

  • 気になる質問をクリップする

    クリップした質問は、後からいつでもマイページで確認できます。

    またクリップした質問に回答があった際、通知やメールを受け取ることができます。

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 過去に投稿した質問と同じ内容の質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

回答 3

+1

Declare Function での文字コード変換がうまくいっていない気がします。
Declare Function → Declare Auto Function としてみてはどうでしょうか?

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2017/07/24 16:18

    ありがとうございます。

    `Declare Auto Function`にしたところ、引数の値は勝手に変わらず、Debug.Printの結果は下記のまま繰り返しになりました。

    > Window_Name:【ダミー】Canon Inkjet MP630 series 印刷設定

    しかし、上記を繰り返すのみで、一向にhwndの値が変わりません。
    文字コードの問題ですかね。

    ちなみに、Function内で処理せずに、直接元の`Private Sub Button1_Click`内で同様の処理をすると、`Declare Function`でも正常に該当hwndがヒットして処理が進みました。

    キャンセル

+1

この現象は、.net frameworkの不具合だと思います。

まず、Visual Basic.NetでのDeclare文のString型のByValとByRefは、値渡し、参照渡しという意味ではありません。
String型をどういう形式に変換して、DLLに渡すかという指示になります。

ByValで渡しても、DLL側で文字列を変更すると、その結果はVB側に反映されます。

今回の現象では、VB側のUnicode文字列を、マルチバイト文字列に変更する際に、文字列の長さの計算法に不具合があり、このような結果になっていると思います。

なぜ、Functionに処理を切り出すと、不具合が発生するかは不明ですが、何らかのメモリ破壊を起こしているためと、思われます。

不具合を解消するためには、Declare文を以下のように変更するのが良いのではないでしょうか。

<DllImport("user32.dll", CharSet:=CharSet.Unicode)>
    Private Shared Function FindWindow(ByVal lpClassName As String, ByVal lpWindowName As String) As Integer
    End Function

この場合、DLLの関数としては、FindWindowWが呼ばれますが、CharSet.AnsiとするとFindWindowAが呼ばれます。どちらでも、動作に違いはありません。

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2017/07/25 14:55

    こちらもうまくいきました!

    お約束通り、Public Class Form1 の前の行に`Imports System.Runtime.InteropServices `を宣言していただいたコードをDeclareの代わりに記載で行けました!

    それにしても、 .net の不具合なんですね。
    不具合発生の仕組みまで教えていただいてありがとうございます。

    キャンセル

checkベストアンサー

0

Alias "FindWindowA" としていますが、Windows 10では FindWindowW を使うと良いでしょう。
現在Windows内部ではUnicodeが使われており、Unicodeを利用する場合末尾がAではなくWのAPIを利用します。

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2017/07/25 14:35

    ありがとうございます!

    FindWindowの宣言時に下記のようにAliasを "FindWindowW" 、Declare の後に Unicode を明記したところ、正常に動作しました!

    ```VB.net
    Declare Unicode Function FindWindow Lib "user32.dll" Alias "FindWindowW" _
    (ByVal lpClassName As String, ByVal lpWindowName As String) As Integer
    ```

    キャンセル

15分調べてもわからないことは、teratailで質問しよう!

  • ただいまの回答率 89.12%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる