DLL, interfejs, FreeLibrary i AccessViolation - potrzebna pomoc

DLL, interfejs, FreeLibrary i AccessViolation - potrzebna pomoc
JU
  • Rejestracja:około 22 lata
  • Ostatnio:2 miesiące
  • Postów:5042
0

Cześć, siedzę nad tym już drugi dzień i nic. Środowisko XE2 Update 4
Generalnie zasada jest taka - mam interfejs. Klasa w DLL implementuje ten interfejs, a potem zwracam ten obiekt do aplikacji głównej.
Na koniec zwalniam bibliotekę DLL.

Może to trochę chaotycznie brzmi, więc trochę kodu. Jak jest interfejs skonstruowany, to chyba nie ma znaczenia.
Generalnie w DLL mam klasę:

Kopiuj
TPlugin = class(TInterfacedObject, IPlugInterface)
//deklaracje metod
end;

W dll mam też jedną funkcję, którą eksportuję:

Kopiuj
function CoCreatePlugin: IPlugInterface; stdcall;
begin
  Result := TPlugin.Create;
end;

(zasada działania pochodzi z jakiegoś artykułu w necie, który czytałem dawno temu)

Teraz w aplikacji głównej robię coś takiego:
(plugin to zmienna typu TPlugin, który jest rekordem i wygląda mniej więcej tak:

Kopiuj
type
  TPlugin = record
    Active: boolean; //czy używać, czy nie
    {$IF DEFINED(SERVICE) or DEFINED(TEST_APP)} //nie sądzę, żeby dyrektywy miały wpływ na błąd, ale są tutaj
    Handle: THandle;
    IFace: IPlugInterface;
    {$IFEND}
end;

I teraz ładuję bibliotekę i wywołuję CoCreatePlugin:

Kopiuj
Plugin.Handle:=LoadLibrary('plik.dll');

if Plugin.Handle <> 0 then
begin
  @CreatePluginProc:=GetProcAddress(Plugin.Handle, 'CoCreatePlugin');
  if @CreatePluginProc = nil then //generalnie wtedy wywalam błąd

  Plugin.IFace:=CreatePluginProc;
  @CreatePluginProc:=nil; //zawsze tutaj jest wszystko ok

I od tego momentu mogę wywoływać wszystkie metody z dll poprzez: Plugin.IFace.Metoda();
Wszystkie metody w interfejsie są oznaczone jako safecall.

Problem pojawia się podczas zwalniania biblioteki. O dziwo, wcześniej to działało.

Kopiuj
res:=FPlugins[PluginIndex].IFace.Disconnect; 
if res<>S_OK then exit;

FreeLibrary(FPlugins[PluginIndex].Handle);
FPlugins[PluginIndex].Handle:=0;
FPlugins[PluginIndex].IFace:=nil;

I teraz tak. Błąd powstaje zawsze w ostatniej linijce, tam gdzie jest przyrównanie do NIL.

Project Project1.exe raised exception class $C0000005 with message 'access violation at 0x0040ba2e: read of address 0x04dcad8c'.

Przy czym adres: 0x04dcad8c to adres, pod którym znajduje się FPlugins[PluginIndex].IFace. (czyli sam obiekt)
Jeśli natomiast nie ma tego przyrównania do nil, to access violation pokazuje się jakby w losowych miejscach. Np. jak kliknę gdziekolwiek poza okno programu. Albo po pewnym czasie(w programie nie ma żadnego działania).

Project Project1.exe raised exception class $C0000005 with message 'access violation at 0x02770fef: read of address 0x02770fef

Adres 0x02770fef MOŻE BYĆ wskaźnikiem na tamten obiekt, tzn: @FPlugins[PluginIndex].IFace
Czasami jednak wskazuje na adresy 0x00000000. Przy czym zawsze jest class $C0000005

I ja już nie wiem, co robić. Czy potrzebujecie jeszcze jakiejś informacji? Czy coś jest niepokojącego w tym kodzie?
Zaznaczam, że jeśli nie mam wywołania FreeLibrary, to nie ma też Access Violation.
Proszę o pomoc.

edytowany 1x, ostatnio: flowCRANE
abrakadaber
abrakadaber
  • Rejestracja:ponad 12 lat
  • Ostatnio:8 miesięcy
  • Postów:6610
2

najpierw przypisz nil a potem zwolnij dll


Chcesz pomocy - pokaż kod - abrakadabra źle działa z techniką.
JU
Tak robiłem na początku. Było to samo.
Azarien
  • Rejestracja:ponad 21 lat
  • Ostatnio:około 3 godziny
0

Tak robiłem na początku. Było to samo.
Teraz jest jeszcze gorzej. Przypisanie IFace:=nil wywołuje metodę _Release interfejsu, więc w tym momencie biblioteka NIE MOŻE być zwolniona.

Być może trzymasz gdzieś jeszcze kopię IFace w jakiejś zmiennej. Musisz się pozbyć wszystkich referencji do interfejsu, żebyś mógł wyrzucić bibliotekę.

edytowany 2x, ostatnio: Azarien
JU
  • Rejestracja:około 22 lata
  • Ostatnio:2 miesiące
  • Postów:5042
0

No właśnie nigdzie indziej nie ma. Jest tylko przypisanie na początku - wywołaniem funkcji "CreatePluginProc" i tyle. Potem tylko w kilku miejscach sprawdzam, czy ten interfejs jest nil:

Kopiuj
if Plugin.IFace <> nil then...
0

if @CreatePluginProc = nil then
Tak się nie sprawdza pointerów. assigned

Plugin.IFace:=CreatePluginProc;
Plugin.IFace:=CreatePluginProc();. Ale to kosmetyka.

@CreatePluginProc:=nil;
Już nie będę się pastwił nad niezrozumiałą dla mnie składnią Delphi, ale na cholerę to nilujesz? Tego nigdzie w pamięci się nie trzyma, to powinna być zmienna lokalna procedury "LoadMyDllLib" etc.

FPlugins[PluginIndex].IFace:=nil;
Zrozum, że najpierw niszczysz interfejs, potem zwalniasz bibliotekę. Z tego co wiem to inaczej niszczy się interfejsy niż tak, ale lecę z pamięci a interfejsów z dwa razy w swojej karierze programistycznej użyłem.

FPlugins[PluginIndex].Handle:=0;
A to na cholerę? Gdy biblioteka jest zwalniana to jej miejsce w tablicy powinno być usuwane poprzez skrócenie tablicy.

Czy coś jest niepokojącego w tym kodzie?

Tak, to jak piszesz kod jest niepokojące.

Teraz jest jeszcze gorzej. Przypisanie IFace:=nil wywołuje metodę _Release interfejsu, więc w tym momencie biblioteka NIE MOŻE być zwolniona.

Cóż, biblioteka powinna być zwolniona po tym gdy interfejs się zamknie. Sekwencyjność kodu powinna tak zrobić.

No właśnie nigdzie indziej nie ma. Jest tylko przypisanie na początku - wywołaniem funkcji "CreatePluginProc" i tyle. Potem tylko w kilku miejscach sprawdzam, czy ten interfejs jest nil:

Co wy macie z tym nil?! jest assigned.

Sprawdzaj czy interfejs został zwolniony zanim wywołasz FreeLibrary. Jak nie to assertuj czy raisuj.

olesio
Z tego co wiem, to Assigned sprawdza czy parametr jej przekazany nie jest równy nil. Wynika to nawet z: Assigned - patrz też przykład kodu, w tym artykule.
Azarien
@3g5iue: skoro składnia jest dla ciebie niezrozumiała, to może się powstrzymaj od wypowiedzi, bo mieszasz prawdę z fałszem i zamęt wprowadzasz.
0

Zdecyduj się TPlugin to albo rekord albo klasa nie może robić za to i to !

babubabu
Chyba nie zrozumiałeś co przeczytałeś.
0

@Juhas

Kopiuj
Plugin.Handle:=LoadLibrary('plik.dll'); 
if Plugin.Handle <> 0 then
begin
  @CreatePluginProc:=GetProcAddress(Plugin.Handle, 'CoCreatePlugin');
  if @CreatePluginProc = nil then //generalnie wtedy wywalam błąd
 
  Plugin.IFace:=CreatePluginProc;
  @CreatePluginProc:=nil; //zawsze tutaj jest wszystko ok

CreatePluginProc powinno być zmienną lokalną i nie potrzebne jest @CreatePluginProc:=nil;

Co do twojego błędu, to może być on spowodowany tym że, któryś wątek wykorzystuje (próbuje) już nie istniejący interfejs.
Powinieneś mieć jakąś funkcję, która zwalnia pamięć twojej DLL'ki (zakładam że Disconnect tego nie robi), choć tą kwestie powinno rozwiązywać FreeLibrary
Debuger wywala cię tam bo to ostatnia linijka z kodu, który dokonuje błędu na prawidłowo pracującej metodzie. Spróbuj zakomentować

Kopiuj
FreeLibrary(FPlugins[PluginIndex].Handle);
FPlugins[PluginIndex].Handle:=0;
FPlugins[PluginIndex].IFace:=nil;

I zobacz czy wywala błąd.

Nie podoba mi się zapis

Kopiuj
res:=FPlugins[PluginIndex].IFace.Disconnect; 
if res<>S_OK then exit;

raczej tak wygląda lepiej i nie alokujesz niepotrzebnie zmiennej

Kopiuj
 FPlugins[PluginIndex].IFace.Disconnect <> S_OK then exit;

@3g5iue
Masz racje, w kodzie jest bajzel xD

Kopiuj
if @CreatePluginProc = nil then

Tak też można i nie jest to błędne

Zrozum, że najpierw niszczysz interfejs, potem zwalniasz bibliotekę. Z tego co wiem to inaczej niszczy się interfejsy niż tak, ale lecę z pamięci a interfejsów z dwa razy w swojej karierze programistycznej użyłem.

Kopiuj
FPlugins[PluginIndex].Handle:=0;

A to na cholerę? Gdy biblioteka jest zwalniana to jej miejsce w tablicy powinno być usuwane poprzez skrócenie tablicy.

O ile się nie mylę to nie ma czegoś takiego jak usuwanie elementu z tablicy, tylko zmniejszasz jej rozmiar i przesuwasz górę o -1, cały ten proces trwa długo (w zależności od rozmiarów tablicy) ponadto łatwiej jest oznaczyć handle zerem i wykorzystać już istniejący index przy ładowaniu następnej biblioteki (może to jednak tworzyć problem z zwalnianiem pamięci)

@babubabu

Chyba nie zrozumiałeś co przeczytałeś

Zrozumiałem co przeczytałem!
Tak czy srak musisz zamieścić strukturę interfejsu w głównej aplikacji (nazwana TPlugin) a dodatkowo deklarujesz że typ TPlugin jest rekordem.

Ale my sobie możemy gdybać jak to wygląda skoro nie masz całego kodu przed sobą tylko skrawki i to nie kompletne :D

crowa
  • Rejestracja:ponad 18 lat
  • Ostatnio:około 8 lat
  • Lokalizacja:Poznań
  • Postów:295
0

to ja jeszcze cos dorzuce od siebie.

  1. Tworzysz dll (bez zadnego tam sharemem i innych badziewi). Mam nadzieje ze dll dziedziczy poTInterfacedObject i implementuje dodatkowo Twoj interface
Kopiuj
   IMyInterface = interface(IUnknown) // to dla c#
   [tutaj GUID = ctrl + shift + g]
     procedure MyMethod(var AValue: OleVariant); safecall;
   end;

a w dll

Kopiuj
  TMyObject = class(TInterfacedObject, IMyInterface)
    procedure MyMethod(var AValue: OleVariant); safecall; 
  end;
  1. Deklarujesz metode eksportujaca interface jesli z dll okreslasz konwencje wolania metody
Kopiuj
  procedure CreateObject(var AObject: IMyInterface); safecall;
  begin
     // do your stuff here
  end;

  exports
    CreateObject;  // lub CreateObject name 'XYZ'
  1. W hoscie wywolujacym dll deklarujesz typ woladnej metody
Kopiuj
type
  TCreateObejct = procedure(var AObject: IMyInterface); safecall;
  1. Okreslasz strukture w ktorej trzymasz informacje o dll, nie jest prawda ze powinna to byc zmienna lokalna np jesli dll exportuje formularz ktory pozniej przez winapi gdzies dokujesz np na pagecontrol to mozesz miec kilka obiktow utworzonych z dll btw kazdy obiekt takiego formularza moze miec rozne stany view, edit, new
    Ja tak mam to zrobione u siebie - u mnie nie tylko dll z delphi sa obslugiwane tak np z c# tez
    Zalecalbyc rekord do trzymania informacji + record helper (masz w koncu xe2)
Kopiuj
type
  TPluginInformation = record
    Plugin: HMODULE;
    CreateObject: TCreateObjct;
    MyInterface: IMyInterface;
  end;
  1. W metodzie w ktorej tworzysz obiekt z dll zakladam ze rekord juz jest gdzies utworzony (zalozmy ze sie nazywa Info)
Kopiuj
procedure ObjectFromDll(Sender: TObject);
begin
  Info.Plugin := SafeLoadLibrary('C:\ala ma kota\My.dll');
 
 if Info.Plugin = 0 then
    raise Exception.Create('blad ladowania dll');

  Info.CreateObject := GetProccAdress(Info.Plugin, 'CreateObject'); // lub XYZ w zaleznosci od sekcji export z dll

  if not Assigned(Info.CreateObject)
     raise Exception.Create('boom');   

  Info.CreateObject(MyInterface);
  MyInterface.MyMethod(.......);

  Info.MyInterface := nil;
  FreeLibrary(Info.Plugin);
end;

I to wszystko. Pamietaj tylko zeby przekazywac do interface przez OleVariant;


Tomasz Andrzejewski
Delphi (XE3-XE7) framework engineer @ InterLan
MCP: Microsoft SQL Server 2008, Implementation and Maintenance
edytowany 1x, ostatnio: flowCRANE
JU
Wszystko mam właśnie w taki sposób i niestety ten AV ciągle wywala. Najgorzej, że nie można go wychwycić.
0

Tak też można i nie jest to błędne

Błędne czy nie, to sprawa dyskusyjna. Natomiast ja powiedziałem że używa się assigned

O ile się nie mylę to nie ma czegoś takiego jak usuwanie elementu z tablicy, tylko zmniejszasz jej rozmiar i przesuwasz górę o -1

No tak, nie ma czegoś takiego jak pisanie, tylko jest przyłożenie ręki do kartki i naciskanie na nią odpowiednio długopisem.

cały ten proces trwa długo

Niech zgadnę. Masz Pentium II 100mhz z Windowsem 95, tak?
Cóż, jeżeli ty swoje programy piszesz po to żeby tak często wykonywana i krytyczna funkcja jak zwalnianie biblioteki co zapewne się robi tylko przy wychodzeniu była maksymalnie optymalna, to dobrze że tych funkcji które są mało istotne jak procedury zajmujące najwięcej czasu nie optymalizujesz. Możesz czas wykonywania 90% programu zmniejszyć do 0 i uzyskać 10% przyrostu prędkości albo zmniejszyć czas wykonywania tych ważnych 10% do zera i uzyskać 90% przyrostu prędkości. Widzę że jesteś za pierwszą opcją.

ponadto łatwiej jest oznaczyć handle zerem i wykorzystać już istniejący index przy ładowaniu następnej biblioteki

No właśnie nie łatwiej jest się bawić w szukanie, tylko usuwać przy wywalaniu i dodawać przy dodawaniu. Proste...

(może to jednak tworzyć problem z zwalnianiem pamięci)

Nie wiem jakim cudem, skoro o zwalnianie tablic dba kompilator.

BTW. @OMG, masz doublepost bo jakiś (l)admin niechlujnie usunął mój post. Nice try, monk.

Z tego co wiem, to Assigned sprawdza czy parametr jej przekazany nie jest równy nil. Wynika to nawet z: Assigned - patrz też przykład kodu, w tym artykule. - olesio wczoraj, 19:58
@3g5iue: skoro składnia jest dla ciebie niezrozumiała, to może się powstrzymaj od wypowiedzi, bo mieszasz prawdę z fałszem i zamęt wprowadzasz. - Azarien wczoraj, 21:35

Cóż, raz już na to odpowiedziałem, drugi raz nie będę.

crowa
  • Rejestracja:ponad 18 lat
  • Ostatnio:około 8 lat
  • Lokalizacja:Poznań
  • Postów:295
0

btw lista uchwytow do dll?
ok pod warunkiem ze to sa inne dll bo jesli ta sama to bedzie ten sam handle (jesli wczesniej wczytano juz dll do pamieci).


Tomasz Andrzejewski
Delphi (XE3-XE7) framework engineer @ InterLan
MCP: Microsoft SQL Server 2008, Implementation and Maintenance
Azarien
  • Rejestracja:ponad 21 lat
  • Ostatnio:około 3 godziny
2

Błędne czy nie, to sprawa dyskusyjna. Natomiast ja powiedziałem że używa się assigned

ten twój ulubiony Assigned() robi tylko tyle co

Kopiuj
function Assigned(p:pointer):boolean;
begin
  Result := p<>nil
end;

i choć użycie tej funkcji może być zalecane, to naprawdę, są lepsze tematy do zaczepienia flejma.

edytowany 2x, ostatnio: Azarien
JU
  • Rejestracja:około 22 lata
  • Ostatnio:2 miesiące
  • Postów:5042
0

Zauważyłem, że błąd AV powstaje jedynie w środowisku. Natomiast po odpaleniu execa, błędu nie ma. Co o tym myślicie?

Azarien
Że masz błąd.
flowCRANE
Moderator Delphi/Pascal
  • Rejestracja:ponad 13 lat
  • Ostatnio:około godziny
  • Lokalizacja:Tuchów
  • Postów:12171
0

@Juhas - taka sytuacja może zaistnieć np. wtedy, gdy instrukcja powodująca wyjątek znajduje się wewnątrz bloku try .. except, który go nie obsługuje (tzn. po wystąpieniu wyjątku nie informujesz użytkownika o zaistniałej sytuacji); Jeśli program kompilujesz i uruchamiasz przez IDE to wtedy debuger wychwytuje wyjątek (pod warunkiem, że mu tego nie zabronisz w ustawieniach projektu), a jeśli aplikację uruchamiasz z dysku (bez debugera) to nie dostajesz żadnej informacji; Inna sytuacja nie przychodzi mi do głowy.


Pracuję nad własną, arcade'ową, docelowo komercyjną grą z gatunku action/adventure w stylu retro (pixel art), programując silnik i powłokę gry od zupełnych podstaw, przy użyciu Free Pascala i SDL3. Więcej informacji znajdziesz na moim mikroblogu.
edytowany 1x, ostatnio: flowCRANE
JU
Jakiś czas temu przesiadałem się na nowe środowisko i możliwe, chociaż mało prawdopodobne, że nie włączyłem obsługi try..except z poziomu środowiska. Muszę to sprawdzić. A jest możliwość, że to jakiś dziwny błąd środowiska?
flowCRANE
Środowiska raczej nie, prędzej RTL/VCL, choć i tak podejrzewam błąd w Twoim kodzie;
JU
sprawdzone. Notify on language exceptions mam wyłączone, czyli bloki try..except powinny działać w środowisku
JU
uparłem się i powstawiałem breakpointy we wszystkich blokach try..except w całej aplikacji. Nic nie złapałem
flowCRANE
Sprawdź pod debugerem w którym momencie powstaje wyjątek i sprawdź w Watches jak wyglądają zmienne/obiekty - być może to wspomniany RTL/VCL ma bugi i raczej z tym nic nie zrobisz;

Zarejestruj się i dołącz do największej społeczności programistów w Polsce.

Otrzymaj wsparcie, dziel się wiedzą i rozwijaj swoje umiejętności z najlepszymi.