Как исправить ошибку "Нет больше файлов" в приложении 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 работает безупречно, это решит проблему.

Другие вопросы по тегам