KbdLayerDescriptor pVkToWcharTable在Win64上返回NULL

时间:2012-07-31 19:25:38

标签: winapi keyboard mingw wow64 syswow64

我的想法已经不多了。我有一段代码改编自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)以下是我用mingw-32编译的测试代码在Windows 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;
}

1 个答案:

答案 0 :(得分:3)

对你来说可能会迟到,但对于遇到同样问题的人来说,这是一个解决方案。此演示和不完整的解释有所帮助,但仅适用于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中都不起作用。因为pVkToBit中的 MODIFIERS 成员只有4个字节而没有 __ptr64 。解决方案是忘记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个字节,因此我们必须插入一些填充({{ 1}})。

你必须对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; }; 成员不在指针之后。)

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

如果没有,请使用_align8中的原始结构,因为指针的行为正确。它们占用4个字节。如果程序在64位操作系统上运行,请使用您创建的结构。你可以用这个来实现它:

kbd.h

在一些初始化函数中:

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; } ,并且在VS和MinGW中都有效。你需要注意的事项是:

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