2014年5月28日水曜日

GetWindowText のデッドロック

こんにちは。yu1rowです。

GetWindowText(SendMessage で WM_GETTEXT を使用した場合も同じ) でハマったので、その情報を備忘録として残します。
※回避コードだけ見るなら、一番下までスクロール。

マルチスレッドで EnumWindows で列挙した全 Window の文字列を GetWindowText やSendMessage を使って調べる処理を DLL に記述。
そしてこの DLL の関数を Excel VBA から呼び出すというケースで、スレッドがブロックしてしまっていつまでも終了しないことがある。

SendMessage を MSDN で確認すると、以下のように記述されていた。

複数のスレッド間で送信されたメッセージが処理されるのは、受信側スレッドがメッセージ取得コードを実行したときだけです。送信側スレッドは、受信側スレッドがメッセージの処理を終えるまで、ブロックされます(待機状態になります)。

しかし、日本語版に無い情報を英語版の MSDN で発見。
To prevent this, use SendMessageTimeout with SMTO_BLOCK set.For more information on nonqueued messages, see Nonqueued Messages.
MSDN に限ったことではないけれど、API リファレンスの英語版には日本語訳では見つけられない重要な文章がサラッと書かれている場合が見受けられる。英語版もたまには読んでみたほうがいい。

全ウィンドウの文字列を GetWindowText(SendMessage) で取得する処理を別スレッドで起動・終了待ちをする処理をExcel VBA で行うときに問題が発生した。
その別スレッドから Excel のウインドウ自体にも SendMessage することになるんだけど、そうするとずっと返ってこない。ブロックしちゃうってことらしい。

SendMessage でブロックされていてスレッドが終了しない場合、SendMessage の代わりにSendMessageTimeout を使用して、適切なタイムアウト時間を指定すれば良いとのこと。
以下コード例。
SendMessageTimeout(hWnd, WM_GETTEXT, 1024, (LPARAM)text, SMTO_BLOCK, 100, &dwRet);
  • 上記 100 となっている値は適切なタイムアウト時間を設定すること
  • SMTO_NORMAL、SMTO_BLOCK|、MTO_ABORTIFHUNG を必要に応じて組み合わせて使用すると良いと思う(オプションの詳細は MSDN 見てね)

ただし上記を使用すると、スレッド終了後の待機(WaitForSingleObject とか)でなんかちょっとだけ待機時間が発生して遅くなる。
上記の例であれば、「100msec×スレッド数」ぐらい?

これが気になる場合、「呼出元プロセスのウインドウに対して SendMessage するから止まる」 というデッドロックが元々の原因であるとすれば、上記の SendMessageTimeout を使用する前に、GetWindowThreadProcessId と GetCurrentProcessId を使用して呼出元プロセスのウインドウを回避するチェックを組み合わせるともっと良いかもしれない。
こうすると、ぅぉっなんか遅いってならない。ピュッピュッって終了してくれる。

以下上記を実装した例。呼出元プロセスのウィンドウは回避、ブロックしても 100ms で終わるようにしている。
DWORD pID;
GetWindowThreadProcessId(hWnd, &pID);
if (GetCurrentProcessId() == pID)
{
    return 0;
}

DWORD dwRet;
if (SendMessageTimeout(hWnd, WM_GETTEXT, 1024, (LPARAM)text, SMTO_BLOCK, 100, &dwRet) == 0)
{
    return 0;
}

ハマった。こんときゃ辛かった。

2014年5月27日火曜日

C# の配列変換

こんにちは。yu1row です。

配列変換の方法を備忘録として書いておきます。
int[] arr みたいな配列があったとして、これから string[] を得る場合、どしたらいいんでしょうか?

当然、これはダメです。
int[] arr = new int[] { 1, 2, 3 };
string[] dest = (string[])arr; // Can't compile

以下のように単純にループで回せば、まぁいけますよね。
int[] arr = new int[] { 1, 2, 3 };
string[] dest = new string[arr.Length];
for (int i = 0; i < arr.Length; i++)
{
    dest[i] = arr[i].ToString();
} 

Array.CovertAll() を使えばなんかすっきりした感じになる気がします。
int[] arr = new int[] { 1, 2, 3 };
string[] dest = Array.ConvertAll(arr, delegate(int i) { return i.ToString(); });

ラムダ式が使える(C# 3.0以降)のであれば、もっとすっきりした感じになる気がします。
int[] arr = new int[] { 1, 2, 3 };
string[] dest = Array.ConvertAll(arr, i => i.ToString());

これだけです。 もっとカッコイイくて短くてジェネリックで、特殊条件にも対応できるコードにできる方法があれば、誰か教えてください。

2014年5月26日月曜日

なんてステキなString.Join()

こんにちは。yu1rowです。

区切り文字を使った文字列連結に便利な String.Join() のご紹介です。
C# に限らず、区切り文字を使った文字列の連結はちょっと面倒に感じることがあります。
たとえば、以下の例のような{"A","B","C"}のような配列を"A - B - C"みたいに繋げたいとき、どうしますか?

入力配列出力値
{"A","B","C"}"A - B - C"
{"A"}"A"
{}""

.NET では、String に String.Join(String, String[]) というメソッドがありまして、これで上記を実現してくれます。

コード例
string[] arr = new string[]{ "A", "B", "C" };
Console.WriteLine(String.Join(" - ", arr)); // Output: "A - B - C"

便利。使わない理由はないと思う。

ただ、これ入力が String の配列なんで、int 型とか、他の型の配列はいちいち変換しなきゃいけない。
ただし、.NET Framework 4 以降は String.Join(String, Object[]) なんてオーバーロードが追加されまして、これが便利なんですわ。

一応コード例
int[] arr = new int[] { 1, 2, 3 };

// For C# 2.0
Console.WriteLine(String.Join(" - ", Array.ConvertAll(arr, delegate(int n) { return n.ToString(); })));

// For C# 3.0 or later
Console.WriteLine(String.Join(" - ", Array.ConvertAll(arr, n => n.ToString())));

// For .NET Framework 4.0 or later
Console.WriteLine(String.Join(" - ", arr));


使わんテはありまへんな! 今日のネタはこれだけ。それでは!

2014年4月30日水曜日

IME と Windows8

こんにちは。yu1rowです。

Windows 8 や Windows 8.1 が登場してしばらくが経ちましたが、アプリケーションによってはIMEの制御に問題が出ることがあり、調査を行いましたのでメモを残します。

デスクトップなアプリケーションでは、テキストボックスにカーソルが移動した場合、日本語入力を自動的に切り替えたりするなんてことをする場合があります。
Windows 8 より以前では、他のウィンドウに切り替えて入力方法を切り替えても、元のウィンドウには何の影響もありませんでした。
Windows 8 以降では、インストールした直後の設定では、日本語入力モードはユーザ(セッション)毎に共通で、一つのウィンドウでの変更が他のウィンドウに影響してしまうという現象が起こる可能性があります。
※ちなみに Windows Server 2012 でも Windows 8 と同じことが起こります。

Windows 7 までの動作と同じにしたい場合、コントロールパネルから切り替えることが出来ます。
  • (jp) - [言語] - [詳細設定] - [アプリ ウィンドウごとに異なる入力方式を設定する]
  • (en) - [language] - [advanced settings] - [Let me set a different input method for each app window]

この操作をわざわざ顧客にさせるようなベンダーはあまり居ないと思いますけどね。

アプリケーションの開発者が現在の設定を確認するには SystemParametersInfo という API を使用します。
IDE 立ち上げるのも面倒だったので Excel VBA の標準モジュールで関数を書いてみました。
Option Explicit
#If VBA7 Then
Declare PtrSafe Function SystemParametersInfo Lib "user32" Alias "SystemParametersInfoA" (ByVal uAction As Long, ByVal uparam As Long, ByRef lpvParam As Any, ByVal fuWinIni As Long) As Long
#Else
Declare Function SystemParametersInfo Lib "user32" Alias "SystemParametersInfoA" (ByVal uAction As Long, ByVal uparam As Long, ByRef lpvParam As Any, ByVal fuWinIni As Long) As Long
#End If
Const SPI_GETTHREADLOCALINPUTSETTINGS As Long = &H104E

' ------------------------------------------------------------
' [Description]
'  IMEの入力モードがセッション毎/スレッド毎どちらで
'  保持されるかの設定状況を返します。
' ------------------------------------------------------------
' [Returns]
'  0 : セッション毎(Windows 8以降のみ)
'  1 : スレッド毎
' ------------------------------------------------------------
' [Author]
'  yu1row
' ------------------------------------------------------------
Public Function GetThreadLocalInputSettings() As Integer
    Dim ret As Long
    Dim data As Long
    ret = SystemParametersInfo(SPI_GETTHREADLOCALINPUTSETTINGS, 0, data, 0)
    If ret <> 0 Then
        GetThreadLocalInputSettings = IIf(data = 0, 0, 1)
    Else
        GetThreadLocalInputSettings = 1
    End If
End Function

SPI_GETTHREADLOCALINPUTSETTINGS(0x104E) は Windows 8 から追加されています。また、SPI_SETTHREADLOCALINPUTSETTINGS(0x104F) も追加されており、これでアプリケーションから設定の変更も可能です。

さて、「コントロールパネルの設定を変えずに、他のウィンドウに影響させずに入力モードを切り替えたい」というワガママソリューションは存在するのでしょうか?
未検証ですが、以下の手順でうまくいくかもしれません。保証できませんけど。

  1. Windows 8 で、かつ入力モードがアプリ毎保持ではない場合、アプリ毎保持に変更する(SPI_GETTHREADLOCALINPUTSETTINGS / SPI_SETTHREADLOCALINPUTSETTINGS)
  2. IME の制御を行う (ImmSetConversionStatus とか)
  3. 1 でアプリ毎保持に変更を行っていた場合、設定を元に戻す(SPI_SETTHREADLOCALINPUTSETTINGS)
どうなんでしょうかね。やってみた人、結果教えてくれると嬉しいですw

それでは。

2013年9月14日土曜日

Windows 資格情報コンテナを編集する

Windows 資格情報コンテナを変更する方法


Windowsの共有フォルダにアクセスする際、パスワードを保存しておき、次からは入力をしなくていいうように資格情報を保存してある場合があります。
これを修正する方法です。

※全てWindows 7 Professionalでの確認のみ行っています

  1. [コントロールパネル] -  [ユーザーアカウント] - [資格情報の管理]
     
  2. ファイル名を指定して実行([Windows]キー+R)
    > rundll32 keymgr.dll,KRShowKeyMgr
     
  3. (コマンドプロンプトから) "cmdkey"
    rem 資格情報一覧を表示
    cmdkey /list
    
    rem 資格情報を削除する例
    cmdkey /delete:192.168.0.100
    
    rem 資格情報を追加する例(パスワードが尋ねられる)
    cmdkey /add:192.168.0.100 /user:userid /pass


一時的に保存された資格情報を削除する



上記の資格情報とは別に一時的に資格情報が保存されます。
「一時的」 とは言うものの、いつまで残っているのかは未検証です。

これはコマンドで削除が可能です。

ネットワークドライブの割り当てなどで「現在、指定されたネットワークフォルダーは異なるユーザー名とパスワードを使って割り当てられています。」というメッセージが表示されたときなどに削除すると解決するかもしれません。

※削除されるまで少し間があるようです(1分程度のタイムラグ?)

rem 現在の資格情報の割り当てを確認
net use

rem いらないものを /delete で削除。
net use \\192.168.100.NNN\folder /delete

rem 全部消すなら以下でもOK(消してもいいなら)
net use * /delete /y


2013年2月19日火曜日

Excelで正規表現を使いたくて

Excelシート上で正規表現検索ってできたっけ?


どうも。yu1row です。

ふつーは正規表現での検索はできないみたいです。
それを可能にするプラグインを入れて試してみます。

ダウンロード


[SEのためのExcelツール] - [正規表現検索 Excel用正規表現検索ダイアログアドイン]

インストール


ダウンロードしたZIPファイルを展開してできたexcelreフォルダ内のexcelre.xlaをダブルクリック。
この際、マクロを有効にして下さい。
言語選択するダイアログが出たら「はい」で英語、「いいえ」で日本語になります。

基本的な使い方


  • 検索ダイアログを表示する
    Ctrl+R
これだけw

オレオレトラブルシューティング


  • ESC → 「コードの実行が中断されました。」

    →「継続」とすると大体大丈夫

  • 検索に引っかからない

    →検索条件を見直してみる

    →ダメな場合、セルを選択している場合は選択を解除してみる

     
  • 「実行時エラー'5': プロシージャの呼び出し、または引数が不正です。」

    →[検索]-[検索する文字列] のテキストボックス内で Ctrl+Z や Ctrl+X を押すと起きるので、以下の方法を試す

  • 「実行時エラー'13': 型が一致しません。」

    →Excelの再起動、もしくは以下の方法

    1. [ツール] - [アドイン] - [Excelre]のチェックOFF - [OK]
    2. 「実行時エラー'75': パス名が無効です。」が出たら[終了]
    3. もういちど[ツール] - [アドイン] - [Excelre]のチェックON - [OK]
Word用のもあるっぽい。
こんど試してみます。

以上です。

2013年2月6日水曜日

PukiWiki の WikiName を解除したい

yu1row です。
今回のネタはタイトルのまんま。

勝手にリンクされてしまう


PukiWiki でページ編集してて勝手にリンクされちゃうのがイヤやなって思って調べたんです。
勝手にリンクされちゃうのは例えば
  • NullPointerException
  • ValueName
みたいな。
「大文字で始まる単語が2つ以上続く文字列」が勝手にリンクされるみたいです。
正規表現的には
\b([A-Z][a-z]+){2,}\b
みたいな。

特定の場所だけリンクされたくないって場合どうするのか?って話です。

解決方法は大文字を"&#xNN;"みたいな数値文字参照で置き換えるってことらしい。面倒。

とりあえずツール作るのは自己満足の極み


変換ツールを作ったんで使いたい人は使ってね(投げやり)
[Escape the WikiName]

需要、あるかなぁ?
今回はここまでー!