【问题标题】:KbdLayerDescriptor pVkToWcharTable returns NULL on Win64KbdLayerDescriptor pVkToWcharTable 在 Win64 上返回 NULL
【发布时间】:2012-07-29 15:47:01
【问题描述】:

我的想法已经不多了。我有一段改编自 http://thetechnofreak.com/technofreak/keylogger-visual-c/ 的代码,用于将键码转换为 unicode 字符。它在所有情况下都可以正常工作,除非您尝试从 64 位 Windows 运行 32 位版本。由于某种原因,pKbd->pVkToWcharTable 一直返回 NULL。我已经尝试过 __ptr64 以及为 kbd dll 路径明确指定 SysWOW64 和 System32。我在互联网上发现了几个项目,指的是这个确切或非常相似的问题,但我似乎无法获得任何解决方案(请参阅:KbdLayerDescriptor returns NULL at 64bit architecture)以下是我在 Windows 上使用 mingw-32 编译的测试代码XP (gcc -std=c99 Wow64Test.c) 然后在 Windows 7 64 位上执行。在 Windows XP 上,我得到了一个有效的指针,但在 Windows 7 上,我得到了 NULL。

***更新:所以看起来我遇到的问题是由于 mingw 没有正确实现 __ptr64 因为 sizeof 操作给出了 4 个字节而不是 Visual Studio 给出的 8 个字节。所以真正的解决方案是想办法使 KBD_LONG_POINTER 的大小动态或至少为 64 位,但我不确定这是否可能。有任何想法吗?

#include <windows.h>
#include <stdio.h>

#define KBD_LONG_POINTER __ptr64
//#define KBD_LONG_POINTER

typedef struct {
    BYTE Vk;
    BYTE ModBits;
} VK_TO_BIT, *KBD_LONG_POINTER PVK_TO_BIT;

typedef struct {
    PVK_TO_BIT pVkToBit;
    WORD wMaxModBits;
    BYTE ModNumber[];
} MODIFIERS, *KBD_LONG_POINTER PMODIFIERS;

typedef struct _VK_TO_WCHARS1 {
    BYTE VirtualKey;
    BYTE Attributes;
    WCHAR wch[1];
} VK_TO_WCHARS1, *KBD_LONG_POINTER PVK_TO_WCHARS1;

typedef struct _VK_TO_WCHAR_TABLE {
    PVK_TO_WCHARS1 pVkToWchars;
    BYTE nModifications;
    BYTE cbSize;
} VK_TO_WCHAR_TABLE, *KBD_LONG_POINTER PVK_TO_WCHAR_TABLE;

typedef struct {
    DWORD dwBoth;
    WCHAR wchComposed;
    USHORT uFlags;
} DEADKEY, *KBD_LONG_POINTER PDEADKEY;

typedef struct {
    BYTE vsc;
    WCHAR *KBD_LONG_POINTER pwsz;
} VSC_LPWSTR, *KBD_LONG_POINTER PVSC_LPWSTR;

typedef struct _VSC_VK {
    BYTE Vsc;
    USHORT Vk;
} VSC_VK, *KBD_LONG_POINTER PVSC_VK;

typedef struct _LIGATURE1 {
    BYTE VirtualKey;
    WORD ModificationNumber;
    WCHAR wch[1];
} LIGATURE1, *KBD_LONG_POINTER PLIGATURE1;

typedef struct tagKbdLayer {
    PMODIFIERS pCharModifiers;
    PVK_TO_WCHAR_TABLE pVkToWcharTable;
    PDEADKEY pDeadKey;
    PVSC_LPWSTR pKeyNames;
    PVSC_LPWSTR pKeyNamesExt;
    WCHAR *KBD_LONG_POINTER *KBD_LONG_POINTER pKeyNamesDead;
    USHORT *KBD_LONG_POINTER pusVSCtoVK;
    BYTE bMaxVSCtoVK;
    PVSC_VK pVSCtoVK_E0;
    PVSC_VK pVSCtoVK_E1;
    DWORD fLocaleFlags;
    BYTE nLgMax;
    BYTE cbLgEntry;
    PLIGATURE1 pLigature;
    DWORD dwType;
    DWORD dwSubType;
} KBDTABLES, *KBD_LONG_POINTER PKBDTABLES;


typedef PKBDTABLES(CALLBACK *KbdLayerDescriptor) (VOID);

int main() {
    PKBDTABLES pKbd;
    HINSTANCE kbdLibrary = NULL;

    //"C:\\WINDOWS\\SysWOW64\\KBDUS.DLL"
    //"C:\\WINDOWS\\System32\\KBDUS.DLL"
    kbdLibrary = LoadLibrary("C:\\WINDOWS\\SysWOW64\\KBDUS.DLL");

    KbdLayerDescriptor pKbdLayerDescriptor = (KbdLayerDescriptor) GetProcAddress(kbdLibrary, "KbdLayerDescriptor");

    if(pKbdLayerDescriptor != NULL) {
        pKbd = pKbdLayerDescriptor();

        printf("Is Null? %d 0x%X\n", sizeof(pKbd->pVkToWcharTable), pKbd->pVkToWcharTable);
    }

    FreeLibrary(kbdLibrary);
    kbdLibrary = NULL;
}

【问题讨论】:

    标签: winapi keyboard mingw wow64 syswow64


    【解决方案1】:

    对您来说可能已经晚了,但这里有一个解决方案,适用于遇到相同问题的任何人。此演示和不完整的解释有帮助,但仅适用于 Visual Studio: http://www.codeproject.com/Articles/439275/Loading-keyboard-layout-KbdLayerDescriptor-in-32-6

    kbd.h中结构体中的指针都有KBD_LONG_POINTER宏,在64位操作系统上定义为*__ptr64*。在 Visual Studio 中,这使得指针占用 8 个字节,而不是通常的 4 个 32 位程序。不幸的是,在 MinGW 中,*__ptr64* 被定义为不做任何事情。

    如链接解释中所述,KbdLayerDescriptor 函数在 32 位和 64 位 Windows 上返回的指针不同。指针的大小似乎取决于操作系统而不是正在运行的程序。实际上,对于 32 位程序来说,在 64 位操作系统上指针仍然是 4 字节,但是在 VS 中,__ptr64 关键字却不是。

    例如,kbd.h 中的某些结构如下所示:

    typedef struct {
        BYTE Vk;
        BYTE ModBits;
    } VK_TO_BIT, *KBD_LONG_POINTER PVK_TO_BIT;
    
    typedef struct {
        PVK_TO_BIT pVkToBit;
        WORD wMaxModBits;
        BYTE ModNumber[];
    } MODIFIERS, *KBD_LONG_POINTER PMODIFIERS;
    

    对于 64 位 Windows 上的 32 位程序,这在 MinGW 和 VS 中都不起作用。因为MODIFIERS 中的pVkToBit 成员在没有__ptr64 的情况下只有4 个字节。解决方案是忘记KBD_LONG_POINTER(您甚至可以将它们全部删除)并定义类似于上述的结构。即:

    struct VK_TO_BIT64
    {
        BYTE Vk;
        BYTE ModBits;
    };
    
    struct MODIFIERS64
    {
        VK_TO_BIT64 *pVkToBit;
        int _align1;
        WORD wMaxModBits;
        BYTE ModNumber[];
    };
    

    (您可以使用VK_TO_BIT,但不要定义自己的VK_TO_BIT64,因为它们是相同的,但有不同的定义有助于理解发生了什么。)

    成员 pVkToBit 仍然占用 4 个字节,但 KbdLayerDescriptor 在 64 位操作系统上将指针填充到 8 个字节,因此我们必须插入一些填充 (int _align1)。

    您必须对kbd.h 中的其他结构执行相同的操作。例如这将替换KBDTABLES:

    struct WCHARARRAY64
    {
        WCHAR *str;
        int _align1;
    };
    
    struct KBDTABLES64
    {
        MODIFIERS64 *pCharModifiers;
        int _align1;
        VK_TO_WCHAR_TABLE64 *pVkToWcharTable;
        int _align2;
        DEADKEY64 *pDeadKey;
        int _align3;
        VSC_LPWSTR64 *pKeyNames;
        int _align4;
        VSC_LPWSTR64 *pKeyNamesExt;
        int _align5;
        WCHARARRAY64 *pKeyNamesDead;
        int _align6;
        USHORT *pusVSCtoVK;
        int _align7;
        BYTE bMaxVSCtoVK;
        int _align8;
        VSC_VK64 *pVSCtoVK_E0;
        int _align9;
        VSC_VK64 *pVSCtoVK_E1;
        int _align10;
        DWORD fLocaleFlags;
        byte nLgMax;
        byte cbLgEntry;
        LIGATURE64_1 *pLigature;
        int _align11;
        DWORD dwType;
        DWORD dwSubType;
    };
    

    (注意_align8 成员不在指针之后。)

    要使用这一切,你必须检查你是否在 64 位窗口上运行:http://msdn.microsoft.com/en-us/library/ms684139%28v=vs.85%29.aspx

    如果不是,请使用来自kbd.h 的原始结构,因为指针行为正确。它们占用 4 个字节。如果程序在 64 位操作系统上运行,请使用您创建的结构。您可以通过以下方式实现:

    typedef __int64 (CALLBACK *LayerDescriptor64)(); // Result should be cast to KBDTABLES64.
    typedef PKBDTABLES (CALLBACK *LayerDescriptor)(); // This is used on 32 bit OS.
    
    static PKBDTABLES kbdtables = NULL;
    static KBDTABLES64 *kbdtables64 = NULL;
    

    并且在一些初始化函数中:

    if (WindowsIs64Bit()) // Your function that checks the OS version.
    {
        LayerDescriptor64 KbdLayerDescriptor = (LayerDescriptor64)GetProcAddress(kbdLibrary, "KbdLayerDescriptor");
        if (KbdLayerDescriptor != NULL)
            kbdtables64 = (KBDTABLES64*)KbdLayerDescriptor();
        else
            kbdtables64 = NULL;
    }
    else
    {
        LayerDescriptor KbdLayerDescriptor = (LayerDescriptor)GetProcAddress(kbdLibrary, "KbdLayerDescriptor");
        if (KbdLayerDescriptor != NULL)
            kbdtables = KbdLayerDescriptor();
        else
            kbdtables = NULL;
    }
    

    此解决方案根本不使用 __ptr64,并且在 VS 和 MinGW 中都可以使用。您需要注意的是:

    1. 结构应在 8 字节边界上对齐。 (这是当前 VS 或 MinGW 中的默认设置,至少对于 C++ 而言。)
    2. 不要将KBD_LONG_POINTER 定义为__ptr64,或者从任何地方删除它。虽然你最好不要改变 kbd.h。
    3. 了解结构成员对齐的工作原理。 (我已将其编译为 C++ 而不是 C。我不确定 C 的对齐规则是否会有所不同。)
    4. 根据操作系统使用正确的变量(kbdtableskbdtables64)。
    5. 编译 64 位程序时显然不需要此解决方案。

    【讨论】:

    • 首先,感谢您花时间解释所有这些!我最终采取了类似但更动态,随后更复杂的路线来实现相同的结果。我基本上已经根据简单的 Wow64 检查计算了运行时的指针填充。源代码在公共域下完全可用:github.com/kwhat/libuiohook/blob/master/src/windows/…
    猜你喜欢
    • 2012-05-11
    • 1970-01-01
    • 2011-02-06
    • 2016-01-26
    • 2021-11-21
    • 2015-06-11
    • 2015-08-11
    • 2013-08-16
    • 1970-01-01
    相关资源
    最近更新 更多