Map marshallen



  • Hallo Zusammen,

    ich habe folgendes Problem:

    Ich muss in meinem C# Projekt eine C++ DLL einbinden, um dort auf Funktionen zuzugreifen. Ich habe bereits in diesem Forum einiges darüber gelesen und es hat mich auch schon weitergebracht, aber dennoch bin ich jetzt auf ein Problem gestoßen, wo ich zur Zeit nicht weiterkomme.

    Es geht sich speziell um diese Funktion:

    int Gateway::doTrans(const char* domainIp, const char* domainPort, const Request& request, Response& response) 
    {
     ..
    }
    

    Mir bereiten hier die Parameter Request& und Response& Schwierigkeiten.
    Die Definition von diesem Typ ist in einer Header abgebildet.

    #include <string>
    #include <map>
    
    using namespace std;
    
    namespace lsag {
      namespace giftcard {
    
        typedef map<string, string> ParameterMap;
        typedef ParameterMap::value_type ParameterPair;
    
      }
    }
    

    Kann mir jemand sagen, wie ich solch einen Typen marshallen muss? Ich habe viele Beispiele für Strukturen, Arrays etc. gefunden aber sowas in der Art konnte ich leider bisher nicht finden. Ich muss sagen, dass dieses Thema für mich sehr neu ist.

    Vom Prinzip her entspricht diese map doch ein IDictionary in c# oder?

    Gruß
    Pierre



  • Interessant ist hier die Implementierung von Map, und die muss überhaupt nicht der von IDictionary entsprechen - ist ja auch ein Interface, kann also auch beliebig implementiert werden... Außerdem ist die implementierung von Request und Response völlig unklar.



  • Äh, seit wann kann man denn C++-Klassen über Windows-DLLs exportieren? Funktioniert das wirklich?



  • Konrad Rudolph schrieb:

    Äh, seit wann kann man denn C++-Klassen über Windows-DLLs exportieren? Funktioniert das wirklich?

    Klar, warum nicht? Das große Problem ist nur, dass der einbindende Compiler das gleiche ABI haben muss, sich also DLLs von unterschiedlichen Compilern nicht mischen lassen.

    Aber z.B. arbeiten Qt und wxWidgets mit DLLs, und das klappt wenn man o.g. beachtet hervorragend. (MFC nicht auch?)

    EDIT:
    Shit, ich dachte wir wären im CLI-Forum. Ob man die Klassen solch einer DLL dann in C# importieren kann, ist die andere Frage. Ich vermute mal nicht.



  • Ich würde mal wage behaupte, das ein Export von nicht native Typen nur verträgt, wenn die Implementierung exakt identisch ist.

    Wäre es nicht schon ein Problem, eine map von einer Version des Compilers zur nächsten zu tragen ? Die Implementierung z.B. der map ist ja nicht auf binärebene genormt.



  • LordJaxom schrieb:

    Konrad Rudolph schrieb:

    Äh, seit wann kann man denn C++-Klassen über Windows-DLLs exportieren? Funktioniert das wirklich?

    Klar, warum nicht? Das große Problem ist nur, dass der einbindende Compiler das gleiche ABI haben muss, sich also DLLs von unterschiedlichen Compilern nicht mischen lassen.

    Gut. Aber damit man diese DLLs dann benutzen kann, muss man mangled names verwenden, oder sehe ich das falsch? Gut möglich, dass ich mich irre, ich habe das noch nie gemacht.



  • Konrad Rudolph schrieb:

    Gut. Aber damit man diese DLLs dann benutzen kann, muss man mangled names verwenden, oder sehe ich das falsch? Gut möglich, dass ich mich irre, ich habe das noch nie gemacht.

    Das Name Mangling übernimmt doch der Compiler selbst. Die Klassen werden genau wie Funktionen mit __declspec(dllexport) bzw. ...import gekennzeichnet. Allerdings muss, wie Knuddlbaer schon zu behaupten wagte, die Implementierung (d.h. auch die Header) exakt identisch sein, da werden selbst Versionsunterschiede eines Compilerherstellers manchmal nicht verziehn.

    BTW: Boost nutzt auch DLLs. Was viele dieser Frameworks gemeinsam haben ist, dass die DLLs hinterher Namen wie boost_1_34_1_msvc_80_d_mt.dll haben 😉



  • Aber wie schaut das mit Templates aus ?

    map ist ja eines und in der DLL kann man doch garnicht wissen, welche Instanzen man benötigt ?

    Falls Du da zufällig ein Link zu dem Thema hast würde ich mich freuen wenn Du diesen Postest. Man könnte dann im "scheint irgendwie zu gehen" dann noch ein Link hinpacken den man abarbeiten kann wenn man mal selbst an der Stelle ist.

    Thx!



  • Map muss irgendwo in einem Header definiert sein, eben weil es ein Template ist. Erst der Compiler ezeugt dann spezialisierte maps mit den typen, die wirklich verwendet werden. D.h. der Code ist irgendwo zu finden.

    C++ Klassen können in C# verwendet werden, beim DllImport Attribute, kann

    CallingConvention = CallingConvention.thisCall
    

    verwendet werden. Soviel ich weis muss man allerdings C-Funktionen für den Konstruktor und Destruktor implementieren.
    Ein weiteres Problem sind die Membernamen der Methoden. Methoden von exportieirten klassen sehen ungefähr so aus:

    methode@klasse@abcde@xyz

    Soviel ich weiß ist es Sache des Compilers, wie Methoden dekoriert werden, also kann man meiner Meinung nach nicht auf den Namen vertrauen, was dann doch relativ schlecht ist (vielleicht liege ich damit auch falsch...). Was auf jeden Fall geht:
    Um die Klasse herum eine C-Wrapper schreiben.
    Im Prinzip kannst du also zwei Wege gehen:
    1. Du findest heraus, wie map im Implementiert ist und erstellst in C# eine Passende struktur.
    2. Du schreibst eine C-Schnittstelle für map, importierst diese nach C# und arbeitest mit der native Implementierung.



  • Hallo zusammen,

    nun ich habe es jetzt geschafft, die DLL wie gewünscht anzusprechen. Ob die Lösung ideal ist, weiß ich nicht zumindest funktioniert es jetzt.

    Ich habe in der C++ DLL einen Wrapper geschrieben der so aussieht.

    #pragma managed
    #pragma warning(disable:4786)
    #pragma warning(disable:4290)
    #pragma warning(disable:4996)
    
    __gc public class ReqManClass 
    { 
    public: 
    
    String* getBalance(char* ip, char* port, char* cardid)
    {
    	Request req;
    	Response res;	
             int ret;
    
    	req.setTransType(transtype::GET_BALANCE);
    	req.setCardId(cardid);
    	ret = Gateway::doTrans(ip,port,req,res);
    
    	balance = (String*)res.getAmount();
    
    	return balance;
    }
    
    private: 
     String* balance;
    

    Somit konnte ich die C++ DLL in mein C# Projekt einbinden.
    Dann habe ich eine Instanz von der Wrapper Klasse in mein C# Projekt eingebunden. Hier die Funktion, die die C++ DLL aufruft.

    public unsafe string getBalance(string dIP, string dPort, string CardID)
            {
    
                byte[] domainIp;
                byte[] domainPort;
                byte[] byteCardId;
    
                domainIp = System.Text.Encoding.ASCII.GetBytes(dIP);
                domainPort = System.Text.Encoding.ASCII.GetBytes(dPort);
                byteCardId = System.Text.Encoding.ASCII.GetBytes(CardID);
    
                sbyte[] sdomainIp = (sbyte[])(Array)domainIp;
                sbyte[] sdomainPort = (sbyte[])(Array)domainPort;            
                sbyte[] sbyteCardId = (sbyte[])(Array)byteCardId;
    
                [b]ReqManClass Request = new ReqManClass();[/b]
                String balance;  
    
                unsafe
                {
    
                    fixed (sbyte* psdomainIp = sdomainIp)
                        fixed (sbyte* psdomainPort = sdomainPort)
                            fixed (sbyte* psbyteCardId = sbyteCardId)
                                {
                                    balance = Request.getBalance(psdomainIp,  psdomainPort,psbyteCardId);                                                                
                                }
    
                    return balance;
                }
    

    Die Funktionen die mir Probleme bereitet haben, werden jetzt in der Wrapper Klasse der C++ DLL aufgerufen, ich musste mich nur noch um char* Problem in C# kümmern, was ich wie oben zu sehen mit sbyte[], bzw. sbyte* lösen konnte.

    Zumindest läuft es 🙂

    Danke für die vielen Antworten.
    Gruß
    Pierre


Anmelden zum Antworten