Я звоню EnumPrintersA/EnumPrintersW функции, использующие Узел-FFI чтобы получить список локальных принтеров, доступных с моего компьютера.
Вы должны создать буфер, который будет заполняться информацией функцией 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;
Сначала я думал, что с вашим кодом что-то не так, поэтому я продолжал искать ошибку (введенную FFI или же JS слои, или опечатка или что-то подобное), но я не мог ничего найти.
Тогда я начал писать программу, похожую на вашу в С (чтобы устранить любые дополнительные слои, которые могут привести к ошибкам). Вот:
#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;
}
Заметка: с точки зрения имен переменных (я держал их краткими — и, следовательно, не очень интуитивно понятным), или же вес в конце их имен означает, что они используются для ASCII / ШИРОКИЙ версия.
Сначала я боялась, что EnumPrinters
может ничего не возвращать, так как на данный момент я не подключен ни к какому принтеру, но, к счастью, у меня есть некоторые (7, если быть более точными) «сохраненные». Вот выходные данные вышеупомянутой программы (спасибо @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...
удивительно (по крайней мере для меня), поведение, которое вы описали, может быть воспроизведено.
Заметка что приведенный выше вывод получен из 32-битной скомпилированной версии программы (64-битные указатели труднее читать :)), но поведение воспроизводимо и при компиляции для 64-битной (я использую VStudio 10.0 на Win10).
Поскольку в конце буфера наверняка есть строки, я начал отладку:
Выше картина VStudio 10.0 Окно отладки, с программой, прерванной в конце testFunc
перед освобождением 1-го указателя. Теперь я не знаю, насколько вы знакомы с отладкой на VStudio, так что я собираюсь пройти через (соответствующие) области окна:
bufw
, объем памяти Окно содержит ряд строк, и в каждой строке есть адрес памяти (выделен серым цветом слева), за которым следует его содержимое в наговор (каждый байт соответствует 2 наговор цифры — например, 1E), то справа то же содержание в голец представление (каждый байт соответствует 1 символу — я собираюсь вернуться к этому), затем следующий ряд и так далее.bufa
,Теперь вернемся к макету памяти: не все символы справа — это то, чем они кажутся, некоторые из них просто отображаются для удобства чтения. Например, есть много точек (.) на правой стороне, но они не все точки. Если вы ищете точку на соответствующем наговор представление вы заметите, что для многих из них это 00 или же NULL
(это непечатаемый символ, но он отображается в виде точки).
Что касается содержимого буфера каждого из 2 объем памяти окна (глядя на представление char), есть 3 зоны:
PRINTER_INFO_4*
зона или тарабарщина в начале: 544 байта, соответствующих приблизительно первым 3 строкамДавайте посмотрим на ШИРОКИЙ Струнная зона (Память 2 — средняя зона): как вы упомянули, у каждого персонажа есть 2 байта: потому что в моем случае они все ASCII очаровывает MSB (или кодовая byte) всегда равен 0 (поэтому вы видите чередование символов и точек: например, «.L.a.s.e.r.J.e.t» в строке 4).
Поскольку в буфере есть несколько строк (или, если хотите, строка) — или даже лучше: несколько TCHAR*
в TCHAR*
— они должны быть отделены: это делается NULL
ШИРОКИЙ char (hex: 00 00, голец ..) в конце каждой строки; в сочетании с тем, что 1-й символ первой строки следующей строки также 00 (.), вы увидите последовательность из 3 NULL
байт (шестнадцатеричный: 00 00 00, голец: …) и это разделитель между 2 (ШИРОКИЙ) струны в средней зоне.
Теперь, сравнивая 2 средние части (соответствующие 2 буферам), вы заметите, что разделители строк точно в тех же позициях и больше: последние части каждой строки одинаковы (последние половины каждой строки, чтобы быть более точным).
Учитывая это, вот моя теория:
Я думаю EnumPrintersA
звонки EnumPrintersW
и затем он перебирает каждую из строк (в конце буфера) и вызывает wcstombs
или даже лучше: WideCharToMultiByte на них (преобразование их на месте — и, следовательно, в результате ASCII Строка занимает только 1-ю половину ШИРОКИЙ строка, оставляя 2-ую половину неизменной), без преобразования всего буфера. Я должен проверить это, посмотрев с дизассемблером в WINSPOOL.DRV.
Лично (если я прав) я думаю, что это неудачный обходной путь (или gainarie как мне нравится это называть) но кто знает может быть все * A, * W пары функций (по крайней мере, те, кто возвращает несколько char*
в char*
) работать так. В любом случае, есть и плюсы для этого подхода (по крайней мере, для этих двух функций):
Я могу подтвердить, что вы нашли с 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, также делают то же самое.
Похоже, что это общий механизм, позволяющий избежать дублирования кода за счет больших буферов, возможно потому, что буферы могут быть в любом случае освобождены.