Funktionsmarshalling generieren, um Funktionen und Parameter durch dumme Schnittstelle zu tunneln



  • intrusive pointer ist aber eb en genau das: Intrsuive. Ich müsste jede einzelne Klasse davon erben lassen. Nix gut. Siehe oben, keinen Code anfassen, der funktioniert.

    Ah, ok. Ich wusste nicht dass du da auch Objekte von Klassen übergeben willst, die bereits existieren.

    Jetzt zurück zur eigentlichen Frage:
    Das struct zu bauen, das könnte mir doch eigentlich der Compiler abnehmen.
    Im Grunde sowas wie (pseudocode)

    Dadurch sparst du dir die Definition einiger Hilfs-Structs.
    Allerdings bezahlst du es mit nicht-sprechenden Namen ("arg1", "arg2" etc.).

    Aber, eine ganz andere Idee: du könntest einfach Interfaces marshallen.

    So in der Art:

    #include <stdexcept>
    #include <string>
    #include <vector>
    #include <iostream>
    
    void sendMessage(long receiver, long input, void* param);
    void receiveMessageCallback(long sender, long input, void* param);
    
    enum Functions {
    	// ...
    	getInterface,
    };
    
    enum MyInterfaceID {
    	fooInterface,
    	barInterface,
    };
    
    struct InterfaceQuery
    {
    	explicit InterfaceQuery(MyInterfaceID iid) : interfaceId(iid), interface(0) {}
    
    	MyInterfaceID interfaceId;
    	void* interface;
    };
    
    template <class T>
    struct Invoker
    {
    	explicit Invoker(long receiver)
    	{
    		InterfaceQuery iq(T::iid);
    		sendMessage(receiver, getInterface, &iq);
    		m_interface = static_cast<T*>(iq.interface);
    		if (!m_interface)
    			throw std::runtime_error("blah");
    	}
    
    	T* operator -> ()
    	{
    		return m_interface;
    	}
    
    private:
    	T* m_interface;
    };
    
    // -----------------------------
    
    class Foo
    {
    public:
    	static MyInterfaceID const iid = fooInterface;
    	virtual void DoFoo(std::string a1, long a2, std::vector<char> const& a3) = 0;
    };
    
    class Bar
    {
    public:
    	static MyInterfaceID const iid = barInterface;
    	virtual void DoBar(int a1) = 0;
    };
    
    // -----------------------------
    
    class SomeObject : public Foo
    {
    public:
    	virtual void DoFoo(std::string a1, long a2, std::vector<char> const& a3)
    	{
    		std::cout << "DoFoo\n";
    	}
    };
    
    SomeObject g_someObject;
    
    void receiveMessageCallback(long sender, long input, void* param)
    {
    	switch (input)
    	{
    	case getInterface:
    		{
    			InterfaceQuery* iq = static_cast<InterfaceQuery*>(param);
    			switch (iq->interfaceId)
    			{
    				// kann man schön in ein Makro verpacken
    			case Foo::iid:
    				iq->interface = static_cast<Foo*>(&g_someObject); // der Cast nach Foo* ist wichtig, daher wäre es wirklich 
                                                                      // kein Fehler das in ein Makro zu verpacken
    
    				break;
    			default:
    				iq->interface = 0;
    				break;
    			}
    		}
    		break;
    
    	default:
    		abort();
    	}
    }
    
    int main() 
    {
    	Invoker<Foo>(1234)->DoFoo("blah", 1234, std::vector<char>());
    	return 0;
    }
    

    Weiss nicht ob das für dich brauchbar ist, da es vermutlich wieder erfordert dass man bestehende Klassen ändert.
    Allerdings müsste man nicht unbedingt vererben, man könnte die "Interfaces" auch als Member machen. Wobei das viel viel mehr Tippaufwand wäre, da man vermutlich Dutzendweise Forwarding-Funktionen schreiben müsste.

    Andrerseits ist der Impact auf bestehenden Code normalerweise minimal, wenn man einer Klasse eine zusätzliche Basisklasse spendiert, wenn man folgendes berücksichtigt:

    a) die neue Basisklasse hat nur virtuelle Funktionen und sonst keine Member
    b) die Namen der virtuellen Funktionen sind so gewählt, dass sie nicht mit anderen bestehenden (u.U. nicht virtuellen) Funktionen kollidieren können
    c) die neue Basisklasse wird NUR für den oben gezeigten Zweck verwendet
    d) die neue Basisklasse ist in einem eigenen Namespace für sich alleine definiert (um Probleme mit Argument-Dependent-Lookup zu vermeiden)

    Das ganze geht natürlich nur, wenn sendMessage() nicht z.B. irgendwelche Dinge synchronisiert, so dass ein Zugriff auf ein Objekt über den erhaltenen Interface-Zeiger nicht mehr OK wäre, weil sendMessage() schon Locks freigegeben hat.

    Und wenn die Zeiger auf die Objekte auch nach sendMessage noch gültig bleiben.

    Uswusf.



  • Ganz einfach wenn eine Dritte Bedingung erfüllt währe:

    -Beide Komponenten laufen im gleichen virtuellen Speicherbereich 
    -Die Wortbreite ist immer 32bit und der Byte-Order immer Intel 
    --> - Beide Module die Identische Code Basis benutzen
    

    Ist das so, dann könnte ich eine Lösung anbieten!

    Lichtlein



  • Puuuh, danke für die Mühe erstmal.

    Das Interface sieht garnicht blöd aus. Ich müsste tatsächlich nur unter alle betroffenen Klassen ein pure-virtual Interface drunterbasteln.

    Was mir aber nicht klar ist, wie in dem Zusammenhang überhaupt Objekte erzeugt werden sollen. In deinem Beispiel hält Komponente B ein globale SomeObject-Instanz.
    In meinem Fall muss aber Komponente A beliebig viele Instanzen erzeugen können, und auf jeder Instanz die Funktionen aufrufen.

    Der invoker bekäme dadurch einen weiteren Parameter, nämlich die Instanz, auf der überhaupt was invoked werden soll. Stellt sich die Frage, wen diese Instanzen gehören sollen.

    Philipp



  • Lichtlein schrieb:

    Ganz einfach wenn eine Dritte Bedingung erfüllt währe:

    --> - Beide Module die Identische Code Basis benutzen

    Du meinst, dass sie die selben Header inkludieren?
    Ja, das tun sie.

    Philipp



  • Nein ich dachte da an eine Binäre Datei in der mehr als 1 Core unterwegs sind.
    (Multiprozessor System).
    Es geht um den void Pointer, wenn es möglich ist diesen Pointer als Funktionspointer zu benutzen dann geht das was Du vorhast ganz einfach und sehr Flexibel.

    Lichtlein



  • Achso.

    Nein, by design ist das alles single-Threaded. Da kann kein anderer drin herumfummeln.
    Immer her mit der Idee!

    Philipp



  • PhilippM schrieb:

    Was mir aber nicht klar ist, wie in dem Zusammenhang überhaupt Objekte erzeugt werden sollen. In deinem Beispiel hält Komponente B ein globale SomeObject-Instanz.
    In meinem Fall muss aber Komponente A beliebig viele Instanzen erzeugen können, und auf jeder Instanz die Funktionen aufrufen.

    Der invoker bekäme dadurch einen weiteren Parameter, nämlich die Instanz, auf der überhaupt was invoked werden soll. Stellt sich die Frage, wen diese Instanzen gehören sollen.

    Dazu kann ich dir nun wirklich nichts sagen, da ich das Design von eurer sendMessage-Schnittstelle nicht kenne - ich hatte angenommen dass der "receiver" Wert in Wirklichkeit für ein Objekt steht, und nicht für ein "Modul".

    Ist aber auch egal, genau so wie in meinem Beispielcode kannst du ein "statisches" Interface mit Factory-Funktionen abfragen. Über diese Factory-Funktionen können dann direkt Objekte erzeugt werden - von denen muss man sich dann nichtmal die Interfaces über sendMessage holen, da man ja schon Zeiger hat.



  • Ich gebe Dir mal meine Idee, vielleicht hilft sie Dir ja weiter.
    Wie gesagt funktioniert das nur wenn Module A und Module B einen
    Funktionspointer an der gleichen Stelle haben und den gleichen Logischen Speicher benutzen.

    Also, Module A will, das Module B Type C anlegt und mit Werten x,y,z bestückt.

    Packen wir die Initial Werte in eine Struktur.

    struct TypeCInitValues
    {
        int x, y, z;
    };
    

    Als nächstes braucht man eine Fabrik Funktion die aus den Initialwerten Type C anlegt.

    TypeC* fabrikTypeC(const TypeCInitValue &init_values)
    {
        return new cTypeC(init_values.x, init_values.y, init_values.z);
    }
    

    Über Deine send und receive funktionen wollen wir nur einen Defenierten Type schicken.
    Der sieht so aus.

    struct SendData
    {
        virtual void receive();
    };
    

    Jetzt die Klasse die alles Verpackt und Sendet, und auch der empfänger der
    alles dann wieder entschlüsselt.

    struct SendHelper
    {
    
        template< typename data_tmp, typename fabik_funktion, typename generated_class>
        struct SendDataTmp : public SendData
        {
            virtual void receive()
            {
                generated_class *pGeneratetClass = (FabrikFunktion)(*pData);
    
                // MACH WAS MIT DER ERZEUGTEN CLASSE
            }
    
            data_tmp            *pData;
            fabrik_funktion     FabikFunktion;
        };
    
        template< typename init_data, typename generated_class >
        static void send(const INIT_DATA &init_data, generated_class* (*pfunc) (init_data &InitData))
        {
            typedef generated_class* (*tFunc) (init_data &InitData)     tFabikFunc;
    
            SendDataTmp<init_data, tFabikFunc, generated_class>  Sdt;
            Sdt.pData = &init_data;
            Sdt.FabrikFunktion = pfunc;
    
            sendMessage(0, 0, &Sdt)
        }
    
    };
    

    So und hier nun der Aufruf des ganzen.

    TypeCInitValues init_values;
    
    init_values.x = 1;
    init_values.y = 2;
    init_values.z = 3;
    
    SendHelper< TypeCInitValues >::send(init_values, fabrikTypeC);
    

    Habe ich jetzt aus den Kopf geschrieben, kann also sein das es nicht Compiliert.

    Hoffe Du erkennst was ich da gemeint habe, und das Du es benutzen kann oder wenigstens einen
    anregung.

    Lichtlein



  • Sieht mir jetzt ganz nach Visitor-Pattern aus.

    Ist auch nicht doof, bloss (ohne Lambda-Expressions) sehr umständlich, da man ziemlich viel tippen/kopieren muss wenn man unterschiedliche "MACH WAS MIT DER ERZEUGTEN CLASSE" (Klasse schreibt man mit K) braucht.

    Falls Lambda-Expressions zur Verfügung stehen finde ich die Idee allerdings sehr gut.



  • Hey, richtig klasse wieviele gute Ideen hier aufschlagen. Danke an alle Antworter 🙂

    Lambda-Funktionen habe ich keine. Noch ist kein boost im Projekt, und ich denke dabei bleibts auch.
    C++0x fällt auch flach, da der aktuellste Compiler auf dem Mac, den ich nutzen kann, 4.2 ist, und da ist noch nix mit C++0x.

    Aber:
    Ich habe an meinem Design nochmal ein bißchen gefeilt und siehe da - es gibt nur eine einzige Klasse, dere Methoden so getunnelt werden müssen, und - Überraschung - die ist auch noch ein Singleton.

    Daher passt Hustbaer's Lösung eigentlich wie angegossen. Das globale Objekt wird durch das Singleton ersetzt und voila! Funktioniert prächtig!

    Aber leider nur auf dem GCC.
    Wenn ich das durch Visual C++ 2010 jagen will, bekomme ich folgenden Fehler:

    enum Function {
        FSystem,
        // ...
    };
    
    enum FMSInterfaces {
        FMSSystemInterface,
        // ...
    };
    
    struct InterfaceQuery
    {
        explicit InterfaceQuery(FMSInterfaces iid) : interfaceId(iid), interface(0) {} // <--- das ist Zeile 25
    
        FMSInterfaces interfaceId;
        void* interface;  // <--- das ist Zeile 28
    };
    
    (28) : error C2332: 'struct': Fehlender Tagname
    (28) : error C2226: Syntaxfehler: Typ 'InterfaceQuery::<unnamed-tag>' nicht erwartet
    (28) : error C2238: Unerwartete(s) Token vor ';'
    (25) : error C2332: 'struct': Fehlender Tagname
    (25) : error C2011: '<unnamed-tag>': 'enum' Typneudefinition
    : Siehe Deklaration von '<unnamed-tag>'
    (25) : error C2059: Syntaxfehler: 'Typ'
    

    Und nu?
    GCC 4.4 und 4.5 kompilieren das wie gesagt prächitg.

    Gruß,
    Philipp

    EDITH hat festgestellt, dass wenn ich

    void* interface;
    

    in

    void* interface_;
    

    umbennene, es tadellos kompiliert.
    Hä?
    Ist "interface" bei msvc ein Schlüsselwort?

    Gruß,
    Philipp



  • PhilippM schrieb:

    Ist "interface" bei msvc ein Schlüsselwort?

    Nö, ist kein Schlüsselwort (obwohl das Syntax-Highlighting von VS 2010 es in Schlüsselwort-Farbe einfärbt).

    Aber irgendwo in den Untiefen der Windows-Header gibt's ein

    #define interface struct
    

    Hatte ich nicht dran gedacht. (Ich hatte den Code sogar mit MSVC geschrieben und probe-compiliert, bloss hatte ich da kein #include <windows.h> drinnen gehabt...)


Anmelden zum Antworten