Почему EnumPrintersA и EnumPrintersW запрашивают одинаковый объем памяти?

Я звоню 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;

3

Решение

Сначала я думал, что с вашим кодом что-то не так, поэтому я продолжал искать ошибку (введенную 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 Окно отладки

Выше картина VStudio 10.0 Окно отладки, с программой, прерванной в конце testFuncперед освобождением 1-го указателя. Теперь я не знаю, насколько вы знакомы с отладкой на VStudio, так что я собираюсь пройти через (соответствующие) области окна:

  • внизу есть 2 Часы окна (используется для отображения переменных во время работы программы). Как видно, переменная название, Значение а также Тип отображаются.
    • справа (Смотреть 1): 1-я (0-я) и последняя (6-я — из 7) структур в начале каждого из 2 буферов.
    • слева (Смотреть 2): адреса 2 буферов.
  • выше Часы окна (Память 2) это содержимое памяти для bufw, объем памяти Окно содержит ряд строк, и в каждой строке есть адрес памяти (выделен серым цветом слева), за которым следует его содержимое в наговор (каждый байт соответствует 2 наговор цифры — например, 1E), то справа то же содержание в голец представление (каждый байт соответствует 1 символу — я собираюсь вернуться к этому), затем следующий ряд и так далее.
  • выше Память 2 (Память 1): это содержимое памяти для bufa,

Теперь вернемся к макету памяти: не все символы справа — это то, чем они кажутся, некоторые из них просто отображаются для удобства чтения. Например, есть много точек (.) на правой стороне, но они не все точки. Если вы ищете точку на соответствующем наговор представление вы заметите, что для многих из них это 00 или же NULL (это непечатаемый символ, но он отображается в виде точки).

Что касается содержимого буфера каждого из 2 объем памяти окна (глядя на представление char), есть 3 зоны:

  • PRINTER_INFO_4* зона или тарабарщина в начале: 544 байта, соответствующих приблизительно первым 3 строкам
  • странные символы из последних ~ 1,5 строк: они находятся вне наших буферов, поэтому мы не заботимся о них
  • средняя зона: где хранятся струны

Давайте посмотрим на ШИРОКИЙ Струнная зона (Память 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*) работать так. В любом случае, есть и плюсы для этого подхода (по крайней мере, для этих двух функций):

  • DEV-накрест — нормально, что одна функция вызывает другую и сохраняет реализацию в 1 месте (вместо дублирования в обеих функциях).
  • С точки зрения производительности — это нормально, чтобы не воссоздавать буфер, так как это
    подразумевать дополнительные вычисления; в конце концов, потребитель буфера обычно не достигает второй половины каждого ASCII строка в буфере.
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, также делают то же самое.
Похоже, что это общий механизм, позволяющий избежать дублирования кода за счет больших буферов, возможно потому, что буферы могут быть в любом случае освобождены.

2

По вопросам рекламы [email protected]