Witam
Używam Lazarusa v1.8.2 z FPC 3.0.4 32bit na Win10 home 64bit.
Przy pisaniu apek mam wyłączoną opcje "Aplikacja graficzna win32" by mieć coś w rodzaju konsoli do wyrzucania wyników poprzez writeln.
Apki to przeważnie jakieś proste interfejsy do baz danych.
Przy uruchomieniu apek (F9) raz jest wszystko OK - apka sie uruchamia - albo nie (jeśli coś pokaszaniłem) - albo wyrzuca błąd :
"Project project1 raised exeption class 'External:SIGSEGV'."
Nawet mimo braku błędów w apce.
Zdarzenie jest nieprzewidywalne i np.: na 4 x F9 bez zmiany kodu 2 razy się normalnie uruchomi a 2 razy się wywali jak zacytowałem wyżej.
Może ktoś coś wie jak z tym sobie poradzić ?
Bez kodu trudno wskazać gdzie masz błąd.
SIGSEGV wskazuje na błąd w dostępie do pamięci, czyli np. odwołanie do niezainicjowanego obiektu, wyjście poza rozmiar tablicy ...
Bez kodu trudno wskazać gdzie masz błąd.<
Problem w tym iż nie jest tak przy jednej apce tylko przy różnych ale z taką sama konfiguracją
Pokaż coś, bo nikt nie będzie zgadywał co masz w kodzie. Podałem dwie najczęstsze przyczyny wyjątku SIGSEGV .
Oki :
unit Unit1;
{$mode objfpc}{$H+}
interface
uses
Classes, SysUtils, odbcconn, sqldb, pqconnection, DB, FileUtil, Forms,
Controls, Graphics, Dialogs, StdCtrls, ComCtrls, CheckLst, Grids, DateUtils,
LConvEncoding, Windows, Types,base64;
type
pracownik = record
id:integer;
name:string;
lastlog:string;
hidden:boolean;
end;
{ TCardRotate }
TCardRotate = class(TForm)
Button1: TButton;
PQDSource: TDataSource;
PQTrans: TSQLTransaction;
PQuery: TSQLQuery;
wskazplikMSA: TButton;
importMSA: TButton;
ENDAPP: TButton;
MSADSource: TDataSource;
MSAodbc: TODBCConnection;
PageControl1: TPageControl;
podajplikbazy: TOpenDialog;
MSAQuery: TSQLQuery;
MSATrans: TSQLTransaction;
PQCon: TPQConnection;
listaimportu: TStringGrid;
zakladkaimportu: TTabSheet;
TabSheet2: TTabSheet;
procedure Button1Click(Sender: TObject);
procedure listaimportuClick(Sender: TObject);
procedure listaimportuDblClick(Sender: TObject);
procedure wskazplikMSAClick(Sender: TObject);
procedure importMSAClick(Sender: TObject);
procedure ENDAPPClick(Sender: TObject);
procedure FormShow(Sender: TObject);
private
{ private declarations }
public
{ public declarations }
procedure connect;
procedure ostatniimport;
procedure wyswietlpracownikow;
procedure zmienstan(id:integer;stan:boolean);
end;
var
CardRotate: TCardRotate;
implementation
{$R *.lfm}
{ TCardRotate }
procedure TCardRotate.FormShow(Sender: TObject);
begin
writeln('start');
importMSA.Enabled:=false;
connect;
wyswietlpracownikow;
ostatniimport;
end;
//----------------------------------------------------
procedure TCardRotate.connect;
var
plik: TextFile;
buildPath,oldPath,conffilepatch,daneconf: string;
begin
conffilepatch:=GetCurrentDir + '\config.txt';
oldPath := SysUtils.GetEnvironmentVariable('PATH');
buildPath := oldPath + ';dll\';
SetEnvironmentVariable('PATH', PChar(buildPath));
writeln(conffilepatch);
AssignFile(plik, conffilepatch);
try
try
reset(plik);
readln(plik, daneconf);
PQCon.HostName := daneconf;
readln(plik, daneconf);
PQCon.UserName := daneconf;
readln(plik, daneconf);
PQCon.Password := daneconf;
readln(plik, daneconf);
PQCon.DatabaseName := daneconf;
finally
PQCon.Connected := True;
CloseFile(plik);
end;
except
ShowMessage('Problem z połączeniem z bazą lub plikiem konfiguracyjnym. Plik powinien znajdować się tu: '
+ conffilepatch);
Application.Terminate;
end;
end;
//----------------------------------------------------
procedure TCardRotate.importMSAClick(Sender: TObject);
var
zapMSA,zapPQ: string;
i, ilewiersz: integer;
pracownicyMSA: array of pracownik;
begin
MSATrans.Active:=true;
zapMSA := 'SELECT e.employeeid as id ,(select iif(max(arisetime)<>null,max(arisetime),2) from eventrecord where e.employeeid=employeeid ) as lastlog, e.employeename as name FROM Employee e order by e.employeeid';
MSAQuery.SQL.Text := zapMSA;
MSADSource.DataSet := MSAQuery;
MSADSource.DataSet.Active := True;
MSADSource.DataSet.Refresh;
ilewiersz := MSADSource.DataSet.RecordCount;
SetLength(pracownicyMSA,ilewiersz+1);
writeln('dorekordu');
for i := 0 to ilewiersz do
begin
writeln('rek',i);
pracownicyMSA[i].name:=CP1250ToUtf8(MSADSource.DataSet.FieldByName('name').Value);
pracownicyMSA[i].id:=MSADSource.DataSet.FieldByName('id').Value;
pracownicyMSA[i].lastlog:=FormatDateTime('YYYY-MM-DD',MSADSource.DataSet.FieldByName('lastlog').Value);
MSADSource.DataSet.Next;
end;
MSAQuery.Clear;
MSAQuery.Close;
MSADSource.DataSet.Active:=false;
MSADSource.DataSet.Close;
for i := 0 to ilewiersz do
begin
zapPQ:='INSERT INTO recorder.user (iduser, iddep, name, hidden, lastlog) VALUES ('+inttostr(pracownicyMSA[i].id)+', '+inttostr(0)+', '''+pracownicyMSA[i].name+''',false,'''+pracownicyMSA[i].lastlog+''') ON CONFLICT (iduser) DO UPDATE SET name = excluded.name, iddep = excluded.iddep, lastlog = excluded.lastlog;';
PQTrans.Active := False;
PQuery.sql.Text := zapPQ;
PQTrans.StartTransaction;
PQuery.ExecSQL;
PQTrans.Commit;
PQuery.Clear;
PQuery.Close;
end;
wyswietlpracownikow;
end;
procedure TCardRotate.ostatniimport;
var
i,ilewiersz:integer;
czekstr,zapPQ:string;
begin
zapPQ:='select count(*) as ilo,max(data) as lastrec from recorder.record';
PQuery.sql.Text := zapPQ;
PQDSource.DataSet := PQuery;
PQDSource.DataSet.Active := True;
PQDSource.DataSet.Refresh;
if (PQDSource.DataSet.FieldByName('ilo').Value>0) then
begin
writeln(PQDSource.DataSet.FieldByName('lastrec').Value);
end;
PQDSource.DataSet.Active:=false;
PQDSource.DataSet.Close;
PQuery.Clear;
PQuery.Close;
end;
procedure TCardRotate.wyswietlpracownikow;
var
i,ilewiersz:integer;
czekstr,zapPQ:string;
begin
writeln('Z PQ');
zapPQ:='select * from recorder.user order by name';
PQuery.sql.Text := zapPQ;
PQDSource.DataSet := PQuery;
PQDSource.DataSet.Active := True;
PQDSource.DataSet.Refresh;
ilewiersz := PQDSource.DataSet.RecordCount;
writeln(ilewiersz);
listaimportu.RowCount:=ilewiersz+1;
for i := 1 to ilewiersz do
begin
czekstr:='◻';
if (PQDSource.DataSet.FieldByName('hidden').Value=true) then czekstr:='🗹';
listaimportu.Cells[0,i]:=czekstr; //'🗹◻ '
listaimportu.Cells[1,i]:=PQDSource.DataSet.FieldByName('name').Value;
listaimportu.Cells[2,i]:=datetostr(PQDSource.DataSet.FieldByName('lastlog').Value);
listaimportu.Cells[3,i]:=inttostr(PQDSource.DataSet.FieldByName('iduser').Value);
listaimportu.Cells[4,i]:=booltostr(PQDSource.DataSet.FieldByName('hidden').Value);
PQDSource.DataSet.Next;
end;
PQDSource.DataSet.Active:=false;
PQDSource.DataSet.Close;
PQuery.Clear;
PQuery.Close;
end;
procedure TCardRotate.wskazplikMSAClick(Sender: TObject);
var
dbconstr:string;
begin
if podajplikbazy.Execute then
begin
dbconstr:='DBQ='+podajplikbazy.FileName;
MSAodbc.Params.Add(dbconstr);
MSAodbc.Connected:=true;
importMSA.Enabled:=true;
end;
end;
procedure TCardRotate.listaimportuDblClick(Sender: TObject);
var
grid: TStringgrid absolute Sender;
wyb,gridcli: integer;
stan:boolean;
czekstr:string;
begin
grid := Sender as TStringgrid;
gridcli := grid.Row;
writeln(grid.Row,' ',grid.Col);
wyb:=strtoint(listaimportu.Cells[3,gridcli]);
stan:=strtobool(listaimportu.Cells[4,gridcli]);
zmienstan(wyb,stan);
end;
procedure TCardRotate.listaimportuClick(Sender: TObject);
var
grid: TStringgrid absolute Sender;
begin
grid := Sender as TStringgrid;
writeln(grid.Row,' ',grid.Col);
end;
procedure TCardRotate.Button1Click(Sender: TObject);
var
zapMSA,zapPQ: string;
strtemp:string;
i, ilewiersz: integer;
khem,id,cid,oid,dim,tim:string;
begin
if podajplikbazy.FileName <> '' then
begin
MSATrans.Active:=true;
zapMSA := 'select top 40 EventType,EmployeeID,CtrlID,Ordinal,AriseTime,format(arisetime,''short date'') as sdata,format(arisetime,''long time'') as stime from EventRecord where EventType=1 order by AriseTime';
MSAQuery.SQL.Text := zapMSA;
MSADSource.DataSet := MSAQuery;
MSADSource.DataSet.Active := True;
MSADSource.DataSet.Refresh;
ilewiersz := MSADSource.DataSet.RecordCount;
writeln('dorekordu ', ilewiersz);
for i := 0 to ilewiersz-1 do
begin
id:=inttostr(MSADSource.DataSet.FieldByName('EmployeeID').Value);
cid:=inttostr(MSADSource.DataSet.FieldByName('CtrlID').Value);
oid:=inttostr(MSADSource.DataSet.FieldByName('Ordinal').Value);
tim:=FormatDateTime('hh:nn:ss',MSADSource.DataSet.FieldByName('stime').Value);
dim:=datetimetostr(MSADSource.DataSet.FieldByName('sdata').Value);
khem:=inttostr(DateTimeToFileDate (MSADSource.DataSet.FieldByName('AriseTime').Value));
strtemp:=khem+id+cid+oid;
writeln('TS: ',khem,' D: ',dim,' T: ',tim,' ID U: ',id,' C ID: ',cid,' Ord: ',oid,' zlep: ',strtemp,' = ',EncodeStringBase64(strtemp));
MSADSource.DataSet.Next;
end;
MSAQuery.Clear;
MSAQuery.Close;
MSADSource.DataSet.Active:=false;
MSADSource.DataSet.Close;
{
for i := 0 to ilewiersz do
begin
zapPQ:='INSERT INTO recorder.user (iduser, iddep, name, hidden, lastlog) VALUES ('+inttostr(pracownicyMSA[i].id)+', '+inttostr(0)+', '''+pracownicyMSA[i].name+''',false,'''+pracownicyMSA[i].lastlog+''') ON CONFLICT (iduser) DO UPDATE SET name = excluded.name, iddep = excluded.iddep, lastlog = excluded.lastlog;';
PQTrans.Active := False;
PQuery.sql.Text := zapPQ;
PQTrans.StartTransaction;
PQuery.ExecSQL;
PQTrans.Commit;
PQuery.Clear;
PQuery.Close;
end;
wyswietlpracownikow;
}
end
else showmessage('Nie wskazano pliku bazy do importu !!');
end;
procedure TCardRotate.zmienstan(id:integer;stan:boolean);
var
zapPQ:string;
sortCol:integer;
sortOrder:TSortOrder;
begin
zapPQ:='UPDATE recorder.user SET hidden='+booltostr(not(stan),true)+' WHERE iduser='+inttostr(id);
PQTrans.Active := False;
PQuery.sql.Text := zapPQ;
PQTrans.StartTransaction;
PQuery.ExecSQL;
PQTrans.Commit;
PQuery.Clear;
PQuery.Close;
sortCol:=listaimportu.SortColumn;
if (sortCol=-1) then sortCol:=1;
sortOrder:=listaimportu.SortOrder;
writeln('s: ',sortCol,' t: ',sortOrder);
wyswietlpracownikow;
listaimportu.SortColRow(true,sortCol);
listaimportu.SortOrder:=sortOrder;
end;
function iledniwm(miesiac, rok: integer): integer;
begin
if ((miesiac = 4) or (miesiac = 6) or (miesiac = 9) or (miesiac = 11)) then
begin
iledniwm := 30;
end
else if (miesiac = 2) then
begin
if (((rok mod 4) = 0) and ((rok mod 100) <> 0)) or ((rok mod 400) = 0) then
begin
iledniwm := 29;
end
else
begin
iledniwm := 28;
end;
end
else
begin
iledniwm := 31;
end;
end;
procedure TCardRotate.ENDAPPClick(Sender: TObject);
begin
MSAodbc.Connected:=false;
PQCon.Connected := false;
Application.Terminate; // -------------------------------exit
end;
function DecodeStringBase64(const s:string):String;
var instream,outstream : TStringStream;
decoder : TBase64DecodingStream;
begin
instream:=TStringStream.Create(s);
try
outstream:=TStringStream.Create('');
try
decoder:=TBase64DecodingStream.create(instream,bdmmime);
try
outstream.copyfrom(decoder,decoder.size);
outstream.position:=0;
result:=outstream.readstring(outstream.size);
finally
decoder.free;
end;
finally
outstream.free;
end;
finally
instream.free;
end;
end;
function EncodeStringBase64(const s:string):String;
var outstream : TStringStream;
encoder : TBase64EncodingStream;
begin
outstream:=TStringStream.Create('');
try
encoder:=TBase64EncodingStream.create(outstream);
try
encoder.write(s[1],length(s));
finally
encoder.free;
end;
outstream.position:=0;
result:=outstream.readstring(outstream.size);
finally
outstream.free;
end;
end;
end.
Szukał bym błędu w dostępie do tablicy pracownicyMSA
.
Nie podoba mi się ten fragment kodu, dlaczego rozmiar tablicy jest większy o jeden od liczby wierszy w datasetcie ?
SetLength(pracownicyMSA,ilewiersz+1);
writeln('dorekordu');
for i := 0 to ilewiersz do
begin
writeln('rek',i);
pracownicyMSA[i].name:=CP1250ToUtf8(MSADSource.DataSet.FieldByName('name').Value);
pracownicyMSA[i].id:=MSADSource.DataSet.FieldByName('id').Value;
pracownicyMSA[i].lastlog:=FormatDateTime('YYYY-MM-DD',MSADSource.DataSet.FieldByName('lastlog').Value);
MSADSource.DataSet.Next;
end;
EDT1
masz kilka akcji podpięte pod buttony, napisz która z nich generuje wyjątek
EDT2
podobnie jest ze stringgridem listaimportu
, którego liczba wierszy też jest większa o jeden od liczby wierszy w datasecie
- Ponieważ ilość wierszy jest liczone od 0 a SetLength od 1 (zdaje się ) , ale
2.Tak jak zauważyłeś większość zdarzeń dzieje się po interakcji z użytkownikiem - a wyjątek występuje zanim zobaczę wygenerowane okno apki (widać tylko pustą konsole)
Ad. 1
Nie widzisz że liczba iteacji pętli for jest większa o jeden od liczby wierszy w datasecie ?
Zrób tak :
SetLength(pracownicyMSA,ilewiersz);
writeln('dorekordu');
for i := 0 to high(pracownicyMSA) do
begin
writeln('rek',i);
pracownicyMSA[i].name:=CP1250ToUtf8(MSADSource.DataSet.FieldByName('name').Value);
pracownicyMSA[i].id:=MSADSource.DataSet.FieldByName('id').Value;
pracownicyMSA[i].lastlog:=FormatDateTime('YYYY-MM-DD',MSADSource.DataSet.FieldByName('lastlog').Value);
MSADSource.DataSet.Next;
end;
Ogólnie to masz bałagan w indeksowaniu zarówno tablic jak i wierszy w StringGridzie, raz wypełniasz od elementu o indeksie zero, innym razem od elementu o indeksie jeden
Ad.1. Właściwie efekt jest taki sam - ale jak wspomniałem - ta operacja jest wykonywana po interwencji użytkownika.
Ad.2. to co wyżej - listaimportu
ma wiersz z nagłówkami
Ponownie dodam : wyjątek występuje zanim zobaczę wygenerowane okno apki (widać tylko pustą konsole)
Dodatkowo jest taki oto kod na początku
procedure TCardRotate.FormShow(Sender: TObject);
begin
writeln('start');
Ja tego start nie widze
więc szukaj błędu w metodzie FormShow
procedure TCardRotate.FormShow(Sender: TObject);
begin
writeln('start');
moment, to masz apkę konsolową czy okienkową ?
nie wiem jak jest w LAzarusie, ale pod Delphi zwykłe konsolowe writeln() w standardowej apce okienkowej generuje błąd I/O
Na początek umiesc na formie komponent Tmemo i pozamieniaj
writeln('cośtam');
na
memo1.lines.add('cośtam');
wyjątek występuje zanim zobaczę wygenerowane okno apki (widać tylko pustą konsole)
masz puste okno ponieważ FormShow wywala się na writeln() i dalsza część kodu metody nie wykonuje się
To apka stricte okienkowa - tak jak pisałem - wyłączyłem opcje "Aplikacja graficzna win32" by widzieć konsole.
Z dodatkowych ciekawostek które dopiero przed chwilą zauważyłem. Jeśli nie zmienię kodu a użyje F9 , IDE sprawdza kod ale nie kompiluje nowego EXE, mimo to potrafi się wywalić, więc problem leży po stronie IDE i(lub) Win 10 oraz praw (?) do uruchomienia apki - samą apke mogę uruchamiać bezpośrednio n-razy i nic - działa.
grzegorz_so napisał(a):
moment, to masz apkę konsolową czy okienkową ?
nie wiem jak jest w LAzarusie, ale pod Delphi zwykłe konsolowe writeln() w standardowej apce okienkowej generuje błąd I/O
A w Lazarusie mamy taki ficzer, że jeśli stworzymy dowolną aplikację okienkową, ale w ustawieniach projektu odznaczy się opcję Win32 gui application (przełącznik -WG
), to aplikacja nadal będzie okienkowa, ale przy rozruchu dodatkowo zostanie otwarta konsola.
Dzięki temu można bez problemu sprawdzać wykonanie programu okienkowego, i jednocześnie móc za pomocą WriteLn
wyświetlać w konsoli dane przydatne podczas testów manualnych. Bardzo fajna sprawa. ;)
@titako: jeśli o Twój problem chodzi, to uruchom ten program pod debuggerem (włącz w ustawieniach projektu opcję Generate debugging info for GDB i wyłącz optymalizacje), a następnie sprawdź w którym miejscu leci wyjątek.
A w Lazarusie mamy taki ficzer, że jeśli stworzymy dowolną aplikację okienkową, ale w ustawieniach projektu odznaczy się opcję Win32 gui application (przełącznik -WG), to aplikacja nadal będzie >okienkowa, ale przy rozruchu dodatkowo zostanie otwarta konsola.
W delphi tez tak można, ale standardowa okienkowa apka nie ma takiej właściwości.
Aby ją uzyskać należy w kodzie głównym projektu dodać
{$APPTYPE CONSOLE}
@furious programming - postąpiłem według twoich wskazówek - jak na razie się nie wywala :) - optymalizacja była na poziomie 3
Sam nigdy nie używam innych optymalizacji niż tej podstawowej (przyjaznej debuggerowi – -O1
). Być może problem leży właśnie w procesie optymalizacji i generowany jest wadliwy kod – trzeba by potestować, sprawdzić co się tam dzieje (dość czasochłonne).
A masz jakiś sprawdzony preset np. na wersje produkcyjną kompilacji - co by plik zbyt duży nie był ?
Przeważnie pisze małe apki "pomocnicze" i fajnie by było, by mała "funkcjonalnie" apka miała mniej niż 15MB .
To że (nie)optymalizacja pomaga oznacza tyle że błąd masz, tylko nie wiadomo gdzie.
Dodałbym tracing skoro to nieduża apka (w każdej funkcji logowanie na wejściu i na wyjściu).
Dzięki temu będziesz wiedział w której funkcji program się wywala.
Namierzenie wadliwej linijki będzie wtedy dziecinnie proste.
No to spróbowałem tak jak napisał @vpiotr - efekt jest mizerny - troszkę zmodyfikowałem procedure by tracker miał co robić zanim użyje WriteLn - jak widać na obrazku błąd wyskakuje zanim cokolwiek ruszy :/
Dlatego wnioskuje iż to chyba jakieś zgrzyty między IDE a W10
Chyba warto przeczytać ten wątek:
https://forum.lazarus.freepascal.org/index.php?topic=6225.0
(tam jeden z problemów to był Comodo Firewall, tylko to stary wątek)
titako napisał(a):
Przeważnie pisze małe apki "pomocnicze" i fajnie by było, by mała "funkcjonalnie" apka miała mniej niż 15MB .
Aplikacje zajmują dużo (np. 17MB), dlatego że 90% objętości stanowią symbole debuggera. Wersja produkcyjna nie powinna zawierać informacji dotyczących debugowania (ani symboli, ani kodu).
W oknie ustawień projektu odznacz opcję generowania danych dla debuggera – rozmiar apki spadnie do 2MB. Symbole te można też usunąć za pomocą strip.exe
, automatycznie lub całkowicie ręcznie. Po drugie, usuń z kodu wszystkie niepotrzebne rzeczy – fragmenty służące do testowania, niepotrzebne moduły, stałe, zmienne, funkcje, klasy itd. Możesz też użyć agresywnej optymalizacji, mającej na celu zmniejszyć objętość kodu wynikowego.
Dodatkowo, jeśli baaardzo zależy Ci na małych plikach wynikowych, możesz pousuwać zbędne zasoby za pomocą edytora zasobów, a także skompresować plik wykonywalny np. za pomocą UPX. Ten dość sporo potrafi ścisnąć (opcja -9
). Ale ma też wady, również związane z nadgorliwością antywirusów, z czym musisz się liczyć.
A masz jakiś sprawdzony preset np. na wersje produkcyjną kompilacji - co by plik zbyt duży nie był ?
Oprócz tego co napisałem wyżej – choć nie wszystkiego używam – nie.
Średniej wielkości aplikacja okienkowa (~50kLoC) w wersji produkcyjnej zajmuje nieco ponad 2MB (debug nieco ponad 23MB), więc nie ma sensu jej zmniejszać. Dwa megabajty dziś to tyle co nic. ;)