2007-2013
Why is my data not saved to my database? | Communicating between multiple forms | MSDN Data Walkthroughs
MSDN "How Do I?" Videos: VB | C#
VBForums Database Development FAQ
My CodeBank Submissions: VB (*NEW* Pausing Code) | C#
My Blog: Using Parameters in ADO.NET | Keyboard Events | Assemblies & Namespaces
Hi Jmc,
I am using your Server app to communicate with remote sensors.
This all works very well and I am able to receive data and send data to the remote sensors using the sendButton_Click function.
My requirement is to be able to send data to the remote sensors without user intervention.
How would I substitute the "Me.hostsComboBox.SelectedItem" for my own Remote IP and Port in the:
Dim host = DirectCast(Me.hostsComboBox.SelectedItem, HostInfo)
Best Regards
I'm guessing that hard-coding them into the app would be inappropriate. Generally this sort of thing is done by adding an entry to the config file and reading the data from there. You can then edit the config file by hand any time to change that data and thereby change how the app behaves. The simplest option for working with the config file is via the Settings page of the project properties. In your case, you'll probably want to use Application scope for one String and one Integer setting. If you then open App.config from the Solution Explorer you can see how they're stored.
2007-2013
Why is my data not saved to my database? | Communicating between multiple forms | MSDN Data Walkthroughs
MSDN "How Do I?" Videos: VB | C#
VBForums Database Development FAQ
My CodeBank Submissions: VB (*NEW* Pausing Code) | C#
My Blog: Using Parameters in ADO.NET | Keyboard Events | Assemblies & Namespaces
Hi jmc,
It would be impossible for me to hard-code the remote IP and Port into the app.
Let me explain:
All remote devices are on Dynamic IP addresses using GSM modems. Every time the GSM re-connects, the IP address changes.
What I am doing on the server side (running your app) is that each message that I receive from a remote device has the unique device ID, I then write the IP, Port and Unique ID to a database.
When I need to send a message to a particular remote device, I retrieve the IP and Port that corresponds to the Unique ID from the database, append the message and send.
One workaround could be reading the IP/Port from the database and setting the hostsComboBox.Text property to that value, the messageTextBox.Text property to the "message" and then invoke the sendButton_click event.
I have not tried this and it seems very cumbersome.
Best Regards
2007-2013
Why is my data not saved to my database? | Communicating between multiple forms | MSDN Data Walkthroughs
MSDN "How Do I?" Videos: VB | C#
VBForums Database Development FAQ
My CodeBank Submissions: VB (*NEW* Pausing Code) | C#
My Blog: Using Parameters in ADO.NET | Keyboard Events | Assemblies & Namespaces
Hey Jmc, have a quick question for you.
I'm attempting to use your library (which I've modified slightly) in a C# app but I cannot for the life of me figure out how to call or explicitly implement the event handlers for ConnectionAccepted, ConnectionClosed, and MessageReceived. Any ideas or advice?
I appreciate it, and thanks in advance,
RCG
2007-2013
Why is my data not saved to my database? | Communicating between multiple forms | MSDN Data Walkthroughs
MSDN "How Do I?" Videos: VB | C#
VBForums Database Development FAQ
My CodeBank Submissions: VB (*NEW* Pausing Code) | C#
My Blog: Using Parameters in ADO.NET | Keyboard Events | Assemblies & Namespaces
I transitioned from VB.NET to C# about a month ago, and I taught myself .NET/OOP in about the same amount of time, so naturally I have a few problem areas; those just happen to be generics and delegates (which event handlers sort of fall under). Anywho, I got it all fixed up. All I had to do was instantiate a server/client object, subscribe the event handlers to the object members in the class constructor using methods I wrote in the class, and it's good to go.
Thanks again for the help.
Hi all,
I'm looking for some guidance on just how to change the setup of this tcp messageserver so that i can set the port address for listening based on a parameter i read in from the command line eg Arg (1).
I can see that the constructors allow for passing in the port value, however, not sure how to make use of this in my formload method and still have the eventshandlers for ConnectionAccepted, MessageRecieved and ConnectionClosed work.
Thanks
Neil
Hey I am curious, could this be combined into one application that is both a server and a client?
Certainly. The definition of a server is a hardware or software entity that receives connection requests while a client is an entity that requests connections. It's quite possible for one machine or application to be both a client and a server. Exactly how it would be designed depends on exactly what the aim is but there's no issue with a single entity making and accepting connection requests.
2007-2013
Why is my data not saved to my database? | Communicating between multiple forms | MSDN Data Walkthroughs
MSDN "How Do I?" Videos: VB | C#
VBForums Database Development FAQ
My CodeBank Submissions: VB (*NEW* Pausing Code) | C#
My Blog: Using Parameters in ADO.NET | Keyboard Events | Assemblies & Namespaces
Look below -_- I'm a newbie on these forums. :X
Thanks for clearing that up for me! I seem to have done it in this application I made called P2P, but it is very slow and bugy. Do you think you could check out my source and fix it or tell me how I should go about fixing it? I would love to go the Asynchronous route, but I am not sure how to. I am currently using a timer to receive.
Source here:
https://www.dropbox.com/sh/p8racx1l7lqj3br/ZTqA6bxuIA
Hi JMc,
I'm using your sample code (v1.1.0.1) and looking to understand how to set/change the port for the listener (MessageServer) at run time rather than fixed in code.
Can you help me with this?
Thanks
Neil
2007-2013
Why is my data not saved to my database? | Communicating between multiple forms | MSDN Data Walkthroughs
MSDN "How Do I?" Videos: VB | C#
VBForums Database Development FAQ
My CodeBank Submissions: VB (*NEW* Pausing Code) | C#
My Blog: Using Parameters in ADO.NET | Keyboard Events | Assemblies & Namespaces
When a MessageServer object is implemented in C# your library throws a NullReferenceException at this line:
The error occurs when a client connects. I'm assuming the same error will occur for the rest of the event handlers.Code:Protected Overridable Sub OnConnectionAccepted(ByVal e As ConnectionEventArgs) Me._synchronisingContext.Post(AddressOf RaiseConnectionAccepted, e) End Sub
Hi JMc,
Seems I haven't been very clear with my question to you.
I do not have an issue with setting the port value, as it is nothing but an integer in the range of 0 to 65535. Equally, I can use the public property you have provided in your example code to read the current port value that the TCPListener is expecting TCP connections to arrive on.
I do know how to read an integer from a User using Textbox, NumericUpDown, also from application setting using My.Settings and how to read an integer from the command line using the args(i) string array... just to name a few.
If I can use an example from your own code, where you create the object named server in the class using the following statement
The object server is declared using port value of 12345 before any events are activated... as such server exists and is ready for use when the form is loading and for any other subsequent events to use.Code:Private WithEvents server As New MessageServer(12345)
For my application, I am reading from the command line a set of configuration items, which includes the port number that I need the TCPlistener to be operating on.
In this case it is contained in args(2) (exact code I'm using to read command line is below)
Where I am struggling is I can not see a way for me to set the actual port number to be something other value other than by manually changing this value before I build/compile this code.Code:Dim args() As String Try ' get commandline args args = Environment.GetCommandLineArgs sDataSource = args(1).ToUpper iListenerPort = CInt(args(2)) UpdateLog("Info: Command Line Arguments Loaded") Catch ex As Exception ' load defaults sDataSource = "E6410\SQLEXPRESS" iListenerPort = 6002 End Try
I can not simply apply a new port value in my form load event using the port parameter
eg
as it this parameter is readonly and there is no code in the MessageServer Class to support changing the port number.Code:server.Port(iListenerPort)
I have already attempted to declare the object in my Form load event using the following statement where iListenerPort = 6002 for testing.
And I've added the following event handlersCode:Dim server As New MessageServer(iListenerPort )
The problem I am finding here is that the server object is localised to this event and not accessible to other events, eg those referenced by the event handlersCode:AddHandler server.ConnectionAccepted, AddressOf server_ConnectionAccepted AddHandler server.MessageReceived, AddressOf server_MessageReceived AddHandler server.ConnectionClosed, AddressOf server_ConnectionClosed
Very specifically the Hosts object is empty when the server.send method is called.
So, returning to my original question, how do I declare an object (in my case TCPserver) as type MessageServer with a port value that I have read from the command line (using Args() string array) using the constructors that you have provided that is accessible to all other events in my application?
I'm thinking that maybe I need to be looking at how to add code to your MessageServer class to allow me to set the Port property and have the TCPListener (that already exists) stop, change port value and start.
Thanks
Neil
That would suggest that nothing is being assigned to the _synchronisingContext field, so you need to work out why. I doubt that it's got anything to do with C#. I think you'll find that SynchronizationContext.Current will return a null reference if you're not using it in either a WinForms or WPF application. Could that be the reason in your case?
2007-2013
Why is my data not saved to my database? | Communicating between multiple forms | MSDN Data Walkthroughs
MSDN "How Do I?" Videos: VB | C#
VBForums Database Development FAQ
My CodeBank Submissions: VB (*NEW* Pausing Code) | C#
My Blog: Using Parameters in ADO.NET | Keyboard Events | Assemblies & Namespaces
I'm using it in a WinForms application at the moment, so I'm sure that isn't the issue. As for working out why it's throwing that error I'm honestly at a loss. I'm running over it right now and it is showing null. I can connect fine (although I'm seeing 2-3 errors popping up due to the read/write methods, NullReferenceExceptions, IOException, ObjectDisposedException), I can close the server and it'll update all connected client's fine, I can restart the server and reconnect just fine, but when I go to close a client using client.Dispose() the server hangs on that method (OnConnectionClosed now).
If I remove client.Dispose(), the ConnectionClosed event will never be raised, even when the client application is stopped or shut down.
Last edited by yfzpurgatory; Dec 15th, 2012 at 11:21 PM.
Treeview with NodeAdded/NodesRemoved events | BlinkLabel control | Calculate Permutations | Object Enums | ComboBox with centered items | Create Sortable BindingList(not mine) | .Net Internals article(not mine) | Wizard Control | Understanding Multi-Threading
C++ programmers will dismiss you as a cretinous simpleton for your inability to keep track of pointers chained 6 levels deep and Java programmers will pillory you for buying into the evils of Microsoft. Meanwhile C# programmers will get paid just a little bit more than you for writing exactly the same code and VB6 programmers will continue to whitter on about "footprints". - FunkyDexter
There's just no reason to use garbage like InputBox. -jmcilhinney
2007-2013
Why is my data not saved to my database? | Communicating between multiple forms | MSDN Data Walkthroughs
MSDN "How Do I?" Videos: VB | C#
VBForums Database Development FAQ
My CodeBank Submissions: VB (*NEW* Pausing Code) | C#
My Blog: Using Parameters in ADO.NET | Keyboard Events | Assemblies & Namespaces
2007-2013
Why is my data not saved to my database? | Communicating between multiple forms | MSDN Data Walkthroughs
MSDN "How Do I?" Videos: VB | C#
VBForums Database Development FAQ
My CodeBank Submissions: VB (*NEW* Pausing Code) | C#
My Blog: Using Parameters in ADO.NET | Keyboard Events | Assemblies & Namespaces
I don't see why it wouldn't when something about C# is causing it not to function correctly. When I use the library in VB.NET 2010 everything works just fine; zero problems that I haven't fixed myself. When I try the same in C# 4 it's a completely different story. For some reason it just isn't jiving well on these lines:
Nothing has been changed, and I know I'm not going crazy. I've ran over the entire library multiple times, and I've very thoroughly went over both the read and send methods line by line to no avail. It has left me completely dumbfounded. The only event that is raised is MessageReceived; both ConnectionClosed and ConnectionAccepted fail, although the client connects.Code:Protected Overridable Sub OnConnectionAccepted(ByVal e As ConnectionEventArgs) Me._synchronisingContext.Post(AddressOf RaiseConnectionAccepted, e) End Sub
Last edited by yfzpurgatory; Dec 16th, 2012 at 11:52 PM.
Please stop talking about "C#" in this thread/section. If have a C# question post it here.
If this thread is finished with please mark it "Resolved" by selecting "Mark thread resolved" from the "Thread tools" drop-down menu.
Please consider giving me some rep points if I help you a lot.
DON'T BUMP YOUR POSTS!!! Links to my code examples can now be found on my website: My websites
Please rate my post if you find it helpful!
Technology is a dangerous thing in the hands of an idiot! I am that idiot.
FOREWORD: I opened my account on this forum today for two reasons; 1) to continue discussing a legitimate question in a reasonable tone, and more importantly, to say that this code is the most amazing code I have ever gotten off the internet. Seriously. I'm also a new guy to .net, coming from AmigaBasic (joke, vb6) 1 month ago. In my month of scouring the internet looking and learning to make test programs to learn the language, I have BY FAR learned more from this code than from anything else. Multiple programs from one solution, all while sharing and building a library, with all sorts of fancy commenting and region stuff I never knew existed. The library implements great into other programs, too! THANK YOU so much. I'm a big fan.
That said, I'm working in VB2010 (.net 4.0) and am bumping into a similar issue. The server (as far as I can tell) must be run from a form in order for the SynchronizationContext.Current to not return a null value.
Making a Windows Forms Application launched from sub main (to make it behave as similar as possible to module-based code), I put the following in a blank new form's code (it's basically just a stripped down version of the test server):
Clients can connect and talk, the server repeats the chat, and clients can even shut down the server with "exit". The server only has a single blank window running, which I guess could be hidden, but I what I originally wanted was to run the server from a module.Code:Module serverModule Public Sub main() serverWindow.Show() Application.Run() End Sub End Module Public Class serverWindow Public WithEvents server As New MessageServer(12345) Public ReadOnly hosts As New List(Of HostInfo) Private Sub server_ConnectionAccepted(ByVal sender As Object, ByVal e As Net.ConnectionEventArgs) Handles server.ConnectionAccepted hosts.Add(e.Host) End Sub Private Sub server_MessageReceived(ByVal sender As Object, ByVal e As Net.MessageReceivedEventArgs) Handles server.MessageReceived server.Send(e.Message) If e.Message = "exit" Then Me.Close() End Sub Private Sub server_ConnectionClosed(ByVal sender As Object, ByVal e As Net.ConnectionEventArgs) Handles server.ConnectionClosed Dim host = e.Host hosts.Remove(host) End Sub Private Sub MainWindow_FormClosed(ByVal sender As Object, ByVal e As System.Windows.Forms.FormClosedEventArgs) Handles Me.FormClosed server.Dispose() Application.Exit() End Sub End Class
A nearly identical module-based version:
Whenever a client joins, the same line "Me._synchronisingContext.Post(AddressOf RaiseConnectionAccepted, e)" says object reference not set to an instance of an object.Code:Module serverModule Public Sub main() Application.Run() End Sub Public WithEvents server As New MessageServer(12345) Public ReadOnly hosts As New List(Of HostInfo) Private Sub server_ConnectionAccepted(ByVal sender As Object, ByVal e As Net.ConnectionEventArgs) Handles server.ConnectionAccepted hosts.Add(e.Host) End Sub Private Sub server_MessageReceived(ByVal sender As Object, ByVal e As Net.MessageReceivedEventArgs) Handles server.MessageReceived server.Send(e.Message) If e.Message = "exit" Then shutdown() End Sub Private Sub server_ConnectionClosed(ByVal sender As Object, ByVal e As Net.ConnectionEventArgs) Handles server.ConnectionClosed Dim host = e.Host hosts.Remove(host) End Sub Private Sub shutdown() server.Dispose() Application.Exit() End Sub End Module
The same error occurs when I try to reproduce the same thing by running the server from a class (without a form). Yet the client seems to run great from a module.
So I guess the question is, would it, and if so, how would it be possible to run a server using your current library without it running from a form?
@drankof, I'm glad that you found the code useful. As I said, SynchronizationContext.Current returns Nothing when used in other than a Windows Forms or WPF app. That's because the whole point of the SynchronizationContext class is to allow you to execute code on the thread that owns a specific control. If you have no controls then it doesn't matter what thread you execute code on. If you don't want to use my library in a GUI app then you can get rid of all the stuff relating to the SynchronizationContext. If you want to be able to use it in both GUI and non-GUI apps then you'll need to do something like this:In non-GUI apps, some additional thread synchronisation may be required to ensure that actions occur in the appropriate sequence but that is beyond the scope of this thread.Code:If mySynchronizationContext Is Nothing Then 'Non-GUI app so call your method directly on the current thread. Else 'GUI app so invoke your method via the SynchronizationContext. Else
2007-2013
Why is my data not saved to my database? | Communicating between multiple forms | MSDN Data Walkthroughs
MSDN "How Do I?" Videos: VB | C#
VBForums Database Development FAQ
My CodeBank Submissions: VB (*NEW* Pausing Code) | C#
My Blog: Using Parameters in ADO.NET | Keyboard Events | Assemblies & Namespaces
Wow...it works by executing code on a thread that owns a control, which would need a form, that makes perfect sense. I think that just explained about 1/2 of what I didn't/don't understand regarding how the asynchronization/multithreading(/not pausing just to passively listen for people joining...not sure what word is appropriate) works on this server. Now I just need to finish investigating some of the other intricacies of threading, in general. Thank you so much and for the tip on how to implement it without a form/GUI.![]()
Great Work, lean programming!
I develop listeners quite a while for a GPRS project. Let me put a question on the table:
When a GPRS device connects to the listener it takes an IP address and a random port. Then the device looses the connection and later connects to listener with the same address and same port. What is happening ? So far the program crashes. In a previous listener I kept a list of ip addresses and manually I perform a check (if exists) before connection. Do you think any other way to prevent a "same port" situation ?
Best Regards
Vangelis
@jmcilhinney - I looked at your post in the code bank. I was wondering why you chose the ports you did? It seems that you should be using port numbers 49152–65535.
My First Computer --- Documentation Link (RT?M) --- Using the Debugger ---
"Those who use Application.DoEvents have no idea what it does and those who know what it does never use it." John Wein
"They who can give up essential liberty to obtain a little temporary safety, deserve neither liberty nor safety." Benjamin Franklin
Hi, I said a long time ago that it wasn't receiving packets in order... here is the case...
This is my modified MessageClient.Read:
vb Code:
Private Sub Read(ByVal ar As IAsyncResult) Try 'The stream will be Nothing if the client has been disposed. If Me.stream IsNot Nothing Then Dim buffer = DirectCast(ar.AsyncState, Byte()) 'Complete the asynchronous read and get the first block of data. Dim byteCount = Me.stream.EndRead(ar) If byteCount = 0 Then 'If there is no data when an asynchronous read completes it is because the server closed the connection. Me.OnConnectionClosed(New ConnectionEventArgs(Me.server)) Else 'Start building the message. 'Dim message As New StringBuilder(Me.Encoding.GetString(buffer, 0, byteCount)) OnDataChunkRecieved(New DataChunkRecievedEventArgs() With {.Chunk = buffer}) 'As long as there is more data... While Me.stream.DataAvailable '...read another block of data. byteCount = Me.stream.Read(buffer, 0, Me.BufferSize) OnDataChunkRecieved(New DataChunkRecievedEventArgs() With {.Chunk = buffer}) End While 'Listen asynchronously for another incoming message. Me.stream.BeginRead(buffer, 0, Me.BufferSize, AddressOf Read, buffer) 'Notify any listeners that a message was received. 'Me.OnMessageReceived(New MessageReceivedEventArgs(Me.server, message.ToString())) End If End If Catch ex As IOException 'The callback specified when BeginRead was called may get invoked one last time when the TcpClient is disposed. 'This exception is thrown when EndRead is called on a disposed client stream. End Try End Sub
I will try to describe this the best way i can ... but basically ... when there is no more data available in the Me.stream.DataAvailable loop ... because we have reached the end of the stream that the client has received sofar (but it is still sending such as a large file).. it will then come back to this sub again as the server is still sending ... and Dim buffer = DirectCast(ar.AsyncState, Byte()) then starts reading data from a random chunk... not necessarily the next chunk that was sent...
also ... if i change the code to this it works:
As this seems to make it slow down enough to receive the next chunk ... but this is not good of course as it slows it down ... and the data may take more than 100ms to get the next bit... it is just proof of concept...Code:While Me.stream.DataAvailable '...read another block of data. byteCount = Me.stream.Read(buffer, 0, Me.BufferSize) OnDataChunkRecieved(New DataChunkRecievedEventArgs() With {.Chunk = buffer}) 'wait for a second to see if more data is coming... System.Threading.Thread.Sleep(100) End While
Any ideas jmcilhinney?
Thanks,
Kris
Last edited by i00; Apr 17th, 2013 at 12:28 AM.
i00 .Net spell checker! - No 3rd party components required! (Code Project - "Best VB.NET article of October 2011")
Auto port forward for your UPnP router (i00 Nat Forwarder)
i00 .Net Image Filters
i00 BindingList with DataGridView - with LINQ filtering support!
My Other CodeBank Submissions: VB.Net
Please give kama if you found this post helpful
(click the scaleson the left <--)
hello? (*bump)
i00 .Net spell checker! - No 3rd party components required! (Code Project - "Best VB.NET article of October 2011")
Auto port forward for your UPnP router (i00 Nat Forwarder)
i00 .Net Image Filters
i00 BindingList with DataGridView - with LINQ filtering support!
My Other CodeBank Submissions: VB.Net
Please give kama if you found this post helpful
(click the scaleson the left <--)
2007-2013
Why is my data not saved to my database? | Communicating between multiple forms | MSDN Data Walkthroughs
MSDN "How Do I?" Videos: VB | C#
VBForums Database Development FAQ
My CodeBank Submissions: VB (*NEW* Pausing Code) | C#
My Blog: Using Parameters in ADO.NET | Keyboard Events | Assemblies & Namespaces
@i00
Your problem is the overall design. You should never depend on the socket implementation to tell you when a download is complete. Always design at least a minimum protocol. My favored method is attaching a prefix that at the start of a stream that tells me how many bytes to expect so I keep track of the number of bytes at the receiving end that we have received at any point. Using this method, one can determine when a download is complete.
Treeview with NodeAdded/NodesRemoved events | BlinkLabel control | Calculate Permutations | Object Enums | ComboBox with centered items | Create Sortable BindingList(not mine) | .Net Internals article(not mine) | Wizard Control | Understanding Multi-Threading
C++ programmers will dismiss you as a cretinous simpleton for your inability to keep track of pointers chained 6 levels deep and Java programmers will pillory you for buying into the evils of Microsoft. Meanwhile C# programmers will get paid just a little bit more than you for writing exactly the same code and VB6 programmers will continue to whitter on about "footprints". - FunkyDexter
There's just no reason to use garbage like InputBox. -jmcilhinney
OK ... But if I do this ... I still have a problem ... how do I wait for the rest of the data? because Me.stream.DataAvailable will return false but it will still be transmitting...
Edit: also this download also relies on the data not being long enough to be split into multiple packets
Kris
Last edited by i00; May 7th, 2013 at 12:19 AM.
i00 .Net spell checker! - No 3rd party components required! (Code Project - "Best VB.NET article of October 2011")
Auto port forward for your UPnP router (i00 Nat Forwarder)
i00 .Net Image Filters
i00 BindingList with DataGridView - with LINQ filtering support!
My Other CodeBank Submissions: VB.Net
Please give kama if you found this post helpful
(click the scaleson the left <--)
i00 .Net spell checker! - No 3rd party components required! (Code Project - "Best VB.NET article of October 2011")
Auto port forward for your UPnP router (i00 Nat Forwarder)
i00 .Net Image Filters
i00 BindingList with DataGridView - with LINQ filtering support!
My Other CodeBank Submissions: VB.Net
Please give kama if you found this post helpful
(click the scaleson the left <--)
I'm currently putting together two small applications to demonstrate a good way to handle file transfers over TCP/IP using Winsock through the TcpListener/TcpClient classes. I'll post it in the code bank when I'm done. Hopefully it would make things a little clearer.
Treeview with NodeAdded/NodesRemoved events | BlinkLabel control | Calculate Permutations | Object Enums | ComboBox with centered items | Create Sortable BindingList(not mine) | .Net Internals article(not mine) | Wizard Control | Understanding Multi-Threading
C++ programmers will dismiss you as a cretinous simpleton for your inability to keep track of pointers chained 6 levels deep and Java programmers will pillory you for buying into the evils of Microsoft. Meanwhile C# programmers will get paid just a little bit more than you for writing exactly the same code and VB6 programmers will continue to whitter on about "footprints". - FunkyDexter
There's just no reason to use garbage like InputBox. -jmcilhinney
Ok, I've posted two sample applications that show how to transfer a file between a client and a server using WinSock. They should give you an idea about to detect completion of data transfers. Here is the Code Bank thread.
Treeview with NodeAdded/NodesRemoved events | BlinkLabel control | Calculate Permutations | Object Enums | ComboBox with centered items | Create Sortable BindingList(not mine) | .Net Internals article(not mine) | Wizard Control | Understanding Multi-Threading
C++ programmers will dismiss you as a cretinous simpleton for your inability to keep track of pointers chained 6 levels deep and Java programmers will pillory you for buying into the evils of Microsoft. Meanwhile C# programmers will get paid just a little bit more than you for writing exactly the same code and VB6 programmers will continue to whitter on about "footprints". - FunkyDexter
There's just no reason to use garbage like InputBox. -jmcilhinney