我使用 node-ffi 调用 EnumPrintersA / EnumPrintersW 功能,以获取可从我的电脑访问的本地打印机列表。
你应该创建一个缓冲区,它将由EnumPrinters函数填充信息
但是你不知道缓冲区所需的大小
在这种情况下,您需要执行两次 EnumPrintersA / EnumPrintersW 。
在第一次调用期间,此函数计算有关打印机信息的内存量,在第二次调用期间,此函数使用有关打印机的信息填充缓冲区。
对于Unicode版本的 EnumPrinters 功能,打印机名称中的每个字母将使用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;
答案 0 :(得分:2)
一开始我觉得你的代码出了问题,所以我一直在寻找一个错误(由 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更准确)& #34;保存&#34 ;.这是上述程序的输出(感谢@qxz纠正我的初始版本(以及有缺陷的版本)):
EnumPrintersA(1) needs a 544(0x00000220) bytes long buffer. EnumPrintersW(1) needs a 544(0x00000220) bytes long buffer. buffera[0x03161B20:0x03161D3F] bufferw[0x03165028:0x03165247] EnumPrintersA(2) copied 544 bytes in the buffer out of which the first 84(0x00000054) represent 7 structures of size 12 EnumPrintersW(2) copied 544 bytes in the buffer out of which the first 84(0x00000054) represent 7 structures of size 12 Printing ASCII results: Item 0 pPrinterName: [Send To OneNote 2013] Item 1 pPrinterName: [NPI060BEF (HP LaserJet Professional M1217nfw MFP)] Item 2 pPrinterName: [Microsoft XPS Document Writer] Item 3 pPrinterName: [Microsoft Print to PDF] Item 4 pPrinterName: [HP Universal Printing PCL 6] Item 5 pPrinterName: [HP LaserJet M4345 MFP [7B63B6]] Item 6 pPrinterName: [Fax] Printing WIDE results: Item 0 pPrinterName: [Send To OneNote 2013] Item 1 pPrinterName: [NPI060BEF (HP LaserJet Professional M1217nfw MFP)] Item 2 pPrinterName: [Microsoft XPS Document Writer] Item 3 pPrinterName: [Microsoft Print to PDF] Item 4 pPrinterName: [HP Universal Printing PCL 6] Item 5 pPrinterName: [HP LaserJet M4345 MFP [7B63B6]] Item 6 pPrinterName: [Fax] Press a key to exit...
令人惊讶(至少对我而言),您所描述的行为可以被复制。
注意以上输出来自程序的 32bit 编译版本( 64bit 指针更难阅读:)),但是构建 64bit 时的行为是可重现的(我在 Win10 上使用 VStudio 10.0 )。
由于缓冲区末尾有确定的字符串,我开始调试:
上图是 VStudio 10.0 调试窗口的图片,程序在 testFunc 结尾处中断,就在释放1 st 之前指针。现在,我不知道你对 VStudio 的调试有多熟悉,所以我要走遍(相关)窗口区域:
现在,回到内存布局:并非右边的所有 char 都必然是它们看起来的,其中一些只是为了人类可读性而显示。例如,右侧有很多点(。),但它们不是全部点。如果您在相应的十六进制表示中查找一个点,您会注意到其中许多人 00 或 NULL (这是一个不可打印的 char ,但它显示为一个点)。
关于每个2 Memory 窗口的缓冲区内容(查看 char 表示),有3个区域:
让我们看看 WIDE 字符串区域(内存2 - 中区):正如您所提到的,每个字符都有2个字节:因为在我的情况下它们都是 ASCII char , MSB (或代码页字节)总是 0 (这就是为什么你看到 char s和点交错的原因:例如&#34; .LaserJet & #34;第4行)。
由于缓冲区中有多个字符串(或者字符串,如果你愿意) - 或者甚至更好:TCHAR*
中的多个TCHAR*
- 它们必须分开:这是由< em> NULL WIDE char ( hex : 00 00 , char :&#34; .. &#34;)在每个字符串的末尾;结合下一个字符串的1 st 字节( char )也是 00 (。),您将看到一个3 NULL 字节的序列( hex : 00 00 00 , char :&#34; ... &#34;),这是中间区域中2( WIDE )字符串之间的分隔符。
现在,比较2个中间部分(对应于2个缓冲区),您会注意到字符串分隔符完全处于相同位置以及更多:最后部分每个字符串也是相同的(每个字符串的后半部分更精确)。
考虑到这一点,这是我的理论:
我认为 EnumPrintersA 调用 EnumPrintersW ,然后它遍历每个字符串(在缓冲区的末尾),并调用 wcstombs 甚至更好:[MS.Docs]: WideCharToMultiByte function对它们进行转换(将它们转换到位 - 因此生成的 ASCII 字符串仅占用 WIDE的一半 st 字符串,保留2 nd 半未修改),而不转换所有缓冲区。我必须通过在 winspool.drv 中查看反汇编程序来验证这一点。
就个人而言(如果我是对的)我认为这是一个蹩脚的解决方法(或者我喜欢称之为获取),但谁知道,也许所有 * , * W 函数对(至少那些在char*
中返回多个char*
的人)就像这样工作。无论如何,这种方法也有优点(至少对于这两种功能):
答案 1 :(得分:2)
我可以确认您在EnumPrintersA
和EnumPrintersW
找到的内容是可重现的。
在我的机器中,它们都需要240个字节。
这让我很好奇,所以我决定为每个函数分配一个单独的缓冲区,并将每个缓冲区转储到一个文件中,然后用十六进制编辑器打开它们。 每个文件的有趣部分当然是打印机的名称。
为了简短起见,我将向您展示打印机的前3个名称。
第一行来自EnumPrintersA
,第二行来自EnumPrintersW
:
Fax.x...FX DocuPrint C1110 PCL 6..C.1.1.1.0. .P.C.L. .6...Microsoft XPS Document Writer.o.c.u.m.e.n.t. .W.r.i.t.e.r...
F.a.x...F.X. .D.o.c.u.P.r.i.n.t. .C.1.1.1.0. .P.C.L. .6...M.i.c.r.o.s.o.f.t. .X.P.S. .D.o.c.u.m.e.n.t. .W.r.i.t.e.r...
从这个结果看来,EnumPrintersA
调用EnumPrintersW
来实际工作,然后简单地将缓冲区中的每个字符串转换为单字节字符,并将结果字符串放在同一个地方。
为了确认这一点,我决定追踪EnumPrintersA
代码,我发现它肯定会在EnumPrintersW
位置调用winspool.EnumPrintersA + 0xA7
。
在不同的Windows版本中,实际位置可能会有所不同。
这让我更加好奇,所以我决定测试其他有A和W版本的功能。 这是我发现的:
EnumMonitorsA 280 bytes needed
EnumMonitorsW 280 bytes needed
EnumServicesStatusA 20954 bytes needed
EnumServicesStatusW 20954 bytes needed
EnumPortsA 2176 bytes needed
EnumPortsW 2176 bytes needed
EnumPrintProcessorsA 24 bytes needed
EnumPrintProcessorsW 24 bytes needed
从这个结果来看,我的结论是EnumPrintersA
为实际工作调用EnumPrintersW
并转换缓冲区中的字符串,而其他具有A和W版本的函数也做同样的事情。
这似乎是一种常见的机制,可以避免重复代码而不考虑更大的缓冲区,可能因为缓冲区无论如何都可以解除分配。