
Windows7 VB6 / VBA の SendKeys の問題について ( エラー 70 書き込みできません。 が発生)
Windows7で仮想化したVB6製のプログラムや、Excel2000 、Access2000 等のOffice2000を実行しVBAのマクロを動かした場合に、エラーが発生することがあります。
具体的には、SendKeys というファンクションをプログラムの中で利用している場合です。
SendKeys は、キーボードのキーを押した事にするためのファンクションです。
キーボードのaを押した事にする場合はこんな感じでコードを書きます。
Call SendKeys("a")
------宣伝-----
※VMware ThinApp (アプリケーション仮想化)に興味のある方は、こちらを
御覧ください。
------宣伝-----
始めに、SendKeys の問題は、仮想アプリケーションの問題ではありません。
※VMware ThinApp (アプリケーション仮想化)に興味のある方は、こちらを
御覧ください。
[VMware ThinApp 製品概要と活用 解説書 (日本語 SoftBankBB製) の紹介]
http://tunemicky.blogspot.jp/2013/08/vmware-thinapp-softbankbb.html------宣伝-----
始めに、SendKeys の問題は、仮想アプリケーションの問題ではありません。
ですので、ThinAppの問題ではありません。
Windows7の仕様によるものです。
正確には、Windows Vistaから、UACが搭載されUIPIという物が導入されました。
セキュリティの強化に伴う制限事項であり、仕様変更です。
詳しい説明は、以下のリンクから
対処方法は、UACを無効にすることになりますが、せっかくのセキュリティの強化機能を
無効化するのは、おすすめしません。
無効化するのは、おすすめしません。
対処方法としては、以下になります。
・UACを無効にする (お勧めしません。)
・コードを書き換える (お勧めですが。。。)
SendInput へとコードを修正します。
・Windows7に対応したバージョンを利用する。
VB6の場合
MSVBVM60.DLL | |||
項目 | バージョン | リリース | SendKeys |
Windows7 SP1標準 | 6.00.9815 | March 5, 2009 | 問題なし |
WindowsXP SP3標準 | 6.00.9802 | August 1, 2007 | 問題なし |
Windows2000 SP4標準 | 6.00.9690 | September 3, 2002 | エラー |
VisualBasic6 SP6 標準 | 6.00.9782 | February 23, 2004 | エラー |
仮想アプリケーションを作成した環境により、非対応のモジュールがキャプチャされ
動作に影響をおよぼすことが考えられます。
例)Windows 2000 環境でキャプチャを行い、VB6 SP6のランタイムをインストール
した場合に、%SystemSystem%フォルダに、対応していないバージョンの
MSVBVM60.dllが含まれてしまう可能性があります。
この場合は、対応するバージョンに変更するか、
MSVBVM60.dll自体を削除することで問題が無くなります。
VBAの場合
VBE6.DLL | |||
項目 | バージョン | リリース | SendKeys |
Office2000 SP4 | 6.00.8967 | August 24, 2000 | エラー |
OfficeXP SP3 | 6.04.9969 | June 30, 2003 | エラー |
OfficeXP SP3 (+最新パッチ) | 6.05.1053 | August 25, 2009 | 問題なし |
Office2003 SP3 | 6.05.1024 | May 8, 2007 | 問題なし |
Office2007 SP3 | 6.05.1053 | August 25, 2009 | 問題なし |
※バージョン6.05.xxxx系は、問題ないように対策されています。
Office2007 以降は、問題ありません。
2012/08/14追加:
OfficeXP SP3の場合は、対策されていません。
そのため、OfficeXP SP3を更新して最新の状態にすると、対策されたバージョンがインストールされます。
無保証ですが、Office2000の場合は、VBE6.DLLを対策された物に更新したところ
問題が無くなりました。
せっかくなので、もう少し詳細に調査したことを書きたいと思います。
始めに以下のリンクを参照ください。
「Windows Vista 上の Office アプリケーションで SendKeys ステートメントが失敗する」
「この問題は、Office 2003 で使用される VBA 6.0 の SendKeys ステートメントにおいて、
ジャーナル フックが使用されているために発生します。
Windows Vista の新しいセキュリティ機能では、ジャーナル フックが利用できません。」
「ジャーナル フック」がポイントです。
キーボードの情報を送信するときの手法は、いくつかあります。
問題になっているのは、VB6 / VBAの中で SendKeys を呼び出した場合ですが
実際に、どのような呼び出しになっているかを確認すると
以下の用になっています。
1.送信するキーをキーボードに対応した仮想キーコードへと変換
VkKeyScanW ->short=41h (WCHAR ch=L'a')
2.仮想キーの状態を取得
GetKeyboardState ->int=1h (PBYTE lpKeyState=*18DA8Ch->'\00h')
3.フックプロシージャを使って、キーボードをフックします。
SetWindowsHookExW (int idHook=1h, HOOKPROC lpfn=*69208967h, HINSTANCE hmod=400000h, struct {DWORD dwThreadId=0h)
http://msdn.microsoft.com/ja-jp/library/aa480152.aspx#EKFAG
を確認すると、まさに制限事項として書かれています。
抜粋すると
次に、対策されたバージョンの呼び出しがどのようになっているか確認すると
以下の用になっています。
1.送信するキーをキーボードに対応した仮想キーコードへと変換
2.仮想キーの状態を取得
3.フックプロシージャを使って、キーボードをフックします。
までは、同じです。
3.でエラーが発生した場合は、更に以下の用に対策されています。
4.キーボードの入力をブロックします。
BlockInput ->int=0h (BOOL fBlockIt=1h)
5.仮想キーの状態を取得
GetKeyboardState ->int=1h (PBYTE lpKeyState=*18D904h->'\00h')
「ジャーナル フック」がポイントです。
キーボードの情報を送信するときの手法は、いくつかあります。
問題になっているのは、VB6 / VBAの中で SendKeys を呼び出した場合ですが
実際に、どのような呼び出しになっているかを確認すると
以下の用になっています。
1.送信するキーをキーボードに対応した仮想キーコードへと変換
VkKeyScanW ->short=41h (WCHAR ch=L'a')
2.仮想キーの状態を取得
GetKeyboardState ->int=1h (PBYTE lpKeyState=*18DA8Ch->'\00h')
3.フックプロシージャを使って、キーボードをフックします。
SetWindowsHookExW (int idHook=1h, HOOKPROC lpfn=*69208967h, HINSTANCE hmod=400000h, struct {DWORD dwThreadId=0h)
※このSetWindowsHookExW で、エラーが発生します。
第1引数を確認すると、int idHook=1h
になっています。
WinUser.hで確認すると、
#define WH_JOURNALPLAYBACK 1
となっています。
MSDNライブラリには、
WH_JOURNALPLAYBACKは、「グローバルのみ」と書かれているため
権限がそれなりに必要です。
なので、「エラー70番 書き込みできません。」が発生することになります。
http://msdn.microsoft.com/ja-jp/library/aa480152.aspx#EKFAG
を確認すると、まさに制限事項として書かれています。
抜粋すると
ジャーナル フック
WH_JOURNALPLAYBACK と WH_JOURNALRECORD は本質的にプロセス間で実行されるため、
最高の権限レベルが必要です。
多くの場合にすべての UI 権限が必要なわけではない SendInput() API をジャーナル フックの代わりに使用することができます。
WH_JOURNALPLAYBACK および WH_JOURNALRECORD ジャーナル フックは、
本質的にはプロセス間のメッセージングであるため、
より高い権限が必要です。ジャーナル フックの別の方法として、
SendInput() 関数がありますが、これは多くの場合フル UI 権限を必要としません。
次に、対策されたバージョンの呼び出しがどのようになっているか確認すると
以下の用になっています。
1.送信するキーをキーボードに対応した仮想キーコードへと変換
2.仮想キーの状態を取得
3.フックプロシージャを使って、キーボードをフックします。
までは、同じです。
3.でエラーが発生した場合は、更に以下の用に対策されています。
4.キーボードの入力をブロックします。
BlockInput ->int=0h (BOOL fBlockIt=1h)
5.仮想キーの状態を取得
GetKeyboardState ->int=1h (PBYTE lpKeyState=*18D904h->'\00h')
6.SendInputを利用しキーボード入力を行います。
SendInput (UINT cInputs=1h, *pInputs=*18DA04h->struct {DWORD type=1h}, int cbSize=1Ch)
というわけで、対策されたものは、SetWindowsHookExでエラーが発生した後に
SendInputを利用して、処理を続行してくれていることがわかりました。
調査した際に利用した、ログは以下になります。
VB6で作成したプログラムを実行した結果、問題なく動作しているログとなります。
調査した際に利用した、ログは以下になります。
VB6で作成したプログラムを実行した結果、問題なく動作しているログとなります。
MSVBVM60.DLL:->USER32.dll VkKeyScanW (WCHAR ch=L'a') MSVBVM60.DLL:<-USER32.dll VkKeyScanW ->short=41h (WCHAR ch=L'a') MSVBVM60.DLL:->kernel32.dll GlobalAlloc (UINT uFlags=0h, SIZE_T dwBytes=20h) MSVBVM60.DLL:<-kernel32.dll GlobalAlloc ->*=*885A10h (UINT uFlags=0h, SIZE_T dwBytes=20h) MSVBVM60.DLL:->kernel32.dll GlobalLock (HGLOBAL hMem=885a10h) MSVBVM60.DLL:<-kernel32.dll GlobalLock ->*=*885A10h (HGLOBAL hMem=885a10h) MSVBVM60.DLL:->USER32.dll GetKeyboardState (PBYTE lpKeyState=*18DA8Ch->'@') MSVBVM60.DLL:<-USER32.dll GetKeyboardState ->int=1h (PBYTE lpKeyState=*18DA8Ch->'\00h') kernel32.dll:->ntdll.dll RtlSetLastWin32Error (DWORD =0h) kernel32.dll:<-ntdll.dll RtlSetLastWin32Error ->void=void (DWORD =0h) kernel32.dll:->ntdll.dll RtlFreeHeap (HANDLE =850000h, ULONG =0h, PVOID =*885F28h) kernel32.dll:<-ntdll.dll RtlFreeHeap ->char='\01h' (HANDLE =850000h, ULONG =0h, PVOID =*885F28h) kernel32.dll:->KERNELBASE.d GetFullPathNameA (== no prototype available ==) kernel32.dll:<-KERNELBASE.d GetFullPathNameA (== no prototype available ==) kernel32.dll:->ntdll.dll RtlSetLastWin32Error (DWORD =2h) kernel32.dll:<-ntdll.dll RtlSetLastWin32Error ->void=void (DWORD =2h) MSVBVM60.DLL:->kernel32.dll GetModuleHandleA+ (LPCSTR lpModuleName=*0h) MSVBVM60.DLL:<-kernel32.dll GetModuleHandleA+ ->*=*400000h->struct {int unused=905A4Dh} (LPCSTR lpModuleName=*0h ) MSVBVM60.DLL:->USER32.dll SetWindowsHookExW+ (int idHook=1h, HOOKPROC lpfn=*72A0DF35h, HINSTANCE hmod=400000h, struct {DWORD dwThreadId=0h) MSVBVM60.DLL:<-USER32.dll *** SetWindowsHookExW+ ->*=*0h (int idHook=1h, HOOKPROC lpfn=*72A0DF35h, HINSTANCE hmod=400000h, struct {DWORD dwThreadId=0h) *** GetLastError() returns 5=0x5 [2]: MSVBVM60.DLL:->kernel32.dll LoadLibraryA+ (LPCSTR lpLibFileName=*7296EB24h->"user32.dll") kernel32.dll:->ntdll.dll _strcmpi (*=*2030Ch->'u', *=*767F49E0h->'t') kernel32.dll:<-ntdll.dll _strcmpi ->int=18D6ACh (*=*2030Ch->'u', *=*767F49E0h->'t') MSVBVM60.DLL:<-kernel32.dll LoadLibraryA+ ->*=*750E0000h->struct {int unused=905A4Dh} (LPCSTR lpLibFileName=*7296EB24h->"user32.dll") MSVBVM60.DLL:->kernel32.dll GetProcAddress+ (HMODULE hModule=750e0000h, struct {LPCSTR lpProcName=*7296EB18h->"SendInput") kernel32.dll:->ntdll.dll RtlFreeHeap (HANDLE =2790000h, ULONG =0h, PVOID =*27E0DC8h) kernel32.dll:<-ntdll.dll RtlFreeHeap ->char='\01h' (HANDLE =2790000h, ULONG =0h, PVOID =*27E0DC8h) kernel32.dll:->ntdll.dll RtlFreeHeap (HANDLE =2790000h, ULONG =0h, PVOID =*2801758h) kernel32.dll:<-ntdll.dll RtlFreeHeap ->char='\01h' (HANDLE =2790000h, ULONG =0h, PVOID =*2801758h) MSVBVM60.DLL:<-kernel32.dll GetProcAddress+ ->*=*7EEF1DF4h (HMODULE hModule=750e0000h, struct {LPCSTR lpProcName=*7296EB18h->"SendInput") MSVBVM60.DLL:->kernel32.dll GetProcAddress+ (HMODULE hModule=750e0000h, struct {LPCSTR lpProcName=*7296EB0Ch->"BlockInput") kernel32.dll:->ntdll.dll RtlFreeHeap (HANDLE =2790000h, ULONG =0h, PVOID =*27E0DC8h) kernel32.dll:<-ntdll.dll RtlFreeHeap ->char='\01h' (HANDLE =2790000h, ULONG =0h, PVOID =*27E0DC8h) kernel32.dll:->ntdll.dll RtlFreeHeap (HANDLE =2790000h, ULONG =0h, PVOID =*2801758h) kernel32.dll:<-ntdll.dll RtlFreeHeap ->char='\01h' (HANDLE =2790000h, ULONG =0h, PVOID =*2801758h) MSVBVM60.DLL:<-kernel32.dll GetProcAddress+ ->*=*7EED71FCh (HMODULE hModule=750e0000h, struct {LPCSTR lpProcName=*7296EB0Ch->"BlockInput") MSVBVM60.DLL:->USER32.dll BlockInput (BOOL fBlockIt=1h) MSVBVM60.DLL:<-USER32.dll BlockInput ->int=0h (BOOL fBlockIt=1h) MSVBVM60.DLL:->USER32.dll GetKeyboardState (PBYTE lpKeyState=*18D904h->'0') MSVBVM60.DLL:<-USER32.dll GetKeyboardState ->int=1h (PBYTE lpKeyState=*18D904h->'\00h') MSVBVM60.DLL:->USER32.dll SendInput (UINT cInputs=1h, *pInputs=*18DA04h->struct {DWORD type=1h}, int cbSize=1Ch) MSVBVM60.DLL:<-USER32.dll SendInput ->unsigned int=1h (UINT cInputs=1h, *pInputs=*18DA04h->struct {DWORD type=1h}, int cbSize=1Ch) MSVBVM60.DLL:->USER32.dll SendInput (UINT cInputs=1h, *pInputs=*18DA04h->struct {DWORD type=1h}, int cbSize=1Ch) MSVBVM60.DLL:<-USER32.dll SendInput ->unsigned int=1h (UINT cInputs=1h, *pInputs=*18DA04h->struct {DWORD type=1h}, int cbSize=1Ch) MSVBVM60.DLL:->USER32.dll GetKeyboardState (PBYTE lpKeyState=*18D804h->'5') MSVBVM60.DLL:<-USER32.dll GetKeyboardState ->int=1h (PBYTE lpKeyState=*18D804h->'\00h')