Jak zaprojektować program C ++ umożliwiający import funkcji w czasie wykonywania?

10

dzisiaj chciałbym zadać ci pytanie dotyczące możliwości C ++ do realizacji określonej architektury oprogramowania.

Oczywiście skorzystałem z wyszukiwania, ale nie znalazłem żadnej bezpośrednio powiązanej odpowiedzi.

Zasadniczo moim celem jest zbudowanie programu, który pozwala użytkownikowi modelować i symulować dowolnie złożone układy fizyczne, np. Samochód. Zakładam, że mam bibliotekę modeli fizycznych (funkcje w ramach klas). Każda funkcja może mieć pewne dane wejściowe i zwracać niektóre dane wyjściowe w zależności od opisu fizycznego, np. Model silnika spalinowego, model aerodynamiczny, model koła itp.

Teraz chodzi o to, aby zapewnić użytkownikowi strukturę, która pozwala mu komponować dowolne funkcje zgodnie z jego potrzebami, tj. Mapować wszelkie zachowania fizyczne. Ramy powinny zapewniać funkcje łączenia wyjść i wejść różnych funkcji. Dlatego środowisko udostępnia klasę kontenera. Nazywam to COMPONENT, który może pomieścić jeden lub wiele obiektów modelu (FUNKCJA). Kontenery te mogą również pomieścić inne komponenty (patrz wzór złożony), a także połączenia (ZŁĄCZE) między parametrami funkcji. Dodatkowo klasa komponentów zapewnia pewne ogólne funkcje numeryczne, takie jak solver matematyczny i tak dalej.

Składanie funkcji powinno odbywać się w czasie wykonywania. W pierwszej kolejności użytkownik powinien mieć możliwość skonfigurowania kompozycji poprzez zaimportowanie pliku XML, który definiuje strukturę kompozycji. Później można pomyśleć o dodaniu GUI.

Dla lepszego zrozumienia tutaj jest bardzo uproszczony przykład:

<COMPONENT name="Main">
  <COMPONENT name="A">
    <FUNCTION name="A1" path="lib/functionA1" />
  </COMPONENT>
  <COMPONENT name="B">
    <FUNCTION name="B1" path="lib/functionB1" />
    <FUNCTION name="B2" path="lib/functionB2" />
  </COMPONENT>
  <CONNECTIONS>
    <CONNECTOR source="A1" target="B1" />
    <CONNECTOR source="B1" target="B2" />
  </CONNECTIONS>        
</COMPONENT>

Nie jest konieczne zagłębianie się w możliwości frameworka, ponieważ mój problem jest znacznie bardziej ogólny. Podczas kompilowania kodu / programu frameworkowego opis problemu fizycznego, a także funkcje zdefiniowane przez użytkownika, nie są znane. Gdy użytkownik wybiera (za pomocą XML lub później za pomocą GUI) funkcję, środowisko powinno odczytać informacje o funkcji, tj. Powinno uzyskać informacje o parametrach wejściowych i wyjściowych, aby zaoferować użytkownikowi możliwość połączenia funkcji.

Znam zasady refleksji i jestem świadomy, że C ++ nie zapewnia tej funkcji. Jestem jednak pewien, że bardzo często wymagana jest koncepcja „budowania obiektów podczas działania”. Jak skonfigurować architekturę oprogramowania w C ++, aby osiągnąć cel? Czy C ++ jest właściwym językiem? Co przeoczyłem?

Z góry dziękuję!

Na zdrowie, Oliver

Oliver
źródło
C ++ ma wskaźniki funkcji i obiekty funkcyjne. Czy wszystkie funkcje są wkompilowane w plik wykonywalny, czy też są w bibliotekach dynamicznych (na jakiej platformie)?
Caleth
1
Pytanie jest zbyt ogólne w tym sensie, że zazwyczaj wymaga dyplomu ukończenia studiów wyższych w zakresie elektrotechniki / [automatyzacji projektowania elektronicznego (EDA)] ( en.wikipedia.org/wiki/Electronic_design_automation ) lub inżynierii mechanicznej / projektowania wspomaganego komputerowo (CAD) . Dla porównania, wywołanie biblioteki dynamicznej C / C ++ jest bardzo łatwe, zobacz konwencje wywoływania C dla x86 . Może to jednak wymagać manipulowania stosem (za pomocą wskaźnika stosu procesora) i wartości rejestru procesora.
rwong
1
Funkcje dynamicznie ładujące się nie są obsługiwane przez język C ++. Będziesz musiał spojrzeć na coś specyficznego dla platformy. Na przykład kompilator C ++ w systemie Windows powinien obsługiwać biblioteki DLL systemu Windows, które obsługują formę refleksji.
Simon B,
W C ++ bardzo trudno jest wywołać funkcję, której podpis (typy argumentów i zwracane) nie jest znany w czasie kompilacji. Aby to zrobić, musisz wiedzieć, jak działają wywołania funkcji na poziomie zestawu wybranej platformy.
Bart van Ingen Schenau
2
Rozwiązałbym to, kompilując kod c ++, który tworzy interpreter dla dowolnego języka obsługującego polecenie eval. Problem Bang rozwiązany przy użyciu c ++. : P Zastanów się, dlaczego to nie wystarczy, i zaktualizuj pytanie. Pomaga, gdy rzeczywiste wymagania są jasne.
candied_orange

Odpowiedzi:

13

W czystym standardzie C ++ nie można „zezwolić na import funkcji w czasie wykonywania”; zgodnie ze standardem zestaw funkcji C ++ jest statycznie znany w czasie kompilacji (w praktyce, czas linkowania), ponieważ został ustalony na podstawie połączenia wszystkich jednostek tłumaczeniowych tworzących program.

W praktyce przez większość czasu (z wyłączeniem systemów wbudowanych) program C ++ działa powyżej pewnego systemu operacyjnego . Przeczytaj Systemy operacyjne: trzy łatwe elementy, aby uzyskać dobry przegląd.

Kilka nowoczesne systemy operacyjne umożliwiają dynamiczne ładowanie z wtyczek . POSIX w szczególności określa dlopen& dlsym. Windows ma coś innego LoadLibrary(i gorszy model łączenia; musisz jawnie opisać odpowiednie funkcje, dostarczone lub używane przez wtyczki). BTW w Linuksie możesz praktycznie dlopenwiele wtyczek (patrz mój manydl.cprogram , z wystarczającą cierpliwością może wygenerować, a następnie załadować prawie milion wtyczek). Więc twój XML może napędzać ładowanie wtyczek. Twój opis wieloskładnikowego / wielozłącza przypomina mi sygnały Qt i gniazda (co wymaga mocpreprocesora ; możesz także potrzebować czegoś takiego).

Większość implementacji w C ++ używa manglingu nazw . Z tego powodu lepiej zadeklarować jako extern "C"funkcje związane z wtyczkami (i zdefiniowane w nich i dostępne przez dlsymprogram główny). Przeczytaj C ++ dlopen mini HowTo (przynajmniej dla Linuksa).

BTW, Qt i POCO to frameworki C ++ zapewniające przenośne i wyższe podejście do wtyczek. A libffi umożliwia wywoływanie funkcji, których podpis znany jest tylko w czasie wykonywania.

Inną możliwością jest osadzenie interpretera, takiego jak Lua lub Guile , w twoim programie (lub napisanie własnego, tak jak zrobił to Emacs). To silna decyzja dotycząca projektu architektonicznego. Możesz przeczytać Lisp In Small Pieces and Pragmatics Language Programming, aby uzyskać więcej.

Istnieją warianty lub mieszanki tych podejść. Możesz użyć biblioteki kompilacji JIT (np. Libgccjit lub asmjit). Możesz wygenerować w czasie wykonywania trochę kodu C i C ++ w pliku tymczasowym, skompilować go jako tymczasową wtyczkę i dynamicznie ładować tę wtyczkę (takie podejście zastosowałem w GCC MELT ).

We wszystkich tych podejściach zarządzanie pamięcią stanowi poważny problem (jest to właściwość „całego programu”, a to, co faktycznie jest „kopertą” twojego programu, „się zmienia”). Będziesz potrzebował przynajmniej trochę kultury na temat wywozu śmieci . Przeczytaj podręcznik GC dotyczący terminologii. W wielu przypadkach (arbitralne odwołania cykliczne, w których słabych wskaźników nie można przewidzieć), schemat liczenia odniesień drogi dla inteligentnych wskaźników C ++ może być niewystarczający. Zobacz także to .

Przeczytaj także o dynamicznej aktualizacji oprogramowania .

Zauważ, że niektóre języki programowania, w szczególności Common Lisp (i Smalltalk ), są bardziej przyjazne dla idei funkcji importujących środowisko uruchomieniowe. SBCL jest darmową implementacją Common Lisp i kompiluje się do kodu maszynowego przy każdej interakcji REPL (a nawet jest w stanie wyrzucić śmieci do kodu maszynowego i może zapisać cały plik obrazu rdzenia, który można później łatwo zrestartować).

Basile Starynkevitch
źródło
3

Najwyraźniej próbujesz stworzyć własny styl oprogramowania typu Simulink lub LabVIEW, ale z niecnym komponentem XML.

W najbardziej podstawowej formie szukasz struktury danych zorientowanej na wykres. Twoje modele fizyczne składają się z węzłów (nazywamy je komponentami) i krawędzi (złącza w nazwie).

Nie ma mechanizmu wymuszonego na język, aby to zrobić, nawet z refleksją, więc zamiast tego będziesz musiał stworzyć interfejs API, a każdy komponent, który chce grać, będzie musiał wdrożyć kilka funkcji i przestrzegać zasad określonych przez interfejs API.

Każdy komponent będzie musiał zaimplementować zestaw funkcji, aby wykonać takie czynności jak:

  • Uzyskaj nazwę komponentu lub inne szczegółowe informacje na jego temat
  • Uzyskaj liczbę, ile wejść lub wyjść komponent udostępnia
  • Przesłuchaj składnik dotyczący konkretnego wejścia naszego wyjścia
  • Połącz wejścia i wyjścia razem
  • i inni

I to tylko do skonfigurowania wykresu. Potrzebne będą dodatkowe funkcje, aby uporządkować sposób wykonywania modelu. Każda funkcja będzie miała określoną nazwę, a wszystkie komponenty muszą mieć te funkcje. Wszystko specyficzne dla komponentu musi być osiągalne poprzez ten interfejs API, w identyczny sposób dla poszczególnych komponentów.

Twój program nie powinien próbować wywoływać tych „funkcji zdefiniowanych przez użytkownika”. Zamiast tego powinien wywoływać funkcję obliczeniową ogólnego przeznaczenia lub niektóre takie funkcje na każdym komponencie, a sam komponent dba o wywołanie tej funkcji i przekształcenie danych wejściowych na wynik. Połączenia wejściowe i wyjściowe są abstrakcjami tej funkcji, to jedyna rzecz, którą program powinien zobaczyć.

Krótko mówiąc, niewiele z tego jest specyficznych dla C ++, ale będziesz musiał zaimplementować rodzaj informacji w czasie wykonywania, dostosowanych do konkretnej domeny problemu. Z każdą funkcją zdefiniowaną przez API będziesz wiedzieć, jakie nazwy funkcji wywołać w czasie wykonywania, a także poznać typy danych każdego z tych wywołań, i po prostu używasz zwykłego starego dynamicznego ładowania bibliotek, aby to zrobić. Dostaniesz to z dużą ilością płyty kotłowej, ale to tylko część życia.

Jedynym aspektem specyficznym dla C ++, o którym należy pamiętać, jest to, aby interfejs API był interfejsem API C, aby można było używać różnych kompilatorów dla różnych modułów, jeśli użytkownicy udostępniają własne moduły.

DirectShow to interfejs API, który robi wszystko, co opisałem i może być dobrym przykładem do obejrzenia.

Jaka jest nazwa?
źródło