我使用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).
由于缓冲区末尾肯定有字符串,因此我开始调试:
上图是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字符串的后半部分