Wrapper Marshalling-Problem



  • Hallo Forum,

    ich schreibe momentan einen .NET-Wrapper für die Winsock2-Funktionen mit C++. Dort muss ich eine Struktur in eine Klasse umwandeln, was ansich auch funktioniert. Problematisch ist es aber bei folgender Variable:

    char FAR  FAR **h_aliases;
    

    Das habe ich in eine String-Liste (System::Collections::Generic::List<String>) umgewandelt, was im C++ Wrapper auch tadellos geschluckt wird. Verarbeite ich die Klasse jedoch in C#, meckert der Compiler "'...h_aliases' is not supportet by the language". ...was mir ziemlich unlogisch erscheint - wozu nimmt man denn .NET-Datentypen?
    Hat vielleicht jemand hier eine Idee, wie man das in anderer Art und Weise wrappen könnte? Das ist halt alles ziemlich dynamisch - weder die Länge noch die Anzahl der Strings ist vorher bekannt.

    VG
    doc

    Die ursprüngliche Struktur:

    typedef struct hostent {
      char FAR      *h_name;
      char FAR  FAR **h_aliases;
      short         h_addrtype;
      short         h_length;
      char FAR  FAR **h_addr_list;
    } HOSTENT, *PHOSTENT, FAR *LPHOSTENT;
    

    Meine dafür angelegte Klasse:

    public ref class WS2_hostent {
    public:
      String^ h_name;
      List<String^> h_aliases;
      Int16 h_addrtype;
      Int16 h_length;
      List<String^> h_addr_list;
    
      WS2_hostent();
      !WS2_hostent();
      ~WS2_hostent();
    };
    

    Die verarbeitende Funktion, welche aus C# aufgerufen wird:

    Int32 WS2_Functions::gethostbyname(String^ name, WS2_hostent^ WS2_HostEnt) {
      hostent* hostEnt = ::gethostbyname((char*)(void*)Marshal::StringToHGlobalAnsi(name));
      WS2_HostEnt->h_name = String(hostEnt->h_name).ToString();
      while (*(hostEnt->h_aliases)) {
        WS2_HostEnt->h_aliases.Add(String(*hostEnt->h_aliases).ToString());
        hostEnt->h_aliases++;
      }
      WS2_HostEnt->h_addrtype = hostEnt->h_addrtype;
      WS2_HostEnt->h_length = hostEnt->h_length;
      while (*(hostEnt->h_addr_list)) {
        WS2_HostEnt->h_addr_list.Add(String(*hostEnt->h_addr_list).ToString());
        hostEnt->h_addr_list++;
      }
      return (hostEnt != NULL) ? WS2_Definitions::WS2_WSAOK : (Int32)WSAGetLastError();
    }
    

    Die Verwendung in C#, die fehlschlägt:

    private void button3_Click(object sender, EventArgs e) {
      WS2_hostent hostEnt = new WS2_hostent();
      SocketFunctions.gethostbyname("www.google.de", hostEnt);
      foreach (String s in hostEnt.h_aliases) { // !!!
        textBox1.AppendText("\th_alias: " + s + "\r\n");
      }
    }
    


  • was spricht gegen die Verwendung von den .NET Sachen für Netzwerk?



  • doc_ev schrieb:

    hostent* hostEnt = ::gethostbyname((char*)(void*)Marshal::StringToHGlobalAnsi(name));
    

    Da ist ein Speicherleck, nirgendwo in der Funktion wird Marshal::FreeHGlobal aufgerufen. Siehe FAQ
    http://www.c-plusplus.net/forum/viewtopic-var-t-is-158664.html

    Und was soll das (char*)(void*) ?

    doc_ev schrieb:

    String(*hostEnt->h_addr_list).ToString()
    

    Was gibt das .ToString() anderes zurück als den String selber ?



  • doc_ev schrieb:

    Verarbeite ich die Klasse jedoch in C#, meckert der Compiler "'...h_aliases' is not supportet by the language". ...was mir ziemlich unlogisch erscheint - wozu nimmt man denn .NET-Datentypen?

    List<String^> h_addr_list;
    

    Das ist so nur in C++/CLI erlaubt. Richtig wäre

    List<String^>^ h_addr_list;
    


  • Da ist noch viel mehr falsch:

    hostEnt->h_aliases++;
    

    Geht nicht weil in der Doku von hostent steht:

    MSDN schrieb:

    An application should never attempt to modify this structure or to free any of its components.

    Gleiches gibt für h_addr_list.

    Außerdem sind die beiden Einträge Zeiger auf Arrays. Auch ein

    .Add(gcnew String(*hostEnt->h_addr_list));
    

    ist Quatsch, weil das ein Array von Adressen ist, kein String.

    Hier ein Beispiel aus dem MSDN :

    BOOL GetIpAddress(char *hostname)
       {
          WCHAR    msg[128];
          HOSTENT *lpHost=NULL;
          struct sockaddr_in   dest;
    
          lpHost = gethostbyname(hostname);
          if (lpHost == NULL)
          {
             wsprintf(msg, L"gethostbyname failed: %d", WSAGetLastError());
             MessageBox(NULL, msg, NULL, MB_OK);
          }
          else
          {
             for(int i=0; lpHost->h_addr_list[i] != NULL ;i++)
             {
                memcpy(&(dest.sin_addr), lpHost->h_addr_list[i],
                       lpHost->h_length);
                wsprintf(msg, L"IP address is: '%S'",
                         inet_ntoa(dest.sin_addr));
                MessageBox(NULL, msg, L"IP Address", MB_OK);
             }
    
          }
          return 0;
       }
    


  • mogel schrieb:

    was spricht gegen die Verwendung von den .NET Sachen für Netzwerk?

    Nichts! Es geht mir nur drum, Zeit tot zu schlagen :). Ich will mich als völlig ahnungsloser - eigentlich C und C# Programmierer - mal mit der Materie befassen und hab dafür die Sockets ausgewählt.

    nn schrieb:

    Da ist ein Speicherleck, nirgendwo in der Funktion wird Marshal::FreeHGlobal aufgerufen. Siehe FAQ

    Danke! Wie gesagt: Völlig ahnungslos. Aber anders lernt mans ja nicht.

    nn schrieb:

    Und was soll das (char*)(void*) ?

    StringToHGlobalAnsi() gibt einen IntPtr zurück. Den kann man nicht direkt nach char* casten. Vielleicht gehts auch anders... muss ja nicht schön sein für mich.

    nn schrieb:

    Was gibt das .ToString() anderes zurück als den String selber ?

    Eine Referenz auf einen String - also genau das, was ich an dieser Stelle brauche.

    nn schrieb:

    Das ist so nur in C++/CLI erlaubt. Richtig wäre

    List<String^>^ h_addr_list;
    

    Das geht in C# aber auch nicht. Hatte ich schon probiert.

    Ich pack jetzt einfach die beiden char** in jeweils einen (durch irgendein Zeichen separierten) String.



  • So wie oben angesprochen gehts. Jetzt kommt leider folgendes raus (mit www.google.de aufgerufen):

    --->gethostbyname():
    h_name: www.l.google.com
    h_addrtype: 2
    h_length: 4
    h_addr_list: J}+cJ}+hJ}+“J}+iJ}+gJ}+jwww.l.google.com
    h_addr_list: J}+hJ}+“J}+iJ}+gJ}+jwww.l.google.com
    h_addr_list: J}+“J}+iJ}+gJ}+jwww.l.google.com
    h_addr_list: J}+iJ}+gJ}+jwww.l.google.com
    h_addr_list: J}+gJ}+jwww.l.google.com
    h_addr_list: J}+jwww.l.google.com
    h_addr_list:

    Das sieht mir fast nach einer vermuksten Unicode-ASCII-Konvertierung aus - oder was meint ihr?



  • Zu deiner Frage:
    Im Member h_addr_list sind keine Strings (const char*), sondern Longs (a je 4 Byte) drin.

    Edit
    Worauf NochMehrFehler schon hingewiesen hat wie ich gerade bemerkte.

    Hier mal ein komplettes Bsp:

    #include "stdafx.h"
    
    #define WIN32_LEAN_AND_MEAN
    #include <windows.h>
    #include <winsock2.h>
    #include <cstring>
    
    #pragma comment (lib, "Ws2_32.lib")
    
    #include "StringConv.h"
    
    using namespace System;
    using namespace System::Diagnostics;
    using namespace System::Net;
    using namespace System::Net::Sockets;
    using namespace System::Collections::Generic;
    
    value class HostEntry
    {
    public:
        HostEntry(String^ name, array<String^>^ aliases, System::Net::Sockets::AddressFamily addressFamily, array<IPAddress^>^ addresses)
            : _name(name)
            , _aliases(aliases)
            , _addressFamily(addressFamily)
            , _addresses(addresses)
        { }
        property String^ Name
        {
            String^ get() { return _name; }
        }
        property array<String^>^ Aliases
        {
            array<String^>^ get()
            {
                return _aliases;
            }
        }
        property System::Net::Sockets::AddressFamily AddressFamily
        {
            System::Net::Sockets::AddressFamily get()
            {
                return _addressFamily;
            }
        }
        property array<IPAddress^>^ Addresses
        {
            array<IPAddress^>^ get()
            {
                return _addresses;
            }
        }
    
    private:
        String^                              _name;
        array<String^>^                      _aliases;
        System::Net::Sockets::AddressFamily  _addressFamily;
        array<IPAddress^>^                   _addresses;
    };
    
    HostEntry^ GetHostByName(String^ hostName)
    {
        hostent* entry = gethostbyname(StringConvA(hostName));
        if (0 != entry)
        {
            List<String^>^ aliases = gcnew List<String^>();
            for (int i = 0; 0 != entry->h_aliases[i]; ++i)
            {
                const char* alias = entry->h_aliases[i];
                aliases->Add(gcnew String(alias));
            }
    
            List<IPAddress^>^ addresses = gcnew List<IPAddress^>();
            for (int i = 0; 0 != entry->h_addr_list[i]; ++i)
            {
                in_addr in = { };
                std::memcpy(&in.S_un.S_addr, entry->h_addr_list[i], sizeof(in.S_un.S_addr));
                const char* addr = inet_ntoa(in);
                addresses->Add(IPAddress::Parse(gcnew String(addr)));
            }
    
            return gcnew HostEntry(gcnew String(entry->h_name), aliases->ToArray(), (System::Net::Sockets::AddressFamily)entry->h_addrtype, addresses->ToArray());
        }
        else
        {
            int error = WSAGetLastError();
            throw gcnew SocketException(error);
        }
    }
    
    int main(array<System::String ^> ^args)
    {
        WSADATA wsaData = {};
        WSAStartup(MAKEWORD(2,2), &wsaData);
    
        try
        {
            HostEntry^ hostEntry = GetHostByName(L"www.google.com");
    
            for each (IPAddress^ address in hostEntry->Addresses)
            {
                Debug::WriteLine(address);
            }
        }
        catch (SocketException^ ex)
        {
            Debug::WriteLine(ex->Message);
        }
    
        WSACleanup();
    
        return 0;
    }
    

    StrinConv.h (von der FAQ dieses Forums):

    // Reference:
    // http://www.c-plusplus.net/forum/viewtopic-var-t-is-158664.html
    
    #pragma once
    
    #include <windows.h>
    #include <tchar.h>
    
    struct StringConvA
    {
        char *szAnsi;
        StringConvA(System::String ^s)
            : szAnsi(static_cast<char*>(System::Runtime::InteropServices::Marshal::StringToHGlobalAnsi(s).ToPointer()))
        {}
        ~StringConvA()
        {
            System::Runtime::InteropServices::Marshal::FreeHGlobal(System::IntPtr(szAnsi));
        }
        operator LPCSTR() const
        {
            return szAnsi;
        }
    };
    
    struct StringConvW
    {
        wchar_t *szUnicode;
        StringConvW(System::String^ s)
            : szUnicode(static_cast<wchar_t*>(System::Runtime::InteropServices::Marshal::StringToHGlobalUni(s).ToPointer()))
        {}
        ~StringConvW()
        {
            System::Runtime::InteropServices::Marshal::FreeHGlobal(System::IntPtr(szUnicode));
        }
        operator LPCWSTR() const
        {
            return szUnicode;
        }
    };
    
    #ifdef _UNICODE
        typedef StringConvW StringConvT;
    #else
        typedef StringConvA StringConvT;
    #endif
    


  • Ich danke dir! So funktionierts wunderbar. Mit dem Array probier ich es aber nicht. Die IP-Adressen stehen alle ein einem String getrennt durch ';'.


Log in to reply