Hello list, Two things: 1) I''ve got a question regarding inheritance and abstract base classes that I''m hoping somebody can give me some help with. I''m trying to make some socket-based stuff work based on the descriptions in the pickaxe book which states that "BasicSocket is an abstract base class for other socket classes". It also says "IPSocket is a base class for sockets using IP as their transport. TCPSocket and UDPSocket are based on this class". I''ve successfully implemented the class method on IPSocket, but when I wanted to make TCPSocket.addr and TCPSocket.peeraddr work (both methods should be inherited from IPSocket), I had to write this functionality directly into my TCPSocketOps class - if I put it into IPSocketOps the methods are not found. Now I have to implement UDPSocket.addr and UDPSocket.peeraddr and I don''t want to duplicate the logic in the methods (besides, then it''s not really inheritance is it?). To make my question clearer, I''ll paste the code below. The methods in question are marked with TODO''s - they sit in the TCPSocketOps class which is marked with RubyClass("TCPSocket") instead of inheriting the methods from RubyClass("IPSocket") and I''m questioning whether there is another way to do this or if that''s really correct. Thanks for your time. Also, the exception I get when I move the methods to IPSocketOps is: System.MissingMethodException: undefined local variable or method `addr'' for #<Ruby::BuiltIns::TCPSocket:0x0000058>:Ruby.Builtins.RubyClass at Ruby.Builtins.Kernel.MethodMissing(CodeContext context, Object self, BlockParam block, SymbolId name, Object[] args) in D:\Projects\IronRuby\trunk\src\IronRuby.Libraries\Builtins\Kernel.cs:line 227 using System; using System.Collections.Generic; using System.Text; using Ruby.Runtime; using System.Net.Sockets; using Ruby.Builtins; using Microsoft.Scripting; using System.Net; namespace Ruby.BuiltIns { [RubyClass("IPSocket")] public static class IPSocketOps { [RubyMethod("getaddress", RubyMethodAttributes.PublicSingleton)] public static string GetAddress(object self, MutableString hostName) { return System.Net.Dns.GetHostEntry(hostName).AddressList[0].ToString(); } } [RubyClass("TCPSocket", Inherits = typeof(Object), Extends typeof(IPSocket))] public static class TCPSocketOps { [RubyMethod("gethostbyname", RubyMethodAttributes.PublicSingleton)] public static object GetHostByName(object self, MutableString hostName) { RubyArray result = ArrayOps.CreateArray(); System.Net.IPHostEntry hostEntry System.Net.Dns.GetHostEntry(hostName); result.Add(hostName); result.Add(ArrayOps.CreateArray(hostEntry.Aliases.Length)); foreach (string alias in hostEntry.Aliases) { ((RubyArray)result[1]).Add(new MutableString(alias)); } result.Add((int)hostEntry.AddressList[0].AddressFamily); result.Add(new MutableString(hostEntry.AddressList[0].ToString())); return result; } [RubyMethod("new", RubyMethodAttributes.PublicSingleton)] public static TCPSocket/*!*/ CreateSocket(CodeContext/*!*/ context, object self, BlockParam/*!*/ startRoutine, [NotNull]params object[]/*!*/ args) { return new TCPSocket(args[0], args[1]); } [RubyMethod("open", RubyMethodAttributes.PublicSingleton)] public static TCPSocket/*!*/ CreateSocketAlias_Open(CodeContext/*!*/ context, object self, BlockParam/*!*/ startRoutine, [NotNull]params object[]/*!*/ args) { return CreateSocket(context, self, startRoutine, args); } // TODO: This should move into IPSocketOps - How does inheritance work in IronRuby? [RubyMethod("addr", RubyMethodAttributes.PublicInstance)] public static object GetAddress(IPSocket self) { return GetAddressFromEndPoint(self.socket.LocalEndPoint); } // TODO: This should move into IPSocketOps - How does inheritance work in IronRuby? [RubyMethod("peeraddr", RubyMethodAttributes.PublicInstance)] public static object GetPeerAddress(IPSocket self) { return GetAddressFromEndPoint(self.socket.RemoteEndPoint); } public static object GetAddressFromEndPoint(EndPoint endPoint) { RubyArray result = ArrayOps.CreateArray(); IPEndPoint ep = (IPEndPoint)endPoint; MutableString addressFamily = null; switch (ep.AddressFamily) { // Surely this already exists somewhere in System.Net? Couldn''t find it though... case AddressFamily.InterNetwork: addressFamily = new MutableString("AF_INET"); break; default: throw new NotImplementedException(); } result.Add(addressFamily); result.Add(ep.Port); result.Add(new MutableString(System.Net.Dns.GetHostEntry(ep.Address).HostName)); result.Add(new MutableString(ep.Address.ToString())); return result; } } public class IPSocket { internal Socket socket; } public class TCPSocket : IPSocket { public TCPSocket(object hostname, object port) { socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); int portNumber = 0; if (port is int) { portNumber = (int)port; } else if (port is MutableString) { switch ((MutableString)port) { case "ftp": portNumber = 21; break; case "http": portNumber = 80; break; default: throw new NotImplementedException(); } } socket.Connect((MutableString)hostname, portNumber); } } }
Apologies - I have an itchy send finger. The other issue is that I think the file CalcOps.cs is in the repository, but not included in the project - so the code refuses to build cleanly on the latest revision. I worked around this by manually adding the file to the correct project in visual studio. Thanks for your time Terence On Dec 20, 2007 2:56 AM, Terence Lewis <lewistm at gmail.com> wrote:> Hello list, > > Two things: > > 1) ... >
Terence Lewis:> > Apologies - I have an itchy send finger. The other issue is that I > think the file CalcOps.cs is in the repository, but not included in > the project - so the code refuses to build cleanly on the latest > revision. I worked around this by manually adding the file to the > correct project in visual studio.I just removed this file from SVN. It was an artifact of a demo that I did at RubyConf ... -John
I think you are getting too carried away with the various conventions around extending .NET types for use by the DLR. If you are simply wanting to wrap the underlying System.Net.Sockets.Socket class (rather than extend it) then you don''t need to have both IPSocket and IPSocketOps classes and so on. It is enough to have an abstract non-static class IPSocket and a concrete class TCPSocket, which derives directly from IPSocket. Everything then works. See the code below. using System; using System.Collections.Generic; using System.Text; using Ruby.Runtime; using System.Net.Sockets; using Ruby.Builtins; using Microsoft.Scripting; using System.Net; namespace Ruby.BuiltIns { [RubyClass("IPSocket")] public abstract class IPSocket { internal Socket socket; [RubyMethod("getaddress", RubyMethodAttributes.PublicSingleton)] public static string GetAddress(object self, MutableString hostName) { return System.Net.Dns.GetHostEntry(hostName).AddressList[0].ToString(); } [RubyMethod("addr", RubyMethodAttributes.PublicInstance)] public static object GetAddress(IPSocket self) { return GetAddressFromEndPoint(self.socket.LocalEndPoint); } [RubyMethod("peeraddr", RubyMethodAttributes.PublicInstance)] public static object GetPeerAddress(IPSocket self) { return GetAddressFromEndPoint(self.socket.RemoteEndPoint); } public static object GetAddressFromEndPoint(EndPoint endPoint) { RubyArray result = ArrayOps.CreateArray(); IPEndPoint ep = (IPEndPoint)endPoint; MutableString addressFamily = null; switch (ep.AddressFamily) { // Surely this already exists somewhere in System.Net? Couldn''t find it though... case AddressFamily.InterNetwork: addressFamily = new MutableString("AF_INET"); break; default: throw new NotImplementedException(); } result.Add(addressFamily); result.Add(ep.Port); result.Add(new MutableString(System.Net.Dns.GetHostEntry(ep.Address).HostName)); result.Add(new MutableString(ep.Address.ToString())); return result; } } [RubyClass("TCPSocket")] public class TCPSocket : IPSocket { [RubyMethod("gethostbyname", RubyMethodAttributes.PublicSingleton)] public static object GetHostByName(object self, MutableString hostName) { RubyArray result = ArrayOps.CreateArray(); System.Net.IPHostEntry hostEntry System.Net.Dns.GetHostEntry(hostName); result.Add(hostName); result.Add(ArrayOps.CreateArray(hostEntry.Aliases.Length)); foreach (string alias in hostEntry.Aliases) { ((RubyArray)result[1]).Add(new MutableString(alias)); } result.Add((int)hostEntry.AddressList[0].AddressFamily); result.Add(new MutableString(hostEntry.AddressList[0].ToString())); return result; } [RubyMethod("new", RubyMethodAttributes.PublicSingleton)] public static TCPSocket/*!*/ CreateSocket(CodeContext/*!*/ context, object self, BlockParam/*!*/ startRoutine, [NotNull]params object[]/*!*/ args) { return new TCPSocket(args[0], args[1]); } [RubyMethod("open", RubyMethodAttributes.PublicSingleton)] public static TCPSocket/*!*/ CreateSocketAlias_Open(CodeContext/*!*/ context, object self, BlockParam/*!*/ startRoutine, [NotNull]params object[]/*!*/ args) { return CreateSocket(context, self, startRoutine, args); } public TCPSocket(object hostname, object port) { socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); int portNumber = 0; if (port is int) { portNumber = (int)port; } else if (port is MutableString) { switch ((MutableString)port) { case "ftp": portNumber = 21; break; case "http": portNumber = 80; break; default: throw new NotImplementedException(); } } socket.Connect((MutableString)hostname, portNumber); } } }
Alternatively, you may prefer not to wrap the .NET type and to extend System.Net.Sockets.Socket into the DLR directly. This is inline with the extension of other .NET classes such as Int32 to Fixnum and MutableString to MutableString. The following code demonstrates how you could do this... using System; using System.Collections.Generic; using System.Text; using Ruby.Runtime; using System.Net.Sockets; using Ruby.Builtins; using Microsoft.Scripting; using System.Net; namespace Ruby.BuiltIns { [RubyClass("IPSocket", Extends=typeof(Socket))] public static class IPSocketOps { [RubyMethod("getaddress", RubyMethodAttributes.PublicSingleton)] public static string GetAddress(RubyClass klass, MutableString hostName) { return System.Net.Dns.GetHostEntry(hostName).AddressList[0].ToString(); } [RubyMethod("addr", RubyMethodAttributes.PublicInstance)] public static object GetAddress(Socket self) { return GetAddressFromEndPoint(self.LocalEndPoint); } [RubyMethod("peeraddr", RubyMethodAttributes.PublicInstance)] public static object GetPeerAddress(Socket self) { return GetAddressFromEndPoint(self.RemoteEndPoint); } public static object GetAddressFromEndPoint(EndPoint endPoint) { RubyArray result = ArrayOps.CreateArray(); IPEndPoint ep = (IPEndPoint)endPoint; MutableString addressFamily = null; switch (ep.AddressFamily) { // Surely this already exists somewhere in System.Net? Couldn''t find it though... case AddressFamily.InterNetwork: addressFamily = new MutableString("AF_INET"); break; default: throw new NotImplementedException(); } result.Add(addressFamily); result.Add(ep.Port); result.Add(new MutableString(System.Net.Dns.GetHostEntry(ep.Address).HostName)); result.Add(new MutableString(ep.Address.ToString())); return result; } } [RubyClass("TCPSocket", Inherits=typeof(IPSocketOps))] public static class TCPSocket : Object{ [RubyMethod("gethostbyname", RubyMethodAttributes.PublicSingleton)] public static object GetHostByName(object self, MutableString hostName) { RubyArray result = ArrayOps.CreateArray(); System.Net.IPHostEntry hostEntry System.Net.Dns.GetHostEntry(hostName); result.Add(hostName); result.Add(ArrayOps.CreateArray(hostEntry.Aliases.Length)); foreach (string alias in hostEntry.Aliases) { ((RubyArray)result[1]).Add(new MutableString(alias)); } result.Add((int)hostEntry.AddressList[0].AddressFamily); result.Add(new MutableString(hostEntry.AddressList[0].ToString())); return result; } [RubyMethod("new", RubyMethodAttributes.PublicSingleton)] public static Socket/*!*/ CreateSocket(CodeContext/*!*/ context, object self, BlockParam/*!*/ startRoutine, [NotNull]params object[]/*!*/ args) { object hostname = args[0]; object port = args[1]; Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); int portNumber = 0; if (port is int) { portNumber = (int)port; } else if (port is MutableString) { switch ((MutableString)port) { case "ftp": portNumber = 21; break; case "http": portNumber = 80; break; default: throw new NotImplementedException(); } } socket.Connect((MutableString)hostname, portNumber); return socket; } [RubyMethod("open", RubyMethodAttributes.PublicSingleton)] public static Socket/*!*/ CreateSocketAlias_Open(CodeContext/*!*/ context, object self, BlockParam/*!*/ startRoutine, [NotNull]params object[]/*!*/ args) { return CreateSocket(context, self, startRoutine, args); } } }