Wednesday, June 18, 2008

"TNS-12541: TNS:no listener" error proves no match for Super-useful Oracle commands

I'm running Oracle on my XP development box. After making a few changes to my network I found that Oracle no longer started. The first step to checking things was to use super-useful Oracle command #1: "tnsping". If you have the Oracle bin in your path, you can run this from anywhere:
tnsping mybase

TNS Ping Utility for 32-bit Windows: Version 10.2.0.1.0 - Production on 18-JUN-2
008 11:14:42

Copyright (c) 1997, 2005, Oracle. All rights reserved.

Used parameter files:
C:\oracle\product\10.2.0\db_1\network\admin\sqlnet.ora

Used TNSNAMES adapter to resolve the alias
Attempting to contact (DESCRIPTION = (ADDRESS = (PROTOCOL = TCP)(HOST = 10.0.1.1
98)(PORT = 1521)) (CONNECT_DATA = (SERVER = DEDICATED) (SERVICE_NAME = mybase)))

TNS-12535: TNS:operation timed out


So, first problem is that the host name I'm using is hard-coded in there to be "10.0.1.1". A quick check of "ipconfig /all" reveals that, with my network tinkering, my IP address has changed. In fact, I'm running XP on a virtual machine and not using a static IP address so the IP address was going to change sometime anyway. So we'll replace my IP address with my new IP address instead. To modify this setting, find your "tnsnames.ora" file; for me it lives at C:\oracle\product\10.2.0\db_1\network\admin\tnsnames.ora.

OK, so now a "tnsping" gives me the lovely "TNS-12541: TNS:no listener" message. Checking my Windows Services, I can see that the Oracle TNS Listener service (OracleOraDb10g_home1TNSListener) is stopped. If I try to start it I get a nice generic Windows Service message "Error 1067: The process terminated unexpectedly."

So, how do I find out what is happening with the listener? By using super-useful Oracle command #2: "lsnrctl". Again you can find this in your Oracle bin directory. You can now check the listener status via "lsnrctl status". Again, this revealed the same "10.0.1.198" IP address hard-coded in the listener config. So again we need to replace that with the new IP address also. The file that controls this is "listener.ora" and it lives in the same place as your "tnsnames.ora".

I found these needed to be done in a set order:
  1. Shutdown Oracle.
    sqlplus user/pwd as sysdba
    shutdown immediate

  2. Stop the listener.
    lsnrctl stop

  3. Change the IP addresses in *.ora files.


  4. Start the listener (notice that your database is not registered)
    lsnrctl start

  5. Start Oracle.
    sqlplus user/pwd as sysdba
    startup

  6. Check the db is registered to the listener.
    lsnrctl status

  7. Try to login via the tnsname.
    sqlplus user/pwd@mybase

So, to summarise, replace the hard-coded IP address with your new IP address in your "tnsnames.ora" and "listener.ora" files.

Note that I did try to use both "localhost" and "127.0.0.1" but couldn't get the listener to pick up the database√.

Tuesday, June 10, 2008

Debug into .Net from VB6

If you've got a VB6 app that is calling a .Net component, and you want to use the VS debugger on the .Net component then you need do the following:


  • Start the VB6 app

  • Set a break point in your .Net app

  • In VS, click on "Tools-Attach to Process..." and select the VB6 application/IDE. This will start your component running in VS.



Now, back in VB6, click the button (or do the action) that activates your .Net component. It will switch into the VS debugger on your breakpoint. Nice!

Be careful about which version of your .Net Dll is registered on the machine (release/debug). If you hav ethe relase one activated then it won't give you any debugging - it does warn you though. To get your debug one back, switch VS back to debug build, close your VB6 app/IDE, and rerun your .Net app. This will re-register the debug version.

For more details, try this.

Update: Very, very, VERY, VERY IMPORTANT!!!! You must enable "Managed Code" debugging when you attach to the process. So, in the "Attach to Process" window, you might have "Automatic: Native Code" showing. This didn't work for me - presumably because my Dll is managed, not native. So click the "Select..." button next to the "Attach to:" and choose "Managed" and "Native". Now attach to VB6.exe and all should work!

Thursday, June 5, 2008

C# Progress/Busy Window

I've been playing around with using a C# progress window. For testing I setup a project that takes three different approaches. I used a simple XML serialisation project as a base, and added in some sleep commands to make the serialisation command take about 10 seconds. The first approach uses a simple form (which doesn't work very nicely), then second uses a simple form with a background thread, and the third uses a form with a cancel button and a progress bar with a background thread.

The upshot is, that you need to use a BackgroundWorkerProcess. You need to encapsulate the stuff you want to do in the background into a single function; you need to identify where your bottleneck is. You send your job, that is going to take some time, to a background worker thread, and that keeps the UI responsive; it makes sure that your "I'm busy right now" window stays refreshed. There are two key things you need to do to get this to work:

1. Make sure that your work to be done in the background, doesn't try to touch the user interface. If you need to show progress then you can use the backgroundWorker.Progress method. Your background process should do its work and then return the data (via e.Result) which can then be populated onto the form if required.

2. Make sure you lock down the UI when you launch the work. The UI will remain active and you don't want the user to open a new screen or start the same update again etc, so you need to disable these buttons as required.

One thing you might want to do if you are waiting on a single network request (and therefore can't really determine % complete for a progress bar) is to display an animation - like some whirring cogs or some papers shuffling.

Wednesday, June 4, 2008

C# Converting a Class to XML

Using a class in C# to hold data is the right idea. XML is just a transport mechanism and should only be used at the boundaries of your application. So, as soon as you grab a chunk of XML from somewhere you should turn it into a class. Similarly, when you want to send some data somewhere, encapsulate the data in a class, and then serialise the class to XML. This way, if you decide to get rid of XML and use INI files, or CSV files, or YAML files then you don't need to change your core application - you only change the routines that serialise and deserialise the class.

Q. How to easily turn a class into XML?

A. For a simple class setup, you only need about four lines of code. For a user-defined class called "cls":


XmlSerializer serializer = new XmlSerializer(cls.GetType());
StringWriter writer = new StringWriter();
serializer.Serialize(writer, cls);
string xml = writer.GetStringBuilder().ToString();

Tuesday, June 3, 2008

CenterParent for a non-Modal Form in C#

I'm using a non-modal form to display a progress/busy/working dialog while a form is busy doing something. Unfortunately, the StartPosition=CenterParent, which works fine via Form.ShowDialog(), doesn't seem to work when a form is display via Form.Show(). I get around this by centering the form in the form_load method via:


private void frmBusy_Load(object sender, EventArgs e)
{
// centerparent not workin for a ".Show" form,
// so set position manually.
this.Location = new System.Drawing.Point(
this.Owner.Location.X +
(this.Owner.Width - this.Width) / 2,
this.Owner.Location.Y +
(this.Owner.Height - this.Height) / 2);
}