与PostMessage的沿keybd_event WIN32不能正常工作时,Visual Studio中具有焦点(或任何应用程序以管理员身份运行) [英] keybd_event along with PostMessage win32 not working when Visual Studio has focus (or any application run as admin)

查看:387
本文介绍了与PostMessage的沿keybd_event WIN32不能正常工作时,Visual Studio中具有焦点(或任何应用程序以管理员身份运行)的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

这是我跟很多的变化用从老XP一天的$ B $一个节目B这是一个CMD行程序,这将改变轨道媒体应用(Spotify的,VLC,媒体播放器),正如与下一首/上一首曲目键盘按钮。



目前即时通讯使用的是Microsoft不具有这些按钮,但有执行此PROG可编程按键自然键盘。



这所有的作品除了当的Visual Studio 2012/2013具有焦点(Windows 7)中(没有试过其它版本),它在SQL Management Studio中的作品。

 使用系统;使用System.Runtime.InteropServices 
;

命名空间NxtTrack
{
类节目
{
函数[DllImport(user32.dll中)]
私人静态的extern BOOL PostMessage的(的IntPtr的HWND,UINT消息,UIntPtr的wParam,lParam的的IntPtr);
函数[DllImport(user32.dll中)]
公共静态外部无效keybd_event(字节vkCode,字节扫描码,诠释旗帜,IntPtr的extraInfo);

枚举TrackMove
{
上一页,下一页
}

静态无效的主要(字串[] args)
{

trackMove trackMove;


{
如果(参数[0] .ToLower()包含(上一页)。)
trackMove = TrackMove.Previous;
,否则如果(ARGS [0] .ToLower()包含(下一步))
trackMove = TrackMove.Next;
,否则
{
抛出新的异常(错误参数);
}
}

{
Console.WriteLine(PARAMS需要:一个或下一个);
的回报;
}
TrackKeys(trackMove);
}

私有静态无效TrackKeys(TrackMove trackMove)
{
//http://msdn.microsoft.com/en-us/library/dd375731% 28V = VS.85%29.aspx

字节味精= trackMove == TrackMove.Previous? (字节)0xB1:(字节)反0xB0;
keybd_event(味精,×45,0,IntPtr.Zero);
}
}
}


解决方案

这是VK_MEDIA_NEXT_TRACK和VK_MEDIA_PREV_TRACK虚拟按键。对他们的处理是非常令人费解的:




  • 不管软件拥有前台窗口将检索他的击键从消息队列中时,它调用的GetMessage()

  • 在程序的消息循环的TranslateMessage()调用转换击键到的 WM_APPCOMMAND消息时,APPCOMMAND_MEDIA_NEXTTRACK或APPCOMMAND_MEDIA_PREVIOUSTRACK并将其发送到子窗口具有焦点

  • 子窗口将不会使用它,将消息传递到DefWindowProc函数()

  • 这将消息传递到子窗口的父。这尽可能多的子窗口嵌套的重复,最终到达顶层窗口

  • 当调用DefWindowProc函数时,Windows然后调用一个壳钩来触发任何程序的回调已调用调用SetWindowsHookEx()为WH_SHELL钩,HSHELL_APPCOMMAND通知。 Windows Media Player等程序将使用

  • 如果没有钩子拦截它,它最终将在资源管理器中结束了作为最后的钩子,然后最后执行的操作。



这里的解决方案是不发送击键但向上移动以前列出的处理链。如果有可能直接调用壳钩,但该功能不会暴露这将是很好。下一步是回送WM_APPCOMMAND讯息。



这需要选择一个窗口发送消息到。这是一个困难的这些天,由于UAC,一个叫UIPI(用户界面特权隔离)功能可防止未提升的程序发送消息高架之一。所以,你不能只使用窗口在前台,它可能是一个与管理员权限运行的应用程序。像Visual Studio,可能的原因PostMessage的和keybd_event(),当你尝试过失败。



关键是将其发送到的窗口,你的自己。你犯了难,你使用的是控制台模式的项目。这可以到处工作。项目+添加引用,选择System.Windows.Forms的。添加一个新类到您的项目并粘贴如下所示的代码。更换你keybd_event()有,比方说,

  AppCommand.Send(AppCommands.MediaNext)调用; 



我扔在全可以发送的所有命令的工具包和 - 堆。我提供了一个清理()方法来释放资源,这是没有必要调用它。该代码是通过4.5.1

 使用系统的.NET版本2.0相兼容; 
使用的System.Threading;使用System.Windows.Forms的
;使用System.Runtime.InteropServices
;

公共枚举AppCommands {
BrowserBack = 1,
BrowserForward浏览= 2,
BrowserRefresh浏览= 3,
BrowserStop浏览= 4,
BrowserSearch浏览= 5,
BrowserFavorite = 6,
BrowserHome浏览= 7,
VolumeMute = 8,
VolumeDown = 9,
VolumeUp = 10,
MediaNext = 11,
MediaPrevious = 12,
MediaStop = 13,
MediaPlayPause = 14,
LaunchMail = 15,
LaunchMediaSelect = 16,
LaunchApp1 = 17,
LaunchApp2 = 18,
BassDown = 19,
BassBoost = 20,
BassUp = 21,
TrebleUp = 22,
TrebleDown = 23,
MicrophoneMute = 24,
MicrophoneVolumeUp = 25,
MicrophoneVolumeDown = 26,
帮助= 27,
查找= 28,
新建= 29,
打开= 30,
关闭= 31,
保存= 32,
打印= 33,
撤消= 34,
重做= 35,
复制= 36,
剪切= 37,
粘贴= 38,
ReplyToMail = 39,
ForwardMail = 40,
的SendMail = 41,
拼写检查= 42,
听写= 43,
MicrophoneOnOff = 44,
CorrectionList = 45,
无限灵= 46,
MediaPause = 47,
MediaRecord = 48,
MediaFastForward = 49,
MediaRewind = 50,
MediaChannelUp = 51,
MediaChannelDown = 52,
删除= 53,
的Flip3D = 54
}

公共静态类AppCommand {
公共静态无效发送(CMD AppCommands){
如果(FRM == NULL)初始化();
frm.Invoke(新MethodInvoker(()=> SendMessage消息(frm.Handle,WM_APPCOMMAND,frm.Handle,(IntPtr的)((int)的CMD&所述;&下; 16))));
}

私有静态无效初始化(){
//运行在另一个线程的消息循环,所以我们是一个控制台模式的应用程序
变种T支持=新的Thread(()=> {
FRM =新表();
VAR哑= frm.Handle;
frm.BeginInvoke(新MethodInvoker(()=> mre.Set ()));
Application.Run();
});
t.SetApartmentState(ApartmentState.STA);
t.IsBackground = TRUE;
t.Start();
mre.WaitOne(); (!FRM = NULL)
}
公共静态无效的清理(){
如果{
frm.BeginInvoke(新MethodInvoker(()=> {
FRM .Close();
Application.ExitThread();
mre.Set();
}));
mre.WaitOne();
FRM = NULL;
}
}

私有静态ManualResetEvent的MRE =新的ManualResetEvent(假);
私有静态表格FRM;

// PInvoke的
私人const int的WM_APPCOMMAND = 0x319;
函数[DllImport(user32.dll中)]
私人静态外部的IntPtr SendMessage函数(IntPtr的的HWND,INT味精,IntPtr的WP,IntPtr的LP);
}


This is a program that I have used with many changes from the old xp day's It's a cmd line program that will change track in media applications(Spotify,vlc,mediaPlayer) just like keyboards with next/previous track buttons.

Currently im using Microsoft natural keyboard that does not have those buttons but have programmable keys that execute this prog.

This all works EXCEPT when Visual Studio 2012/2013 has the focus (Windows 7) (haven't tried other versions), and it works in Sql management studio.

using System;
using System.Runtime.InteropServices;

namespace NxtTrack
{    
class Program
{
    [DllImport("user32.dll")]
    private static extern bool PostMessage(IntPtr hWnd, uint Msg, UIntPtr wParam, IntPtr lParam);
    [DllImport("user32.dll")]
    public static extern void keybd_event(byte vkCode, byte scanCode, int flags, IntPtr extraInfo);

    enum TrackMove
    {
        Previous,Next
    }

    static void Main(string[] args)
    {

        TrackMove trackMove;

        try
        {
            if(args[0].ToLower().Contains("previous"))
                trackMove = TrackMove.Previous;
            else if(args[0].ToLower().Contains("next"))
                trackMove = TrackMove.Next;
            else
            {
                throw new Exception("wrong param");
            }
        }
        catch
        {
            Console.WriteLine("Params needed: Next or Previous");
            return;
        }
        TrackKeys(trackMove);
    }

    private static void TrackKeys(TrackMove trackMove)
    {
        //http://msdn.microsoft.com/en-us/library/dd375731%28v=VS.85%29.aspx

        byte msg = trackMove == TrackMove.Previous ? (byte)0xB1 : (byte)0xB0;
        keybd_event(msg, 0x45, 0, IntPtr.Zero);
    }
}
}

解决方案

These are the VK_MEDIA_NEXT_TRACK and VK_MEDIA_PREV_TRACK virtual keys. The processing for them is highly convoluted:

  • Whatever program owns the foreground window will retrieve his keystroke from its message queue when it calls GetMessage()
  • The TranslateMessage() call in that program's message loop translates the keystroke to a WM_APPCOMMAND message, APPCOMMAND_MEDIA_NEXTTRACK or APPCOMMAND_MEDIA_PREVIOUSTRACK and sends it to the child window with the focus
  • The child window won't use it and passes the message to DefWindowProc()
  • Which passes the message to the parent of the child window. This repeats as often as the child windows are nested, eventually reaching the top-level window
  • When it calls DefWindowProc, Windows then calls a shell hook to trigger a callback in any program that has called SetWindowsHookEx() for the WH_SHELL hook, HSHELL_APPCOMMAND notification. A program like Windows Media Player will use that
  • If no hooks intercept it, it will eventually end up in Explorer as the last hook which then finally performs the operation.

The solution here is to not send a keystroke but move up the previously listed processing chain. It would be nice if it were possible to directly call the shell hook but that feature is not exposed. Next step back is to send the WM_APPCOMMAND message instead.

That requires picking a window to send the message to. That's a difficult these days due to UAC, a feature called UIPI (User Interface Privilege Isolation) prevents a non-elevated program from sending messages to an elevated one. So you can't just use the window in the foreground, it may well be an app that's running with admin privileges. Like Visual Studio, the likely reason why PostMessage and keybd_event() is failing when you tried them.

The trick is to send it to a window that you own. Which you made difficult, you are using a console mode project. That can be worked around. Project + Add Reference, select System.Windows.Forms. Add a new class to your project and paste the code shown below. Replace your keybd_event() call with, say,

  AppCommand.Send(AppCommands.MediaNext);  

I threw in the whole kit-and-caboodle of all the commands you can send. I provided a Cleanup() method to release resources, it is not necessary to call it. The code is compatible with .NET version 2.0 through 4.5.1

using System;
using System.Threading;
using System.Windows.Forms;
using System.Runtime.InteropServices;

public enum AppCommands {
    BrowserBack = 1,
    BrowserForward = 2,
    BrowserRefresh = 3,
    BrowserStop = 4,
    BrowserSearch = 5,
    BrowserFavorite = 6,
    BrowserHome = 7,
    VolumeMute = 8,
    VolumeDown = 9,
    VolumeUp = 10,
    MediaNext = 11,
    MediaPrevious = 12,
    MediaStop = 13,
    MediaPlayPause = 14,
    LaunchMail = 15,
    LaunchMediaSelect = 16,
    LaunchApp1 = 17,
    LaunchApp2 = 18,
    BassDown = 19,
    BassBoost = 20,
    BassUp = 21,
    TrebleUp = 22,
    TrebleDown = 23,
    MicrophoneMute = 24,
    MicrophoneVolumeUp = 25,
    MicrophoneVolumeDown = 26,
    Help = 27,
    Find = 28,
    New = 29,
    Open = 30,
    Close = 31,
    Save = 32,
    Print = 33,
    Undo = 34,
    Redo = 35,
    Copy = 36,
    Cut = 37,
    Paste = 38,
    ReplyToMail = 39,
    ForwardMail = 40,
    SendMail = 41,
    SpellCheck = 42,
    Dictate = 43,
    MicrophoneOnOff = 44,
    CorrectionList = 45,
    MediaPlay = 46,
    MediaPause = 47,
    MediaRecord = 48,
    MediaFastForward = 49,
    MediaRewind = 50,
    MediaChannelUp = 51,
    MediaChannelDown = 52,
    Delete = 53,
    Flip3D = 54
}

public static class AppCommand {
    public static void Send(AppCommands cmd) {
        if (frm == null) Initialize();
        frm.Invoke(new MethodInvoker(() => SendMessage(frm.Handle, WM_APPCOMMAND, frm.Handle, (IntPtr)((int)cmd << 16))));
    }

    private static void Initialize() {
        // Run the message loop on another thread so we're compatible with a console mode app
        var t = new Thread(() => {
            frm = new Form();
            var dummy = frm.Handle; 
            frm.BeginInvoke(new MethodInvoker(() => mre.Set()));
            Application.Run();
        });
        t.SetApartmentState(ApartmentState.STA);
        t.IsBackground = true;
        t.Start();
        mre.WaitOne();
    }
    public static void Cleanup() { 
        if (frm != null) {
            frm.BeginInvoke(new MethodInvoker(() => { 
                frm.Close();
                Application.ExitThread();
                mre.Set(); 
            }));
            mre.WaitOne();
            frm = null;
        }
    }

    private static ManualResetEvent mre = new ManualResetEvent(false);
    private static Form frm;

    // Pinvoke
    private const int WM_APPCOMMAND = 0x319;
    [DllImport("user32.dll")]
    private static extern IntPtr SendMessage(IntPtr hWnd, int msg, IntPtr wp, IntPtr lp);
}

这篇关于与PostMessage的沿keybd_event WIN32不能正常工作时,Visual Studio中具有焦点(或任何应用程序以管理员身份运行)的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆