Błąd "Out of memory" i Runtime error 213 w .dll

0

Witam Was, moi drodzy po dłuższej przerwie. Przychodzę z problemem, który mam nadzieję pomożecie mi rozwiązać. Mianowicie chodzi o błąd out of memory w dll . Z nieznanych mi przyczyn, klauzula try...except nie wychwytuje tego błedu w dll i program się wykrzacza z runtime error 213. Jak sobie z tym poradzić? Czy jest sposób by w dll poznać czy przydzielenie pamięci się powiedzie czy nie? Próbowałem jakichś metod z funkcjami zwracającymi ilość dostępnej pamięci ale to lipnie działało. Bardzo proszę o konstruktywne odpowiedzi. Z góry dziękuję.

0

Po pierwsze nikt z fusów nie wróży może załącz jakiś testowy projekt wraz z DLL, który pozwala na odtworzenie błędu a po drugie co za DLL czy to twoja (masz źródła) czy nie?

0
kAzek napisał(a):

Po pierwsze nikt z fusów nie wróży może załącz jakiś testowy projekt wraz z DLL, który pozwala na odtworzenie błędu a po drugie co za DLL czy to twoja (masz źródła) czy nie?

+1

Kolejne:
Jeśli @Marmar stosujesz alokowanie i zwalnianie mix w main i DLL, to prowadzi do dyskretnej katastrofy. Zycie obiektów na granicy jest bardzo ryzykowne.
Fabryczne jak String (W Builder C++ przynajmniej) mają specjalne wsparcie, ale chyba (?) tylko po włączeniu w projekt konkretnych wersji DLL

Zapraszam wszystkich zwolenników wyższości Delphi z sąsiedniego wątku. Ja jednak wolę prawdziwe stracktrace.

1

@ZrobieDobrze każde narzędzie trzeba jednak trochę znać , rozwiązywanie wszystkich problemów za pomocą "try...except" to nie najlepsza metoda a tak to chyba chciał rozwiązać OP.

Praktycznie na ma danych aby cokolwiek pomóc

Strzał w ciemno:

{$SetPEFlags $20}  // {$SetPEFlags IMAGE_FILE_LARGE_ADDRESS_AWARE}  // https://docwiki.embarcadero.com/RADStudio/Sydney/en/PE_(portable_executable)_header_flags_(Delphi)
0
Adamek Adam napisał(a):

@ZrobieDobrze każde narzędzie trzeba jednak trochę znać , rozwiązywanie wszystkich problemów za pomocą "try...except" to nie najlepsza metoda a tak to chyba chciał rozwiązać OP.

Praktycznie na ma danych aby cokolwiek pomóc

Strzał w ciemno:

{$SetPEFlags $20}  // {$SetPEFlags IMAGE_FILE_LARGE_ADDRESS_AWARE}  // https://docwiki.embarcadero.com/RADStudio/Sydney/en/PE_(portable_executable)_header_flags_(Delphi)

Oj, chodzi mi o proste alokowanie pamięci w dll (oczywiście moja dll i mam źródło) za pomocą getmem, reallocmem, lub dla tablic dynamicznych setlength. Jeśli nie ma pamięci to program się wykrzacza z okienkiem runtime error 213. W execu działa try...except i mozna to przechwycić, oprogramować, a w dll nie działa, stąd moje pytanie.

ZrobieDobrze napisał(a):
kAzek napisał(a):

Po pierwsze nikt z fusów nie wróży może załącz jakiś testowy projekt wraz z DLL, który pozwala na odtworzenie błędu a po drugie co za DLL czy to twoja (masz źródła) czy nie?

+1

Kolejne:
Jeśli @Marmar stosujesz alokowanie i zwalnianie mix w main i DLL, to prowadzi do dyskretnej katastrofy. Zycie obiektów na granicy jest bardzo ryzykowne.
Fabryczne jak String (W Builder C++ przynajmniej) mają specjalne wsparcie, ale chyba (?) tylko po włączeniu w projekt konkretnych wersji DLL

Zapraszam wszystkich zwolenników wyższości Delphi z sąsiedniego wątku. Ja jednak wolę prawdziwe stracktrace.

Chodzi tylko o kontrolę nad tym czy da się pobrać z systemu pamięć w dll czy nie da się.

0

SOA#1

program Project11;

uses
  Windows;

{$R *.res}

procedure DllMessage(n,m:Integer); external 'Project12.dll';

begin
  AllocConsole;
  Writeln('start');

  DllMessage(1000,10);
  Writeln('po DllMessage');
  Readln;
end.
library Project12;


uses
  SysUtils,
  Classes;

{$R *.res}

procedure DllMessage(n,m: integer);
var
  s: array of Integer;
  i: integer;
  tab: array of PByte;
begin
  Writeln('DllMessage(',n,',',m,')');
  SetLength(tab,n);

  try
    for i := 0 to n - 1 do
    begin
      Writeln('alloc mem n=',i);

      getmem( tab[i], m*1024*1024);
    end;
  except
    on e:Exception do
    begin
      Writeln('Exception?');
    end;
  end;
end;

exports DllMessage;

begin
end.

bez SetPEFlags udało się 195 x 10MB
z SetPEFlags udało się 398 x 10MB
:D

0

niby błąd 213 to Collection index out of range

Dla mnie właśnie bardziej tutaj jest problem, coś wypada poza zakres i może jakaś pętla się nie kończy w specyficznych warunkach (dlatego w końcu jest jakiś problem z pamięcią).

No ale bez kawałka kodu ciężko będzie wyrokować.
Dodatkowo, jeśli to twoja biblioteka to może użyj czegoś w stylu madExcept, może trochę więcej danych o błędzie uzyskasz?

0
Marmar napisał(a):

Oj, chodzi mi o proste alokowanie pamięci w dll (oczywiście moja dll i mam źródło) za pomocą getmem, reallocmem, lub dla tablic dynamicznych setlength. Jeśli nie ma pamięci to program się wykrzacza z okienkiem runtime error 213. W execu działa try...except i mozna to przechwycić, oprogramować, a w dll nie działa, stąd moje pytanie.

a zwalniasz ją kiedykolwiek?

Chodzi tylko o kontrolę nad tym czy da się pobrać z systemu pamięć w dll czy nie da się.

czy co się da?

Może pokaż jakikolwiek kawałek kodu wreszcie

0
Adamek Adam napisał(a):

SOA#1

program Project11;

uses
  Windows;

{$R *.res}

procedure DllMessage(n,m:Integer); external 'Project12.dll';

begin
  AllocConsole;
  Writeln('start');

  DllMessage(1000,10);
  Writeln('po DllMessage');
  Readln;
end.
library Project12;


uses
  SysUtils,
  Classes;

{$R *.res}

procedure DllMessage(n,m: integer);
var
  s: array of Integer;
  i: integer;
  tab: array of PByte;
begin
  Writeln('DllMessage(',n,',',m,')');
  SetLength(tab,n);

  try
    for i := 0 to n - 1 do
    begin
      Writeln('alloc mem n=',i);

      getmem( tab[i], m*1024*1024);
    end;
  except
    on e:Exception do
    begin
      Writeln('Exception?');
    end;
  end;
end;

exports DllMessage;

begin
end.

bez SetPEFlags udało się 195 x 10MB
z SetPEFlags udało się 398 x 10MB
:D

Adamek, właśnie u mnie to try...except tu nie działa. Nie wychwytuje błędu tylko zgłasza błąd 213 i się program wywala. Mam Turbo Delphi 2006

0
abrakadaber napisał(a):
Marmar napisał(a):

Oj, chodzi mi o proste alokowanie pamięci w dll (oczywiście moja dll i mam źródło) za pomocą getmem, reallocmem, lub dla tablic dynamicznych setlength. Jeśli nie ma pamięci to program się wykrzacza z okienkiem runtime error 213. W execu działa try...except i mozna to przechwycić, oprogramować, a w dll nie działa, stąd moje pytanie.

a zwalniasz ją kiedykolwiek?

Chodzi tylko o kontrolę nad tym czy da się pobrać z systemu pamięć w dll czy nie da się.

czy co się da?

Może pokaż jakikolwiek kawałek kodu wreszcie

Abrakadaber, zwykła rzecz jak w kodzie Adamka. Getmem, brak pamięci i zwałka.

0

Może masz subtelne różnice które dla Ciebie są nie istotne ale jednak tworzą problem ? (zależności, call conversion, bledną lista parametrów, align)
Ewentualnie pokaż swój kawałek kodu, może problem tkwi w ustawieniach projektu wiec wypchnij an GIT komplet pas,dpr,proj
A co na to debugger ?

0
Marmar napisał(a):

Adamek, właśnie u mnie to try...except tu nie działa. Nie wychwytuje błędu tylko zgłasza błąd 213 i się program wywala. Mam Turbo Delphi 2006

W C++, np w Builder bym stawiał sobie pytanie, czy elementy proceduralne (o rodowodzie z C) w ogóle rzucają wyjątki. W C++ nie, im sama koncepcja wyjątku jest obca, wiec dlaczego mają rzucać. Należy a) czytać dokumentację funkcji b) sprawdzać wynik który być może jest negatrywny

Delhi od dawna nie praktykuję.

0
Adamek Adam napisał(a):

Może masz subtelne różnice które dla Ciebie są nie istotne ale jednak tworzą problem ? (zależności, call conversion, bledną lista parametrów, align)
Ewentualnie pokaż swój kawałek kodu, może problem tkwi w ustawieniach projektu wiec wypchnij an GIT komplet pas,dpr,proj
A co na to debugger ?

w dll:

p:array of pointer;
 function doit(n,m:cardinal):cardinal;stdcall;
 var i,j:integer;
 begin for i:=0 to length(p)-1 do if p[i]<>nil then begin freemem(p[i]);p[i]:=nil;end; result:=0;
setlength(p,n);try
 for i:=0 to length(p)-1 do
 getmem(p[i],m);//fillchar(p[i]^,m,1); inc(result,m);
except 
for j:=0 to i-1 do freemem(p[j]); result:=0;//nie ma wyłapania wyjątku w dll tutaj
 end;
 end;
exports doit,

w execu:

procedure TForm1.Button1Click(Sender: TObject);
var n,m,k:int64;   avail,us:int64;
i:integer;s,t:string;
begin
 n:=spinedit1.Value;{100000}m:=spinedit2.Value{100000};
 try
 k:=doit(n,m);
except
n:=0; // nie ma wyłapania wyjątku w execu tutaj
end;
end;

Bardzo proszę. Prosty kod i nie działa. Przepraszam. Ten runtime error to 203 a nie 213.

1

przecież żaden kod nie złapie ci wyjątku między except/catch a end ani w delfinie ani w c# - wyjątki są łapane pomiędzy try a except/catch

0
abrakadaber napisał(a):

przecież żaden kod nie złapie ci wyjątku między except/catch a end ani w delfinie ani w c# - wyjątki są łapane pomiędzy try a except/catch

No tak. I potem kod skacze do tego co jest po except. O to mi chodziło, że tu tak nie działa w dll.

0

sugeruję formatować kod bo to ułatwią życie i wyłapywanie drobnych problemów w kodzie które powodują ogromne problemy w działaniu aplikacji.
kod strasznie nieczytelny,

  try
    for i := 0 to length(p) - 1 do
      getmem(p[i], m); // fillchar(p[i]^,m,1); inc(result,m);
  except
    for j := 0 to i - 1 do  <<<<<<<<<<<<<<<<<<<<<<< jaka wartość ma "i" ? bo ja nie widzę !!!
      freemem(p[j]);
    result := 0; // nie ma wyłapania wyjątku w dll tutaj
  end;

kompilator nie podpowiada ostrzeżenia w tym miejscu ?

1
for j := 0 to i - 1 do  <<<<<<<<<<<<<<<<<<<<<<< jaka wartość ma "i" ? bo ja nie widzę !!!

Taką, jaką i miało w momencie rzucenia wyjątku. Mam tylko nadzieję, że i to signed integer. :D

2

moja uwaga bazuje na ostrzeżeniu kompilatora W1037 FOR-Loop variable 'I' may be undefined after loop
https://docwiki.embarcadero.com/RADStudio/Sydney/en/W1037_FOR-Loop_variable_%27%25s%27_may_be_undefined_after_loop_(Delphi)

z ciekawostka sprawdziłem i u mnie po wyjątku licznik FOR ma poprawną wartość, wiec jezeli i ma poprawna wartosc to pewnie freemem zwalnia to czego nie powinien. Ewidentnie debugerem trzeba prześledzić w parę sekund byłą by odpowiedz

1
  p: array of pointer;

  function doit(n, m: cardinal): cardinal; stdcall;
  var
    I, j: integer;
  begin
    for I := 0 to length(p) - 1 do // <--  length(p) !!!!
      if p[I] <> nil then
      begin
        freemem(p[I]);
        p[I] := nil;
      end;
    result := 0;
    setlength(p, n);
    try
      for I := 0 to length(p) - 1 do
        getmem(p[I], m); // fillchar(p[i]^,m,1); inc(result,m);
    except
      begin
        for j := 0 to I - 1 do
          freemem(p[j]);
        result := 0; // nie ma wyłapania wyjątku w dll tutaj
      end;
    end;
  end;
for I := 0 to length(p)

Sformatowałem Twój kod, ten który podałeś jest totalnie nieczytelny.
Iterujesz po tablicy p ale nie widzę deklaracji jej rozmiaru

    try
      for I := 0 to length(p) - 1 do
        getmem(p[I], m); // fillchar(p[i]^,m,1); inc(result,m);
    except
      begin
        for j := 0 to I - 1 do
          freemem(p[j]);
        result := 0; // nie ma wyłapania wyjątku w dll tutaj
      end;
    end;

Coś takiego jak powyżej to tylko proszenie się o problemy. Odwołujesz się do zmiennej i sterującą pętlą for ... poza kodem pętli.
Może coś w ten sposób

    for I := 0 to length(p) - 1 do
      try
        getmem(p[I], m);
      except
        begin
          for j := 0 to I do
            freemem(p[j]);
          result := 0;
        end;
        break;
      end;
4

Panowie, kilka podpowiedzi.

Po pierwsze, jeśli iterujecie po tablicy, to do określenia ostatniego indeksu iteratora używajcie funkcji High — unikniecie błędów „off-by-one”, głowa będzie spokojniejsza. Natomiast do iterowania po tablicy w celu odczytu, warto korzystać z pętli for in — indeksowanie zabezpieczone, a dodatkowo, do zmiennej trafia element macierzy/listy w każdej iteracji, więc mamy zmienną od razu do użytku.

Po drugie, widzę komentarze z zaremowanym FillChar. Jeśli potrzebujecie zaalokować generyczny blok danych i od razu go zainicjalizować zerami, to korzystajcie z funkcji AllocMem zamiast GetMem. Nie ma sensu wołać dwóch funkcji, skoro wszystko można załatwić jedną. Przy czym preferuję korzystanie z funkcji do alokacji pamięci, zamiast z procedur (ale to akurat kwestia gustu).

Po trzecie, rozmiar macierzy jest znany, a także znany jest rozmiar każdego bloku, którego wskaźnik trafia do tej macierzy, to dlaczego by nie zaalokować jednego bloku dla wszystkich paczek? Jedna operacja, jeden blok, współpraca z cache CPU, szybkość i wygoda. Późniejsze iterowanie po paczkach, jeśli potrzebne, to pobranie adresu początku bloku i dodanie offsetu. W skrócie, procesor uwielbia wykonywać obliczenia, ale nie lubi pobierania danych z RAM-u.

Po czwarte — @Marmar — musisz zacząć przykładać się do formatowania kodu, bo to co tworzysz obecnie jest kompletnie nieczytelne. Nie dość, że sam sobie utrudniasz pracę, to w dodatku tracisz nasz czas — zamiast skupić się na analizie Twojego kodu, najpierw musimy go formatować. :/

0
furious programming napisał(a):

Panowie, kilka podpowiedzi.

Dobrze panie nauczycielu, dziękuję za cenne porady:-) ale do rzeczy. Czy znasz sposób na kontrolowanie użycia pamięci w dll by uniknąć runtime error 203 gdy pamięć się kończy? Jak już wspomniałem klauzula try...except nie działa w dll dla tego błędu w moim Delphi 2006.

0

dobrze że podałeś że to błąd 203 a nie 213 bo według mnie to dużo zmienia.
błąd 203 to nie jest out of memory tylko przepełnienie sterty.
Czy to nie jest tak że sterta standardowo ma "zaledwie" 1 MB jeśli dobrze pamiętam i trzeba sobie nią pozarządzać?

przetestuj na zdecydowanie mniejszej tablicy i zobacz czy błąd występuje?

2
Marmar napisał(a):

Czy znasz sposób na kontrolowanie użycia pamięci w dll by uniknąć runtime error 203 gdy pamięć się kończy?

Coż — aby wiedzieć czy dostępna pamieć się kończy, należy sprawdzić czy się kończy. ;)

Możesz skorzystać z systemowej funkcji GlobalMemoryStatusEx i w strukturze typu MEMORYSTATUSEX otrzymać wszystkie przydatne informacje (tutaj przykład użycia w Delphi). Jednak trzeba brać pod uwagę to, że w systemie działają setki procesów i tysiące wątków, więc wartości zwrócone przez tą funkcję długo poprawne nie są. Chodzi o to, że w międzyczasie inne procesy mogą alokować dane na własne potrzeby, dlatego raczej dorzuć do tego spory margines, który będzie kompensował działania innych procesów. Niestety innej możliwości nie ma, choć lepsza taka niż żadna.

Jak już wspomniałem klauzula try...except nie działa w dll dla tego błędu w moim Delphi 2006.

Wyjątki w połączeniu z bibliotekami DLL to ogólnie zły pomysł, bo to nie działa pomiędzy DLL a aplikacją i są z tym same problemy. Jedyne co w Twoim przypadku można zrobić to starać się nie dopuścić do tego, aby pamięć się skończyła, czyli do pojawienia się wyjątku.

Ciekaw jestem jeszcze jednej rzeczy — dlaczego pamieć się kończy? Twój pecet ma jej mało i ciągle balansujesz na granicy, czy kod masz napisany tak, że częstuje się RAM-em jak Chrome?

2
furious programming napisał(a):

Ciekaw jestem jeszcze jednej rzeczy — dlaczego pamieć się kończy? Twój pecet ma jej mało i ciągle balansujesz na granicy, czy kod masz napisany tak, że częstuje się RAM-em jak Chrome?

Podejrzewam, że problem leży w tym, że biblioteka i aplikacja są 32bit-owe (Turbo Delphi 2006 nie kompilowało do x64), więc program zużywając ~2GB ramu po prostu się wykłada (2gb to maksymalny rozmiar pamięci dla aplikacji 32 bitowych), a sprawdzanie wolnej pamięci w systemie nie wiele da, ponieważ system x64 obsługuje nawet do kilku TB ramu.

1

Cóż — jeśli się tkwi w 16-letniej technologii, to ma się problem. Pozostaje polecić Lazarusa/Delphi.

0

Próbki kodu OP sugerują że "bug" mógł się łatwo ukryć w tym nieczytelnym gąszczu, a widziałem tylko mały wycinek.
Jak by OP zrobił prosty projekt który można uruchomić na jakimś nowszym Delphi to łatwo by było potwierdzić/zaprzeczyć że to stara wersja.

2

@Adamek Adam: jeśli faktycznie jest to program 32-bitowy, to mu się inty przekręcają przy 2GB.

Tkwienie w 32-bitowych aplikacjach, pomimo tego, że od nastu lat mamy procesory 64-bitowe, jest wręcz absurdalne. Jeśli ktoś przespał okres migracji na nową, 64-bitową architekturę, śpi nadal i obudzić się nie chce, to nic nie da się z tym zrobić — pozostaje tylko współczuć.

0
robertz68 napisał(a):

dobrze że podałeś że to błąd 203 a nie 213 bo według mnie to dużo zmienia.
błąd 203 to nie jest out of memory tylko przepełnienie sterty.
Czy to nie jest tak że sterta standardowo ma "zaledwie" 1 MB jeśli dobrze pamiętam i trzeba sobie nią pozarządzać?

przetestuj na zdecydowanie mniejszej tablicy i zobacz czy błąd występuje?

Robert, przy mniejszej ilości danych jest ok, więc to rzeczywiście chodzi o stertę. O możliwość kontrolowania ile jest wolnej pamięci dla aplikacji.

furious programming napisał(a):
Marmar napisał(a):

Czy znasz sposób na kontrolowanie użycia pamięci w dll by uniknąć runtime error 203 gdy pamięć się kończy?

Coż — aby wiedzieć czy dostępna pamieć się kończy, należy sprawdzić czy się kończy. ;)

Możesz skorzystać z systemowej funkcji GlobalMemoryStatusEx i w strukturze typu MEMORYSTATUSEX otrzymać wszystkie przydatne informacje (tutaj przykład użycia w Delphi). Jednak trzeba brać pod uwagę to, że w systemie działają setki procesów i tysiące wątków, więc wartości zwrócone przez tą funkcję długo poprawne nie są. Chodzi o to, że w międzyczasie inne procesy mogą alokować dane na własne potrzeby, dlatego raczej dorzuć do tego spory margines, który będzie kompensował działania innych procesów. Niestety innej możliwości nie ma, choć lepsza taka niż żadna.

Jak już wspomniałem klauzula try...except nie działa w dll dla tego błędu w moim Delphi 2006.

Wyjątki w połączeniu z bibliotekami DLL to ogólnie zły pomysł, bo to nie działa pomiędzy DLL a aplikacją i są z tym same problemy. Jedyne co w Twoim przypadku można zrobić to starać się nie dopuścić do tego, aby pamięć się skończyła, czyli do pojawienia się wyjątku.

Ciekaw jestem jeszcze jednej rzeczy — dlaczego pamieć się kończy? Twój pecet ma jej mało i ciągle balansujesz na granicy, czy kod masz napisany tak, że częstuje się RAM-em jak Chrome?

Forious, ja nie moge nic ciekawego wyczytać z TMemoryStatusEx co by było dla mnie użyteczne. Próbowałem już tego.Tam są różne liczby i wg. moich obserwacji żadna nie odpowiada rzeczywistemu kończeniu się pamięci. Może nie potrafię. Napisz,proszę jeśli możesz, która to składowa tego rekordu. A dlaczego tak dużo pamięci potrzebuję? Projekt operuje na dużych liczbach integer o dynamicznej długość. A one sporo zajmują. W szczególności dla przyśpieszenia obliczeń, chcę trzymać symbole newtona dla tak dużych liczb w tablicy. No i muszę wiedzieć na ile mogę skorzystać z pamięci by mi się program nie wykrzaczał z błędem 203.

1

A w Delphi 2006 po stronie EXE wyjątki działają "po ludzku" ? , bo może zanim wywołasz funkcję z DLL sprawdź po stronie EXE ile możesz zajać

Nie rozważałeś Lazarus-a ? Zrobił byś aplikację 64 bit i problem magicznie sam by się rozwiązał

1
Marmar napisał(a):

No i muszę wiedzieć na ile mogę skorzystać z pamięci by mi się program nie wykrzaczał z błędem 203.

32-bitowość Twojej aplikacji ogranicza Cię do 2GB, a żeby móc alokować więcej danych, musiałbyś pokombinować.

Możesz np. odpalić kilka procesów swojego programu i dane pomiędzy nimi wymieniać za pomocą jakichś systemowych mechanizmów, np. file mappingu. W takim przypadku, każdy proces będzie mógł operować na swojej paczce maksymalnie 2GB, więc w teorii możesz alokować ile chcesz. Niestety minusem jest konieczność pisania całego systemu synchronizacji tych procesów i wymiany danych pomiędzy nimi, a to do najłatwiejszych zadań nie należy.

A możesz też w końcu porzucić 32-bitowy świat, skompilować program jako 64-bitowy i mieć problem adresacji z głowy.

Pytanie zasadnicze — dlaczego używasz Turbo Delphi? Musisz go używać, bo takie IDE macie w pracy czy w jakikolwiek sposób ktoś Cię zmusza do jego używania, czy może to Twój własny projekt, nad którym masz pełną władzę?

1 użytkowników online, w tym zalogowanych: 0, gości: 1