Javascript-为什么EnumPrintersA和EnumPrintersW请求相同的内存量?

我使用node-ffi调用EnumPrintersA / EnumPrintersW函数以获取可从我的PC访问的本地打印机列表.
您应该创建缓冲区,该缓冲区将由EnumPrinters函数填充信息.
但是您不知道所需的缓冲区大小.
在这种情况下,您需要执行两次EnumPrintersA / EnumPrintersW.
在第一次调用期间,此函数计算有关打印机信息的内存量,在第二次调用期间,此函数将有关打印机的信息填充到缓冲区.
如果是EnumPrinters函数的Unicode版本,则打印机名称中的每个字母将在Windows中使用两个字符进行编码.

为什么第一次调用EnumPrintersW返回的内存量与第一次调用EnumPrintersA相同?
Unicode字符串的长度是非unicode字符串的两倍,但所需的缓冲区大小相同.

var ffi = require('ffi')
var ref = require('ref')
var Struct = require('ref-struct')
var wchar_t = require('ref-wchar')

var int = ref.types.int
var intPtr = ref.refType(ref.types.int)
var wchar_string = wchar_t.string

var getPrintersA =  function getPrinters() {
   var PRINTER_INFO_4A = Struct({
      'pPrinterName' : ref.types.CString,
      'pServerName' : ref.types.CString,
      'Attributes' : int
   });

   var printerInfoPtr = ref.refType(PRINTER_INFO_4A);

   var winspoolLib = new ffi.Library('winspool', {
      'EnumPrintersA': [ int, [ int, ref.types.CString, int, printerInfoPtr, int, intPtr, intPtr ] ]
   });

   var pcbNeeded = ref.alloc(int, 0);
   var pcReturned = ref.alloc(int, 0);

   //Get amount of memory for the buffer with information about printers
   var res = winspoolLib.EnumPrintersA(6, ref.NULL, 4, ref.NULL, 0, pcbNeeded, pcReturned);
   if (res != 0) {
      console.log("Cannot get list of printers. Error during first call to EnumPrintersA. Error: " + res);
      return;
   }

   var bufSize = pcbNeeded.deref();
   var buf = Buffer.alloc(bufSize);

   console.log(bufSize);

   //Fill buf with information about printers
   res = winspoolLib.EnumPrintersA(6, ref.NULL, 4, buf, bufSize, pcbNeeded, pcReturned);
   if (res == 0) {
      console.log("Cannot get list of printers. Eror: " + res);
      return;
   }

   var countOfPrinters = pcReturned.deref();

   var printers = Array(countOfPrinters);
   for (var i = 0; i < countOfPrinters; i++) {
      var pPrinterInfo = ref.get(buf, i*PRINTER_INFO_4A.size, PRINTER_INFO_4A);
      printers[i] = pPrinterInfo.pPrinterName;
   }

   return printers;
};

var getPrintersW =  function getPrinters() {
   var PRINTER_INFO_4W = Struct({
      'pPrinterName' : wchar_string,
      'pServerName' : wchar_string,
      'Attributes' : int
   });

   var printerInfoPtr = ref.refType(PRINTER_INFO_4W);

   var winspoolLib = new ffi.Library('winspool', {
      'EnumPrintersW': [ int, [ int, wchar_string, int, printerInfoPtr, int, intPtr, intPtr ] ]
   });

   var pcbNeeded = ref.alloc(int, 0);
   var pcReturned = ref.alloc(int, 0);

   //Get amount of memory for the buffer with information about printers
   var res = winspoolLib.EnumPrintersW(6, ref.NULL, 4, ref.NULL, 0, pcbNeeded, pcReturned);
   if (res != 0) {
      console.log("Cannot get list of printers. Error during first call to EnumPrintersW. Eror code: " + res);
      return;
   }

   var bufSize = pcbNeeded.deref();
   var buf = Buffer.alloc(bufSize);

   console.log(bufSize);

   //Fill buf with information about printers
   res = winspoolLib.EnumPrintersW(6, ref.NULL, 4, buf, pcbNeeded.deref(), pcbNeeded, pcReturned);
   if (res == 0) {
      console.log("Cannot get list of printers. Eror code: " + res);
      return;
   }

   var countOfPrinters = pcReturned.deref();
   var printers = new Array(countOfPrinters);
   for (var i = 0; i < countOfPrinters; i++) {
      var pPrinterInfo = ref.get(buf, i*PRINTER_INFO_4W.size, PRINTER_INFO_4W);
      printers[i] = pPrinterInfo.pPrinterName;
   }

   return printers;
};

https://msdn.microsoft.com/ru-ru/library/windows/desktop/dd162692(v=vs.85).aspx

BOOL EnumPrinters(
  _In_  DWORD   Flags,
  _In_  LPTSTR  Name,
  _In_  DWORD   Level,
  _Out_ LPBYTE  pPrinterEnum,
  _In_  DWORD   cbBuf,
  _Out_ LPDWORD pcbNeeded,
  _Out_ LPDWORD pcReturned
);

https://msdn.microsoft.com/ru-ru/library/windows/desktop/dd162847(v=vs.85).aspx

typedef struct _PRINTER_INFO_4 {
  LPTSTR pPrinterName;
  LPTSTR pServerName;
  DWORD  Attributes;
} PRINTER_INFO_4, *PPRINTER_INFO_4;

解决方法:

一开始我以为您的代码有问题,所以我一直在寻找错误(由ffi或js层或错别字或类似内容引起),但我什么都找不到.

然后,我开始用C编写一个与您类似的程序(以消除可能引入错误的任何额外层).

main.c:

#include <stdio.h>
#include <Windows.h>
#include <conio.h>


typedef BOOL (__stdcall *EnumPrintersAFuncPtr)(_In_ DWORD Flags, _In_ LPSTR Name, _In_ DWORD Level, _Out_ LPBYTE pPrinterEnum, _In_ DWORD cbBuf, _Out_ LPDWORD pcbNeeded, _Out_ LPDWORD pcReturned);
typedef BOOL (__stdcall *EnumPrintersWFuncPtr)(_In_ DWORD Flags, _In_ LPWSTR Name, _In_ DWORD Level, _Out_ LPBYTE pPrinterEnum, _In_ DWORD cbBuf, _Out_ LPDWORD pcbNeeded, _Out_ LPDWORD pcReturned);


void testFunc() {
    PPRINTER_INFO_4A ppi4a = NULL;
    PPRINTER_INFO_4W ppi4w = NULL;
    BOOL resa, resw;
    DWORD neededa = 0, returneda = 0, neededw = 0, returnedw = 0, gle = 0, i = 0, flags = PRINTER_ENUM_LOCAL | PRINTER_ENUM_CONNECTIONS;
    LPBYTE bufa = NULL, bufw = NULL;
    resa = EnumPrintersA(flags, NULL, 4, NULL, 0, &neededa, &returneda);
    if (resa) {
        printf("EnumPrintersA(1) succeeded with NULL buffer. Exiting...\n");
        return;
    } else {
        gle = GetLastError();
        if (gle != ERROR_INSUFFICIENT_BUFFER) {
            printf("EnumPrintersA(1) failed with %d(0x%08X) which is different than %d. Exiting...\n", gle, gle, ERROR_INSUFFICIENT_BUFFER);
            return;
        } else {
            printf("EnumPrintersA(1) needs a %d(0x%08X) bytes long buffer.\n", neededa, neededa);
        }
    }
    resw = EnumPrintersW(flags, NULL, 4, NULL, 0, &neededw, &returnedw);
    if (resw) {
        printf("EnumPrintersW(1) succeeded with NULL buffer. Exiting...\n");
        return;
    } else {
        gle = GetLastError();
        if (gle != ERROR_INSUFFICIENT_BUFFER) {
            printf("EnumPrintersW(1) failed with %d(0x%08X) which is different than %d. Exiting...\n", gle, gle, ERROR_INSUFFICIENT_BUFFER);
            return;
        } else {
            printf("EnumPrintersW(1) needs a %d(0x%08X) bytes long buffer.\n", neededw, neededw);
        }
    }

    bufa = (LPBYTE)calloc(1, neededa);
    if (bufa == NULL) {
        printf("calloc failed with %d(0x%08X). Exiting...\n", errno, errno);
        return;
    } else {
        printf("buffera[0x%08X:0x%08X]\n", (long)bufa, (long)bufa + neededa - 1);
    }
    bufw = (LPBYTE)calloc(1, neededw);
    if (bufw == NULL) {
        printf("calloc failed with %d(0x%08X). Exiting...\n", errno, errno);
        free(bufa);
        return;
    } else {
        printf("bufferw[0x%08X:0x%08X]\n", (long)bufw, (long)bufw + neededw - 1);
    }

    resa = EnumPrintersA(flags, NULL, 4, bufa, neededa, &neededa, &returneda);
    if (!resa) {
        gle = GetLastError();
        printf("EnumPrintersA(2) failed with %d(0x%08X). Exiting...\n", gle, gle);
        free(bufa);
        free(bufw);
        return;
    }
    printf("EnumPrintersA(2) copied %d bytes in the buffer out of which the first %d(0x%08X) represent %d structures of size %d\n", neededa, returneda * sizeof(PRINTER_INFO_4A), returneda * sizeof(PRINTER_INFO_4A), returneda, sizeof(PRINTER_INFO_4A));
    resw = EnumPrintersW(flags, NULL, 4, bufw, neededw, &neededw, &returnedw);
    if (!resw) {
        gle = GetLastError();
        printf("EnumPrintersW(2) failed with %d(0x%08X). Exiting...\n", gle, gle);
        free(bufw);
        free(bufa);
        return;
    }
    printf("EnumPrintersW(2) copied %d bytes in the buffer out of which the first %d(0x%08X) represent %d structures of size %d\n", neededw, returnedw * sizeof(PRINTER_INFO_4W), returnedw * sizeof(PRINTER_INFO_4W), returnedw, sizeof(PRINTER_INFO_4W));

    ppi4a = (PPRINTER_INFO_4A)bufa;
    ppi4w = (PPRINTER_INFO_4W)bufw;
    printf("\nPrinting ASCII results:\n");
    for (i = 0; i < returneda; i++) {
        printf("  Item %d\n    pPrinterName: [%s]\n", i, ppi4a[i].pPrinterName ? ppi4a[i].pPrinterName : "NULL");
    }
    printf("\nPrinting WIDE results:\n");
    for (i = 0; i < returnedw; i++) {
        wprintf(L"  Item %d\n    pPrinterName: [%s]\n", i, ppi4w[i].pPrinterName ? ppi4w[i].pPrinterName : L"NULL");
    }

    free(bufa);
    free(bufw);
}


int main() {
    testFunc();
    printf("\nPress a key to exit...\n");
    getch();
    return 0;
}

注意:就变量名而言(我将其缩写-因此不太直观),其名称末尾的a或w表示它们用于ASCII / WIDE版本.

最初,我担心EnumPrinters可能不会返回任何内容,因为此时我尚未连接到任何打印机,但是幸运的是我有一些(更准确地说是7个)“保存”.这是上述程序的输出(感谢@qxz纠正我的初始(和有问题的)版本):

06001

令人惊讶的是(至少对我而言),您描述的行为可以重现.

请注意,以上输出来自程序的32位编译版本(更难读取64位指针:)),但在为64位构建时也可以重现该行为(我在Win10上使用VStudio 10.0).

由于缓冲区末尾肯定有字符串,因此我开始调试:

Javascript-为什么EnumPrintersA和EnumPrintersW请求相同的内存量?

上图是VStudio 10.0调试窗口的图片,在释放第一个指针之前,该程序在testFunc的结尾处被中断.现在,我不知道您对在VStudio上进行调试有多熟悉,所以我将逐步介绍(相关)窗口区域:

>在底部,有2个“监视”窗口(用于在程序运行时显示变量).如图所示,将显示变量名称,值和类型

>在右侧,(监视1):在两个缓冲区的每个缓冲区的开始处,第一个(第0个)和最后一个(第6个-共有7个)结构
>在左侧,(监视2):2个缓冲区的地址

>在“监视”窗口上方,(存储器2)是bufw的存储器内容.内存窗口包含一系列行,每行中都有内存地址(左侧为灰色),其内容以十六进制表示(每个字节对应于2个十六进制数字,例如1E),然后在右侧具有相同的内容以char表示(每个字节对应1个char-我将再次讨论),然后是下一行,依此类推
>高于存储器2,(存储器1):这是bufa的存储器内容

现在,回到内存布局:并非所有右边的字符都一定是它们看起来的样子,其中一些字符只是为了人类可读而显示.例如,右侧有很多点(.),但它们并非全部都是点.如果在相应的十六进制表示形式上查找点,您会注意到,对于许多字符来说,它都是00或NULL(这是不可打印的字符,但显示为点).

关于2个“内存”窗口(从char表示形式)中每个缓冲区的内容,共有3个区域:

> PRINTER_INFO_4 *区域或开头的乱码:544个字节,对应于大约第1 3行
>来自最后约1.5行的时髦字符:它们位于缓冲区之外,因此我们不在乎
>中间区域:存储字符串的位置

让我们看一下WIDE字符串区域(内存2-中间区域):正如您提到的,每个字符都有2个字节:因为在我的情况下,它们都是ASCII字符,因此MSB(或代码页字节)始终为0(这就是为什么)您会看到字符和点交错:例如,第4行中的“ .LaserJet”.

由于缓冲区中有多个字符串(或字符串,如果可以的话)-甚至更好:一个TCHAR *中的多个TCHAR *-必须将它们分开:这由NULL WIDE char(十六进制:00 00,char :“ ..”)在每个字符串的末尾;结合下一个字符串的第一个字节(char)也是00(.)的事实,您将看到3个NULL字节的序列(十六进制:00 00 00,字符:“ …”),它是分隔符在中间区域中的2个(宽)弦之间.

现在,比较2个中间部分(对应于2个缓冲区),您会注意到字符串分隔符的位置完全相同,并且更多:每个字符串的最后一部分也相同(每个字符串的最后一半更精确).

考虑到这一点,这是我的理论:

我认为EnumPrintersA调用EnumPrintersW,然后遍历每个字符串(在缓冲区的末尾),然后调用wcstombs或什至更好的字符串:[MS.Docs]: WideCharToMultiByte function(将它们转换就位-因此生成的ASCII字符串仅取1) WIDE字符串的一半,而第二部分则保持不变),而不转换所有缓冲区.我必须通过在winspool.drv中使用反汇编程序进行验证.

就我个人而言(如果我是对的),我认为这是一个la脚的解决方法(或者我喜欢称它为增益),但是谁知道,也许所有的* A,* W函数对(至少那些返回多个char的函数对) *字符中的* s)就是这样.无论如何,这种方法也有优点(至少对于这两个函数):

> dev-wise:一个函数可以调用另一个函数并将实现保留在一个位置(而不是在两个函数中都重复使用)是可以的
>性能方面:不重新创建缓冲区是可以的,因为那样会
暗示额外的计算;毕竟,缓冲区使用者通常不会到达缓冲区中每个ASCII字符串的后半部分

上一篇:python-CFFI:TypeError:ctype’char []’的初始化程序必须是字节或列表或元组,而不是str


下一篇:pve6_群晖_双网口直连_链路聚合