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
docDie 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.htmlUnd 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 ';'.