Как исправить ошибку Больше файлов в приложении Delphi с таблицами Paradox в Windows 10 1803?

В старых приложениях Delphi, которые используют старую и устаревшую, но все еще использующую ядро ​​базы данных BDE с файлами базы данных Paradox, находящимися на компьютере с Windows 10, обновленном до версии 1803 «Spring Creators Update», но клиентские компьютеры, использующие любую старую версию Windows, например Windows 10 1709 или Windows 7 при открытии таблицы Paradox иногда возникает ошибка «Больше файлов нет», код ошибки 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/

Как можно обойти ошибку «Нет файлов» с помощью некоторых достаточно легко выполнимых изменений в приложении Delphi, позволяя при этом клиентскому и серверному компьютерам с совместным доступом к файлам продолжать использовать разнородные версии Windows?

Пожалуйста, постарайтесь воздержаться от ответов или комментариев на такие очевидные вещи, как «небо голубое» или «BDE устарела и устарела». Сохранение BDE - это решение, которое нельзя изменить, ни в коем случае не как «исправление ошибки».

В качестве экстренного исправления мы прибегли к простому повторному использованию DbiOpenTable, когда он возвращает код ошибки DBIERR_OSENMFILE. Я опубликовал ответ с исходным кодом на хак idapi32.dll. Пока что кажется, что если первая DbiOpenTable говорит «Нет файлов», вторая попытка завершается успешно, и приложение работает, ничего не замечая.


person Side S. Fresh    schedule 07.06.2018    source источник
comment
Обновление 1803 отключает SMBv1. Вы можете повторно включить это с помощью функций Windows: tenforums.com/tutorials/.   -  person whosrdaddy    schedule 07.06.2018
comment
Обязательный (несмотря на ваш запрос) комментарий: BDE устарел уже более десяти лет. Этого времени достаточно, чтобы найти замену. В какой-то момент вам придется это сделать, потому что заставить его работать становится все труднее или сложнее, или вы просто решите прекратить обновление другого программного обеспечения и ОС, в которых используется BDS, и вы должны ожидать, что другие остановятся. поддерживая ваше использование этой устаревшей платформы. А если серьезно - больше десяти лет.   -  person Ken White    schedule 07.06.2018
comment
@whosrdaddy: независимо от того, имеет ли поддержка SMB1 какое-либо отношение к периодическим ошибкам «Больше файлов», включение SMB v1 не может быть предложено в качестве исправления ошибки по причинам, связанным с безопасностью, перечисленным на странице, на которую вы указали ссылку.   -  person Side S. Fresh    schedule 08.06.2018


Ответы (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 в предложение uses и вызовите 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.
person Side S. Fresh    schedule 07.06.2018

VMWare, Virtual Box и т. Д. Для виртуализации Windows 7. Если, как вы говорите, W7 будет работать безупречно, это решит проблему.

person Fabricio Araujo    schedule 15.06.2018