Czy taka koncepcja jest dobra na pluginy bez względu na język?

0

Cześć.

Ostatnio był wątek o liście jednokierunkowej w dllce jako pluginie. U mnie coś prostszego. Rozważam scalenie swoich programów do telewizji online pod VLC w jeden i każdą platformę chciałbym obsłużyć jako plugin dla programu głownego w bibliotece dll. Ale docelowo może w przyszlosci ktoś by chciał późnej rozwijać własne pluginy w innym języku niż Delphi.

Poza tym dodatkowo pomyślałem sobie, że fajnie by było ułatwić takie operacje jak obsługę metod GET i POST HTTP, parsowanie JSON'a czy inne potrzebne żeby można było w dllce umieszczać jak najmniej kodu i najbardziej potrzebnego nawet w czystym WinAPI, posiłkując się tym co dostarcze do dyspozycji.

Tak chyba działają mniej więcej pluginy do zapomnianego USDownloadera albo AQQ. Wypełniany jest rekord w funkcji inicjującej i mamy dostep do metod, które wywołujemy. Poniżej przykład prostego kodu. Na oko działa jak chciałem, ale czy to na pewno zadziała i da się implementować w innych językach programowania?

I drugie pytanie. Jak radzicie zwracać listę kanałów TV z ich danymi typu nazwa link RTMP, link do grafiki loga i ewentualne inne dane potrzebne rtmpdump albo samemu VLC? Czy zrobić coś jak funkcja EnumProcessModules W WinAPI, że user podaje zerowy element tablicy jako zmienną, do tego rozmiar tablicy i zmienną CbNeeded żeby wiedzieć ile elementów jest potrzebne? Wtedy można sobie już z poziomu mojego kodu w aplikacji głownej enumerować tę tablicę. Czy bezpieczniej i optymalniej, czy tam w ogóle lepiej zrobić funkcję enumerującą jak przy Enum(Child)Windows na przykład w parametrze przekazywać typ rekordowy, oczywiście wszelkie dane tekstowe jako na przykład PAnsiChary.

Bo nie zakładam potrzeby Unicode. I wtedy po prostu user otrzymuje do dyspozycji funkcje, która czyści ListBox z listą kanałów do wyboru i dodaje do niego element. Minus, że trzeba by ją wywoływać wielokrotnie. W sumie tablicę też trzeba wypełnić w pętli i sprawdzać czy user nie podał za małej niż wymagana. Jakie macie pomysły, sugestie. Proszę o podzielenie się. Ważne żeby to było uniwersalne gdy ktoś napisze plugin nie w Delphi. Z góry dziękuję.

Przykładowy kod wywołujący plugin:

program test;

{$APPTYPE GUI}

uses
  Windows;

type
  TTestFunc = function(Text : PAnsiChar) : boolean; stdcall;
  TInitFunc = function(const TestFunc : Pointer) : boolean; stdcall;
  TLoadFunc = function : boolean; stdcall;
  TGetAbout = function : PAnsiChar; stdcall;

function MyTestFunc(Text : PAnsiChar) : integer; stdcall;
begin
  Result := MessageBoxA(0, Text, 'Test func', MB_OK);
end;

var
  DllH : THandle;
  InitFunc : TInitFunc; 
  LoadFunc : TLoadFunc;
  GetAbout : TGetAbout;
begin
  DllH := LoadLibrary('p1ugin.dll');
  if DllH > 0 then
  begin
    InitFunc := GetProcAddress(DllH, 'InitFunc');
    if @InitFunc <> nil then
    begin
      InitFunc(@MyTestFunc);
    end;
    LoadFunc := GetProcAddress(DllH, 'LoadFunc');
    if @LoadFunc <> nil then
    begin
      LoadFunc;
    end;
    GetAbout := GetProcAddress(DllH, 'GetAbout');
    if @GetAbout <> nil then
    begin
      MessageBox(GetForegroundWindow, GetAbout, 'About this plugin', MB_ICONINFORMATION + MB_OK);
    end;
    FreeLibrary(DllH);
  end;
end.

I przykładowy prosty plugin do testów mojej koncepcji:

library p1ugin;

uses
  Windows;

type
  TTestFunc = function(Text : PAnsiChar) : boolean; stdcall;

var
  Test : TTestFunc;

function InitFunc(const TestFunc : Pointer) : boolean; stdcall;
begin
  @Test := TestFunc;
  Result := True;
end;

function LoadFunc : boolean; stdcall;
begin
  Result := Test('Czy dziaua?');
end;

function GetAbout : PAnsiChar; stdcall;
begin
  Result := 'It is a 1st tests plugin by olesio';
end;

exports
  InitFunc,
  LoadFunc,
  GetAbout;

begin
end.
0

Radzę robić to jako osobną klasę.
Wtedy może działać nawet kilka plugin'ów na raz.
Wskaźniki na funkcje odpala się jako metody.

0

Aha, dzięki za odpowiedź. Czyli po stronie głownego kodu programu obsługującego pluginy mam tworzyć za każdym wywołaniem z dllki funkcji klasę? Bo pomyslałem, że jeśłi na przykład będzie u mnie metoda do przykładowo operacji po HTTP to robiąć to w wątku z WinAPI, a nie VCL, czyli na przykład poprzez CreateThread i tak może to wywoływać wiele pluginów na raz ze swojego kodu. Można przykład kodu. Bo trochę po nieprzespanej nocy się motam z wyobrażeniem. Jedna klasa jako zmienna globalna i to co wywoła plugin dllki ma być w jej poszczególnych metodąch? Ale konwencja przekazania do dllki na przykład TestFunc jest ok? Bo ile wiem na pewno nie można używać w dllce odwołań do klas jakichkolwiek z VCL, ponieważ w przypadku dllki nie napisanej w Delphi nie zadziała.

0

Jak radzicie zwracać listę kanałów TV z ich danymi typu nazwa link RTMP, link do grafiki loga i ewentualne inne dane
Czy zrobić coś jak funkcja EnumProcessModules W WinAPI, że user podaje zerowy element tablicy jako zmienną, do tego rozmiar tablicy i zmienną CbNeeded żeby wiedzieć ile elementów jest potrzebne?

Zdefiniuj rekord z danymi. Najlepiej przyjmij maksymalne rozmiary elementów (długości stringów) i niech to będą tablice statycznie w rekordzie.
Dowiedz się na przykład, jaka jest maksymalna długość nazwy kanału.

Zdefiniuj funkcję GetKanałCount ;-) zwracającą liczbę, a potem GetKanałInfo przyjmującą nr kanału i wskaźnik na rekord do wypełnienia. Albo wskaźnik na całą tablicę - w tym momencie obie strony wiedzą że tablica musi mieć rozmiar liczba_kanałów*sizeof(TKanał).

Raczej unikaj rozwiązania z Enum i cbNeeded, bo to hack. Ewentualnie zachowaj to tylko dla dodatkowych danych (dla konkretnego jednego kanału), których rozmiaru nie da się z góry ustalić.

0

@Azarien, dzięki. No właśnie piszesz, że cbneeded to czy enum hack. Ale ma to ten plus że nie musimy określać maksymalnej długości nazw kanałów. Adresy czy dodatkowe dane stringowe mogą być bardzo rózne, cięzko się nie pomylić by nie założyc za mało albo za dużo znaków. I w ogólę nie moge pojąc tego rozmiar * size. Skoro element tablicy jest typu rekordowego to zauważyłem, że pod Delphi jeśłi mamy funkcję WinAPI enumerującą HMODULE, to czy dam SetLength z 1024 czy 1024 * SizeOf(HMODULE) to i tak mi to działało "na oko" jak należy. Czy konieczność robienia elementy * rozmiar rekordu to wymóg w innych językach niż Delphi?

0

Ze niby nie umiesz klasy napisać, no nie żartuj:

  DllH : THandle; // Prywatna składowa
  InitFunc : TInitFunc;  // Prywatna składowa + odpowiednia właściwość readonly
  LoadFunc : TLoadFunc; // Prywatna składowa + odpowiednia właściwość readonly
  GetAbout : TGetAbout; // Prywatna składowa + odpowiednia właściwość readonly

  DllH := LoadLibrary('p1ugin.dll'); //konstruktor
  if DllH > 0 then //konstruktor
  begin
    InitFunc := GetProcAddress(DllH, 'InitFunc'); //konstruktor
    if @InitFunc <> nil then  //konstruktor jak źle to wyjątek lub składowa prywatna IsBad

    LoadFunc := GetProcAddress(DllH, 'LoadFunc'); //konstruktor
    if @LoadFunc <> nil then  //konstruktor jak źle to wyjątek lub składowa prywatna IsBad

    GetAbout := GetProcAddress(DllH, 'GetAbout'); //konstruktor
    if @GetAbout <> nil then  //konstruktor jak źle to wyjątek lub składowa prywatna IsBad

    FreeLibrary(DllH); //destruktor
0

Dragon wiem jak się pisze klasy i nie żartowałem sobie. Po prostu zbieram rady, a nie umiałem sobie wyobrazić za bardzo co i jak. Zanim siądę do właściwej pracy nad programem, szukam podpowiedzi doświadczonych żeby to tak zaprojektować. Bo wiadomo, że gdy już powstaną pluginy niewskazane jest później wszystko robić na nowo.

0

Opisuję pobieżnie.
Tworzysz sobie 2 interfejsy. Jeden dla aplikacji głównej, drugi dla pluginu.
Plugin (dll) musi posiadać określoną funkcję, np: "CreatePlugin".

 
function CreatePlugin(IApplication mainApp, var IPlugin plugin): HRESULT
begin
  plugin := TMyPlugin.Create();
  globalMainApp := mainApp;
  result:=S_OK;
end;

Dzięki takiemu rozwiązaniu masz dostęp do interfejsu aplikacji z poziomu pluginu (mainApp) i dostęp do pluginu z poziomu aplikacji (plugin), gdyż ta funkcja tworzy Ci obiekt pluginu i zwraca go do aplikacji. Oczywiście obiekt pluginu musi implementować interfejs pluginu ;)
Musisz mieć jeszcze jedną funkcję w dllce, która usunie obiekt pluginu, gdy już nie będzie potrzebny.

Myślę, że ogarniesz o co mi chodzi.

1

Opiszę jak to robi Winamp.

DLL-ka (plugin) eksportuje tylko jedną funkcję. Funkcja ta zwraca wskaźnik na strukturę. Struktura zawiera wskaźniki na pozostałe funkcje.
To działa i nie ma w tym żadnej magii.

W ten sposób otrzymujemy to co opisał @_13th_Dragon znacznie szybciej, bez mozolnego GetProcAddress dla każdej funkcji osobno.

0

A czemu nie zrobic tego w oparcji wlasnie o interface (jedyny shared unit miedzy aplikacja a dll to definicja interface)

Zakaldajac

a) exe (host) dla pluginow sam musi byc pluginem dla ladowanych dll (exe tez moze posiadac export ale lepiej robic to wstrzykujac instancje exe jako interface)
b) komunikacja dll <> exe obustronna

Pobierznie opisujac mamy

  • w exe unit z definicja interface
  • w exe class factory (interface) umozliwiacaje na podstawie GUID utworzenie klasy (dobrze sie sprawdza z bpl)
  • exe laduje biblioteke i pobiera jedna metode - funkcje (niech sie nazywa initialize) przekazuje do niej Application.Handle oraz siebie jako fabryke klas (niech interface sie nazywa IClassFactoryInterface)
  • funkcja initialize zwraca interface z dll

Proste i niezalezne od kompilatora - delphi, c++, c# (nie assembly ale native dll), java smiga bez problemow.

http://4programmers.net/Forum/Delphi_Pascal/158521-wykorzystanie_w_delphi_biblioteki_dll_uzywanej_w_c?p=1018534#id1018534

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