Как исправить ошибку "Нет больше файлов" в приложении Delphi с таблицами Paradox в Windows 10 1803?
В старых приложениях Delphi, которые используют старый и устаревший, но все еще используемый механизм баз данных BDE с файлами базы данных Paradox, находящимися на компьютере Windows 10, который обновлен до версии 1803 "Spring Creators Update", но на клиентских компьютерах используется любая более старая версия Windows, например Windows 10 1709 или Windows 7, открытие таблицы Paradox иногда завершается с ошибкой "No more files", idapi32.dll код ошибки DBIERR_OSENMFILE. Это вызывает исключение EDBEngineError в DBTables.pas / TTable.GetHandle(), которое вызывается TTable.CreateHandle, вызывается TBDEDataSet.OpenCursor().
Похоже, что ошибка вызвана некоторыми изменениями, связанными с совместным использованием файлов, в обновлении Windows 10 1803. Удаление обновления 1803 с компьютера с общим доступом к файлам Windows 10 или обновление всех клиентских компьютеров до Windows 10 + 1803, похоже, устраняет ошибку.
Люди предполагают, что изменения имеют какое-то отношение к протоколу SMB, может быть, Защитник Windows и / или другие вопросы, связанные с безопасностью. Вот обсуждение Google Plus https://plus.google.com/106831056534874810288/posts/F4nsoTz2pDi
Как можно обойти ошибку "Нет больше файлов" с помощью некоторых достаточно легко выполнимых изменений в приложении Delphi, позволяя клиентским и серверным компьютерам с общим доступом к файлам продолжать использовать гетерогенные версии Windows?
Пожалуйста, постарайтесь не отвечать и не комментировать очевидные вещи, такие как "небо голубое" или "BDE стар и устарел". Сохранение BDE - это решение, которое не может быть изменено, конечно, не как "исправление ошибки".
В качестве экстренного исправления мы прибегли к простой повторной попытке DbiOpenTable, когда он возвращает код ошибки DBIERR_OSENMFILE. Я отправил ответ с исходным кодом взломать idapi32.dll. До сих пор кажется, что если первый DbiOpenTable сообщает "Нет больше файлов", вторая попытка завершается успешно, и приложение работает, ничего не замечая.
2 ответа
- ВНИМАНИЕ: то, что следует, является взломом. Кладж. Пластырь, клей, клейкая лента и жвачка. BDE старый. Вы полностью по своему усмотрению, если вы используете BDE и / или попробуйте этот хак. Я не несу ответственности за его использование. Если это работает для вас, хорошо для вас. Если это разрушает ваш бизнес, это плохо для вас.
Поскольку таблицы Paradox по-прежнему в основном работали и ошибка, по-видимому, возникала немного случайно, и поскольку кто-то подозревал, что Защитник Windows как-то связан с этим, я подумал, что, возможно, ему просто нужно немного поработать. Если DbiOpenTable() внезапно запускается иногда при сбое по определенной комбинации версий клиент / сервер SMB из-за "Нет больше файлов" ... тогда почему бы просто не попробовать снова выполнить файловую операцию. Я помещаю логику "если она возвращает ошибку DBIERR_OSENMFILE, затем Sleep() и попробуйте снова" вокруг функции DbiOpenTable, и угадываю, что, похоже, сработало.
Хакерство вокруг "особенностей" BDE знакомо всем, кто должен поддерживать приложения на основе BDE. Поэтому я сделал исправление для функции DbiOpenTable idapi32.dll, начиная со старой подпрограммы, изначально написанной Рейнальдо Яньесом, для исправления ошибки "Недостаточно места на диске" в BDE, когда свободное место на диске находится на границе 4 ГБ. Смотрите https://cc.embarcadero.com/Item/21475
Чтобы использовать это, добавьте Fix1803 в предложении использования и вызовите PatchBDE где-нибудь перед тем, как начинать открывать таблицы Paradox. Может быть, позвоните UnPatchBDE, когда вы закончите, хотя я не думаю, что это необходимо.
Но помните, что вы сами по себе, и это очень экспериментальный код.
unit Fix1803;
// * KLUDGE WARNING *
// Patch (hack) idapi32.dll DbiOpenTable() to try harder, to work with Windows 10 1803 "Spring Creators Update".
//
// The patching routine is an extension of code originally written by Reinaldo Yañez.
// see https://cc.embarcadero.com/Item/21475
//
// Some original Spanish comments are left in place.
interface
procedure PatchBDE;
procedure UnPatchBDE;
implementation
uses
Windows, Db, DbTables, BDE, SysUtils;
// ------------------------------------------- DbiOpenTable hook
var DbiOpenTable_address_plus_9 : Pointer;
function Actual_DbiOpenTable_CallStub(hDb: hDBIDb; pszTableName: PChar; pszDriverType: PChar; pszIndexName: PChar; pszIndexTagName: PChar; iIndexId: Word; eOpenMode: DBIOpenMode; eShareMode: DBIShareMode; exltMode: XLTMode; bUniDirectional: Bool; pOptParams: Pointer; var hCursor: hDBICur): DBIResult stdcall; assembler;
asm
// these two instructions are implicitly contained in the start of the function
// push ebp
// mov ebp, esp
add esp, $fffffee8
jmp dword ptr [DbiOpenTable_address_plus_9]
end;
function LogHook_DbiOpenTable (hDb: hDBIDb; pszTableName: PChar; pszDriverType: PChar; pszIndexName: PChar; pszIndexTagName: PChar; iIndexId: Word; eOpenMode: DBIOpenMode; eShareMode: DBIShareMode; exltMode: XLTMode; bUniDirectional: Bool; pOptParams: Pointer; var hCursor: hDBICur): DBIResult stdcall;
var
i : Integer;
begin
Result := Actual_DbiOpenTable_CallStub(hDb, pszTableName, pszDriverType, pszIndexName, pszIndexTagName, iIndexId, eOpenMode, eShareMode, exltMode, bUniDirectional, pOptParams, hCursor);
// if we got the "No more files" error, try again... and again.
i := 1;
while (Result = DBIERR_OSENMFILE) and (i < 10) do
begin
Windows.Sleep(i);
Result := Actual_DbiOpenTable_CallStub(hDb, pszTableName, pszDriverType, pszIndexName, pszIndexTagName, iIndexId, eOpenMode, eShareMode, exltMode, bUniDirectional, pOptParams, hCursor);
Inc(i);
end;
end;
// ------------------------------------------- Patching routines
const // The size of the jump instruction written over the start of the original routine is 5 bytes
NUM_BYTES_OVERWRITTEN_BY_THE_PATCH = 5;
type
TRYPatch = record
OrgAddr: Pointer;
OrgBytes: array[0..NUM_BYTES_OVERWRITTEN_BY_THE_PATCH-1] of Byte;
end;
procedure TRYPatch_Clear(var ARYPatch : TRYPatch);
begin
FillChar(ARYPatch, SizeOf(TRYPatch), 0);
end;
function RedirectFunction(OldPtr, NewPtr, CallOrigStub : Pointer; var OriginalRoutineAddressPlusN: Pointer; NumBytesInCompleteInstructionsOverwritten : Integer): TRYPatch;
type
PPtr=^pointer;
PPPtr=^PPtr;
TByteArray=array[0..maxint-1] of byte;
PByteArray=^TByteArray;
function SameBytes(Ptr1, Ptr2 : Pointer; NumBytes : Integer) : Boolean;
var
i : Integer;
begin
Result := true;
i := 0;
while (Result) and (i < NumBytes) do
begin
Result := Result and ((PByteArray(Ptr1)^[i] = PByteArray(Ptr2)^[i]));
Inc(i);
end;
end;
var
PatchingAddress : Pointer;
OldProtect,
Protect : DWORD;
p: PByteArray;
i : Integer;
begin
PatchingAddress := OldPtr;
if PWord(PatchingAddress)^ = $25FF then
begin {Es un JMP DWORD PTR [XXXXXXX](=> Esta utilizando Packages)}
p := PatchingAddress;
PatchingAddress := (PPPtr(@p[2])^)^; // PatchingAddress now points to the start of the actual original routine
end;
// Safety check (as if this thing was "safe"). The given replacement routine must start with the same bytes as the replaced routine.
// Otherwise something is wrong, maybe a different version of idapi32.dll or something.
if (CallOrigStub <> nil) and not SameBytes(PatchingAddress, CallOrigStub, NumBytesInCompleteInstructionsOverwritten) then
raise Exception.Create('Will not redirect function, original call stub doesn''t match.');
// Change memory access protection settings, so we can change the contents
VirtualProtect(PatchingAddress, NUM_BYTES_OVERWRITTEN_BY_THE_PATCH, PAGE_READWRITE, @OldProtect);
// Save the old contents of the first N bytes of the routine we're hooking
Result.OrgAddr := PatchingAddress; // Save the address of the code we're patching (which might not be the same as the original OldPtr given as parameter)
for i := 0 to NUM_BYTES_OVERWRITTEN_BY_THE_PATCH-1 do
result.OrgBytes[i] := PByte(Integer(PatchingAddress) + i)^;
// Replace the first bytes of the original function with a relative jump to the new replacement hook function
// First write the instruction opcode, $E9 : JMP rel32
PByte(PatchingAddress)^:= $E9;
// Then write the instruction's operand: the relative address of the new function
PInteger(Integer(PatchingAddress)+1)^ := Integer(NewPtr) - Integer(PatchingAddress) - 5;
// Address to jump to, for the replacement routine's jump instruction
OriginalRoutineAddressPlusN := Pointer(Integer(PatchingAddress) + NumBytesInCompleteInstructionsOverwritten);
// Restore the access protection settings
VirtualProtect(PatchingAddress, NUM_BYTES_OVERWRITTEN_BY_THE_PATCH, OldProtect, @Protect);
FlushInstructionCache(GetCurrentProcess, PatchingAddress, NUM_BYTES_OVERWRITTEN_BY_THE_PATCH);
end;
procedure RestorePatch(RestorePatch: TRYPatch);
var
OldProtect,
Protect : DWORD;
OldPtr: Pointer;
i : Integer;
begin
OldPtr := RestorePatch.OrgAddr;
VirtualProtect(OldPtr, NUM_BYTES_OVERWRITTEN_BY_THE_PATCH, PAGE_READWRITE, @OldProtect);
for i := 0 to NUM_BYTES_OVERWRITTEN_BY_THE_PATCH-1 do
PByte(Integer(OldPtr) + i)^ := RestorePatch.OrgBytes[i];
VirtualProtect(OldPtr, NUM_BYTES_OVERWRITTEN_BY_THE_PATCH, OldProtect, @Protect);
FlushInstructionCache(GetCurrentProcess, OldPtr, NUM_BYTES_OVERWRITTEN_BY_THE_PATCH);
end;
var
idapi32_handle: HMODULE;
Patch_DbiOpenTable : TRYPatch;
procedure PatchBDE;
begin
if idapi32_handle <> 0 then Exit; // already_patched
idapi32_handle := LoadLibrary('idapi32');
if idapi32_handle <> 0 then
begin
Patch_DbiOpenTable := RedirectFunction(GetProcAddress(idapi32_handle, 'DbiOpenTable'), @LogHook_DbiOpenTable, @Actual_DbiOpenTable_CallStub, DbiOpenTable_address_plus_9, 9);
end;
end;
procedure UnPatchBDE;
begin
if idapi32_handle <> 0 then
begin
{Leave everything as before, just in case...}
if Patch_DbiOpenTable.OrgAddr <> nil then
RestorePatch(Patch_DbiOpenTable);
FreeLibrary(idapi32_handle);
idapi32_handle := 0;
end;
end;
initialization
idapi32_handle := 0;
TRYPatch_Clear(Patch_DbiOpenTable);
end.
VMWare, Virtual Box и т. Д. Для виртуализации Windows 7. Если, как вы говорите, W7 работает безупречно, это решит проблему.