ベスパリブ

プログラミングを主とした日記・備忘録です。ベスパ持ってないです。

ジョギング始めました

ソフトウェアエンジニアの人たちって、筋トレとかジョギングとかロードバイクとかやってますよね。なんか体を鍛えるとエンジニア力も上がるみたいな定説ありますよね。

やる気出すだめにスポーツウェアを買いました。
スポーツウェアがやる気スイッチになって、今の所三日坊主は回避できました。
ジョギングの効果が出るのは3ヶ月後らしいので、3ヶ月後にまた何か書きます。
書かなかった場合は察してください。

ポインタの値渡しを理解する(あるいはポインタのポインタの使い所)

以下のようなコードがあったわけです。

#include <stdio.h>
char data[16] = "abcdefg";

int main(void){
    // Your code here!
    char* pData = NULL;
    
    pData = data;

    printf("%s\n", pData); // abcdefg
}

以下のように関数にしたかったわけです。

#include <stdio.h>
char data[16] = "abcdefg";

void func(char* pData){
    pData = data;
}

int main(void){
    // Your code here!
    char* pData = NULL;
    
    func(pData);

    printf("%s\n", pData);  // Runtime error(Exit status:139(Invalid memory reference))
}

エラーが出るわけです。
それぞれのアドレスを表示してみます。

#include <stdio.h>
char data[16] = "abcdefg";

void func(char *pData){
    pData = data;
    printf("func-in: %d\n", pData);  // func-in: 6295616
}

int main(void){
    // Your code here!
    char* pData = NULL;
    
    func(pData);
    printf("func-out: %d\n", pData);  // func-out: 0
    
    pData = data;
    printf("%d\n", pData);  // 6295616
    
}

これなんでだろ~ってずっと悩んでいたんですが、小一時間悩んで、このfunc()に渡しているpDataはいわゆる「ポインタの値渡し」というやつでは?と思い至る。
その場合、ポインタのポインタを渡せば解決できるかもと思い修正。func2()を作成します。

#include <stdio.h>
char data[16] = "abcdefg";

void func(char *pData){
    pData = data;
    printf("func-in: %d\n", pData);  // func-in: 6295616
}

void func2(char **pData){
    *pData = data;
    printf("func2-in: %d\n", *pData);  // func2-in: 6295616
}

int main(void){
    // Your code here!
    char* pData = NULL;
    
    func(pData);
    printf("func-out: %d\n", pData);  // func-out: 0
    
    func2(&pData);
    printf("func2-out: %d\n", pData);  // func2-out: 6295616
    printf("%s\n", pData);  // abcdefg
    
    pData = data;
    printf("%d\n", pData);  // 6295616
    
}

オッ、意図したとおりに動作しました。
C言語に参照なし」とは言いますが、こうして見ると、「&」が参照渡しの記号に見えてしょうがない。

WM_IME_CHAR、WM_IME_KEYDOWN、WM_IME_KEYUPの挙動を調べた

WinAPIの話です。

VK_DELETE メッセージを送信したら、"."(ドット)が出力される問題

仮想キーVK_DELETEをSendMessageしたら、なぜか"." (ドット) が出力されてしまう問題に悩んでいました。

原因は単純で、SendMessage(hWnd, WM_IME_CHAR, VK_DELETE, 0)としていたせいでした。

WM_IME_CHARとは何か。
http://www.geocities.jp/katayama_hirofumi_mz/imehackerz/ja/WM_IME_CHAR.html
によると、第三引数のwParamはUnicode文字の値と書いてあります。

VK_DELETEは0x2eです。
WikipediaUnicodeを調べます。
https://ja.wikipedia.org/wiki/Unicode%E4%B8%80%E8%A6%A7_0000-0FFF
なるほど。ドットです。

WM_IME_CHARとは?

WM_IME_CHARの仕様は、

IMEが変換結果の文字を取得するときに、アプリに送られます。ウィンドウは、そのウィンドウプロシージャーを通じてこのメッセージを受け取ります。

らしいですが、ちょっとよくわかりません。

原文のMSDNも読みましたが、やはりちょっとよくわかりません。

よくわかっていないなりの理解なのですが、Unicodeを送信したいときはWM_IME_CHARを使えということでしょう。実際、そういう動作をします。

Call SendMessage(hWnd, WM_IME_CHAR, &H21, 0)  ' !
Call SendMessage(hWnd, WM_IME_CHAR, &H2E, 0)  ' .
Call SendMessage(hWnd, WM_IME_CHAR, &H7E, 0)  ' ~ 

結局、DELETEを送信するには?

やりたいことは「DELETEキーを押したい」と同じです。
ここにきて、"仮想キー"の考えが腑に落ちた気がします。仮想キーは文字通り「仮想的なキーボード」なので、「キーを押して」、「キーを離す」というメッセージを送信するということになります。

Call SendMessage(hWnd, WM_IME_KEYDOWN, VK_DELETE , 0)
Call SendMessage(hWnd, WM_IME_KEYUP, VK_DELETE, 0)

何かがおかしい

Call SendMessage(hWnd_Edit, WM_IME_CHAR , Asc("a"), 0)
Call SendMessage(hWnd_Edit, WM_IME_CHAR , Asc("b"), 0)
Call SendMessage(hWnd_Edit, WM_IME_CHAR , Asc("c"), 0)
Call SendMessage(hWnd_Edit, WM_IME_KEYDOWN, VK_BACK, 0)
Call SendMessage(hWnd_Edit, WM_IME_KEYUP, VK_BACK, 0)

とすると、"b"と"c"が消えました( VK_BACK はBackSpace)。
WM_IME_KEYDOWN と WM_IME_KEYUPでワンセットかと思いきや、 WM_IME_KEYDOWN と WM_IME_KEYUPで二回VK_BACKを押したことになっているようです(私の環境だけ?)。

ちなみに、「押しっぱなし」がどうなるのかも確かめてみました。

Call SendMessage(hWnd_Edit, WM_IME_CHAR , Asc("a"), 0)
Call SendMessage(hWnd_Edit, WM_IME_CHAR , Asc("b"), 0)
Call SendMessage(hWnd_Edit, WM_IME_CHAR , Asc("c"), 0)
Call SendMessage(hWnd_Edit, WM_IME_KEYDOWN, VK_BACK, 0)

"c"が消えただけで、"a"と"b"は残りました。どうやら「押しっぱなし」のようなことにはならないようです。

さらに、VK_LEFT(左キー)の挙動を調べました。

Call SendMessage(hWnd_Edit, WM_IME_CHAR, Asc("a"), 0)
Call SendMessage(hWnd_Edit, WM_IME_CHAR, Asc("b"), 0)
Call SendMessage(hWnd_Edit, WM_IME_CHAR, Asc("c"), 0)
Call SendMessage(hWnd_Edit, WM_IME_CHAR, Asc("d"), 0)
Call SendMessage(hWnd_Edit, WM_IME_CHAR, Asc("e"), 0)
Call SendMessage(hWnd_Edit, WM_IME_KEYDOWN, VK_LEFT, 0)
Call SendMessage(hWnd_Edit, WM_IME_KEYUP, VK_LEFT, 0)
Call SendMessage(hWnd_Edit, WM_IME_KEYDOWN, VK_LEFT, 0)
Call SendMessage(hWnd_Edit, WM_IME_KEYUP, VK_LEFT, 0)
Call SendMessage(hWnd_Edit, WM_IME_CHAR, Asc("X"), 0)
' "abcXde"
Call SendMessage(hWnd_Edit, WM_IME_CHAR, Asc("a"), 0)
Call SendMessage(hWnd_Edit, WM_IME_CHAR, Asc("b"), 0)
Call SendMessage(hWnd_Edit, WM_IME_CHAR, Asc("c"), 0)
Call SendMessage(hWnd_Edit, WM_IME_CHAR, Asc("d"), 0)
Call SendMessage(hWnd_Edit, WM_IME_CHAR, Asc("e"), 0)
Call SendMessage(hWnd_Edit, WM_IME_KEYDOWN, VK_LEFT, 0)
Call SendMessage(hWnd_Edit, WM_IME_KEYDOWN, VK_LEFT, 0)
Call SendMessage(hWnd_Edit, WM_IME_CHAR, Asc("X"), 0)
' "abcXde"
Call SendMessage(hWnd_Edit, WM_IME_CHAR, Asc("a"), 0)
Call SendMessage(hWnd_Edit, WM_IME_CHAR, Asc("b"), 0)
Call SendMessage(hWnd_Edit, WM_IME_CHAR, Asc("c"), 0)
Call SendMessage(hWnd_Edit, WM_IME_CHAR, Asc("d"), 0)
Call SendMessage(hWnd_Edit, WM_IME_CHAR, Asc("e"), 0)
Call SendMessage(hWnd_Edit, WM_IME_KEYUP, VK_LEFT, 0)
Call SendMessage(hWnd_Edit, WM_IME_KEYUP, VK_LEFT, 0)
Call SendMessage(hWnd_Edit, WM_IME_CHAR, Asc("X"), 0)
' "abcdeX"

この辺、挙動を確認しながらプログラム組まないと、よくわからないバグになりそうです。環境による気もしてきました……。 どこかに良い書籍や情報はないでしょうか。

使い分けとか

キーボードでできることは仮想キーにやらせよう(オライリー・ジャパン風)
逆に、 「ʨ」とかの入力はUnicodeでしかできないと思います(入力できるのか知らないけど。私の環境だと?に変換された)

補足

MSDNを辿っていると、知識として入れておいたほうが良さそうなページを見つけました。
キーボード入力

Editコントロールが入力可能になるまで待つ

WinAPIの話です。
Editコントロールのハンドルを取得できても、Editコントロールに入力可能というわけではないようです。
このせいで、ハンドル取得した直後に文字列をSendMessageしても、テキストボックスに文字が入っていない場合があります。

しょうがないので、Editコントロールが入力可能になるまで待つ関数を作ります。
方法としては、
1. Editコントロールに適当な文字をSendMessageし、文字数を取得する。
2. 文字数が1文字以上なら、入力可能なので、文字を消して終了
3. 文字数が0ならば、入力できていないので、1.に戻る
です。単純ですね。

Public Declare Function SendMessage4GetText Lib "user32" Alias "SendMessageW" (ByVal hWnd As IntPtr, ByVal wMsg As Integer, ByVal wParam As Integer, ByVal iParam As System.Text.StringBuilder) As Integer
Public Const VK_BACK As Integer = &H8  ' BackSpace

Public Sub Wait4Edit(ByVal hWnd As IntPtr)
    Dim buf As New System.Text.StringBuilder()
    While 1
        buf.Length = 2048
        System.Windows.Forms.Application.DoEvents()
        Call SendMessage(hWnd, WM_IME_CHAR, Asc("a"), 0)  ' 適当な文字を送信する
        If SendMessage4GetText (hWnd, WM_GETTEXT, buf.Length, buf) >= 1 Then  ' 入力できていたら、文字数が1文字以上になる
            Exit While
        End If
        System.Threading.Thread.Sleep(100)
    End While
    ' 適当な文字を送っているので、コントロールに入力されている文字を全部消す
    While 1
        buf.Length = 2048
        System.Windows.Forms.Application.DoEvents()
        Call SendMessage(hWnd_Edit, WM_IME_KEYDOWN, VK_BACK, 0)
        Call SendMessage(hWnd_Edit, WM_IME_KEYUP, VK_BACK, 0)
        If SendMessage4GetText (hWnd, WM_GETTEXT, buf.Length, buf) = 0 Then
            Return
        End If
        System.Threading.Thread.Sleep(100)
    End While
End Sub

引数hWndにはEditコントロールのハンドルを与えます。
与えるハンドル間違えたりすると無限ループから返ってこないことに注意。

なぜWhile文を2つに分けているかというと、最初一つにまとめていたのですが、なぜか入力ボックスに文字が残っていたりした("a"が2回送られる? or VK_BACKが反応しない?)ので、「文字を消す」処理を確実に行わせたかったため二つに分けています。この辺の仕様をまだよくわかっていません。でもまあ、動くしいいか(適当)

追記[20180525]

IsWindowVisible()とIsWindowEnabled()という便利な関数を見つけました。これを使えば良さそうです。

Public Declare Function IsWindowEnabled Lib "user32" Alias "IsWindowEnabled" (ByVal hWnd As IntPtr) As Boolean
Public Declare Function IsWindowVisible Lib "user32" Alias "IsWindowVisible" (ByVal hWnd As IntPtr) As Boolean

Public Sub Wait4Edit(ByVal hWnd As IntPtr)
    ' エディットボックスが入力可能になるまで待つ
    While True
        If (IsWindowEnabled(hWnd) = True) And (IsWindowVisible(hWnd) = True) Then
            Exit While
        End If
        System.Threading.Thread.Sleep(10)
    End While
End Sub

FindWindowに「タイムアウトするまで探し続ける機能」欲しい!!!!

うるさい!!!
WinAPIの話です。

FindWindowの問題点

FindWIndowでウィンドウハンドルを取得する際、PCが重かったりするとウィンドウの表示が遅れたりして取得に失敗したりするので、FindWindow()前にSleepするという方法がありますが、これもどれだけSleepすればよいかという問題を抱えており、PCが激重だった場合、Sleepして待っても結局ウィンドウハンドルが取得できないという問題も抱えています。

このような問題はFindWIndowに「タイムアウトするまで探し続ける機能」があればそれで解決する話なので、そのようなラッパー関数を作成しました。できれば標準で欲しかった。

Declare Unicode Function FindWindow Lib "user32.dll" Alias "FindWindowW" (ByVal lpClassName As String, ByVal lpWindowName As String) As IntPtr

Public Function FindWindow2(ByVal cnm As String, ByVal cap As String, Optional ByVal timeout As Integer = 10000) As IntPtr
    ' ウィンドウが見つからない場合、timeoutミリ秒経過するまで無限ループで取得し続ける
    ' タイムアウトしたら0を返す
    ' 関数名はもっと考えよう
    Dim hWnd As IntPtr = 0
    Dim sw As New System.Diagnostics.Stopwatch()
    sw.Start()
    While 1
        System.Windows.Forms.Application.DoEvents()
        hWnd = FindWindow(cnm, cap)
        If hWnd <> 0 Then
            Return hWnd
        End If
        If sw.ElapsedMilliseconds >= timeout Then
            ' タイムアウトした
            Return 0
        End If
        'System.Threading.Thread.Sleep(500)
    End While
    Return hWnd
End Function

CPU使用率が気になる場合は、ループ内で適当な時間Sleepさせれば良いと思います。

2018年の目標

2月の終わりですが、2018年の目標を決めました。

1. 英語
2. 競プロ
3. 機械学習系のプロダクトを1つ作成

英語を一番頑張りたいと思います。
他2つはジャストアイデアですが、興味がある分野なのでやっていきたいと思います(やらなそう)。

英語

・英検1級
・TOEIC900台
を目標とします。
たしか3年前くらいのTOEICが600台だったような気がします。
現状はReadingはまあまあできます。
Listening、Writing、Speakingがボロボロです。
なので結構強気の目標です。
なんで英検なのかっていうのは、WritingとSpeakingがあるからです(Writingは申し訳程度ですが)。
TOEFLにしろよと突っ込まれそうですが、TOEFLには怖いイメージしかないので英検にしました。

競プロ

最近競技プログラミングをはじめました。
全然問題解けないけど面白いので、趣味としてやろうと思います。
目標スコアは……よくわかりません。とりあえず一年やってみた結果どの程度になるのか見ようと思います。

機械学習系のプロダクトを1つ作成

なんかします(やらなそう)。

以上です。