Tuesday, December 1, 2009

Type conversion of C++ and C#

In C++, type conversion can access member variable. However, in C#, type conversion can not. Every time we cast a type A object to Type B, a new object of Type B will be created.

The following is how type conversion operator is implemented in c++.

=========================================================

#include "stdafx.h"


class TypeA
{
public: 
int a;
   char b;

TypeA& TypeA::operator=(const double d)
{
this->a = d;
return *this;
}


operator const double ()
    {
return this->a;

    }



};

int _tmain(int argc, _TCHAR* argv[])
{
TypeA a;
a=3;
a.a = 5;
double d =(double)a;

return 0;
}

=========================================================


The following sample shows how to implement type conversion in c#

========================================================

using System;
using System.Collections.Generic;
using System.Text;

namespace ConsoleApplication1
{
class TypeA
{
public int a;
public int b;

public static implicit operator TypeA( double d )
{
TypeA TypeA = new TypeA();
TypeA.a = (int)d;
return TypeA;
}

public static implicit operator double( TypeA TypeA )
{
return TypeA.a; 
}



}

class Program
{
static void Main( string[] args )
{
TypeA a = 2.5;

double b = a;

}
}
}


==========================================================

Wednesday, October 21, 2009

listview imagelist resource leakage

ListView may leak with ImageLists


I have worked with the issue for several days. Basically as long as I set the state image list for a list view, I will end up with a big GDI Object leakage and eventually the listview will not be able to work properly.

Following article describes the issue more clearly.



Tuesday, October 13, 2009

Use Web Browser control to print and preview a html document

The following article provides the idea to print preview using web browser.

http://www.developmentnow.com/g/36_2003_7_0_0_192939/Preview-control.htm

Make sure the web browser control is visible and in a windows form.

=======================

You can use COM object Microsoft Web Browser (customize your toolbox)
After that put the web browser control to form.

In your code it looks like:
private AxSHDocVw.AxWebBrowser axWebBrowser1;

You can get HTML content and show it so:
object flags=null;
object postData=null;
object header=null;
object targetFrame=null;
axWebBrowser1.Navigate("www.namip.ru", ref flags, ref targetFrame, ref 
postData, ref header);

When the fetching will be finished you can do so:
object pv = null;
object pva = null;
axWebBrowser1.ExecWB(SHDocVw.OLECMDID.OLECMDID_PRINTPREVIEW,SHDocVw.OLECMDEXECOPT.OLECMDEXECOPT_DODEFAULT,ref
pv, ref pva);

===========================

We can also use the IE browser to directly perform the task.

================================


                SHDocVw.InternetExplorer browser = new SHDocVw.InternetExplorerClass();
browser .Visible=true;

                Object obj = new Object();

                browser.Navigate(strPath, ref obj, ref obj, ref obj, ref obj);

                //wait until the DHTMLEdit control load the whole document
                for (; browser.Busy != false; )
                {
                    System.Windows.Forms.Application.DoEvents();
                }

                mshtml.IHTMLDocument2 doc = browser.Document as mshtml.IHTMLDocument2;
SHDocVw.OLECMDF cmd = browser.QueryStatusWB( SHDocVw.OLECMDID.OLECMDID_PRINTPREVIEW );

//IOleCommandTarget ct = ( IOleCommandTarget ) doc;

if ( bPrintPreview == true )
{
object pvaIn;

if ( strPath == string.Empty )
{
pvaIn = null;
}
else if ( ! System.IO.File.Exists( strPath ) )
{
pvaIn = null;
}
else
{
pvaIn = strPath;
}
Object pvaout = null;

object pv = null;
object pva = null;
ie.ExecWB( SHDocVw.OLECMDID.OLECMDID_PRINTPREVIEW, SHDocVw.OLECMDEXECOPT.OLECMDEXECOPT_DODEFAULT, ref
pv, ref pva );
}
else
{
doc.execCommand( "Print", true, 0 );
}


================================

Friday, September 25, 2009

How to create a trigger to insert a record in a backup table when a record is modified or inserted in one table

The following is a quick example that works on SQL Server 7.0

set ANSI_NULLS ON
set QUOTED_IDENTIFIER ON
GO
Create TRIGGER [dbo].[trgInsertUpdatePrice]
ON [dbo].[Prices]
After INSERT, Update
AS
        INSERT INTO Prices_log
select * from Inserted

Thursday, September 24, 2009

Open Source Bug track software in ASP.Net

I am trying to find some good open source asp.net bug track software. I found the following article to be a good start point.


The following page is the bug track.net page. It is a very nice bug track system.


Tuesday, September 22, 2009

How to trace down memory leakage in C++ code

Following link provide a good method.

http://msdn.microsoft.com/en-us/library/4wth1ha5(VS.80).aspx

For example, if following memory leaks were detected: (Check the output window of VS after you close application in debug mode)

Detected memory leaks!
Dumping objects ->
f:\dd\vctools\vc7libs\ship\atlmfc\src\mfc\strcore.cpp(141) : {214137} normal block at 0x06C6B2B8, 42 bytes long.
 Data: <, ?x            > 2C 08 3F 78 19 00 00 00 19 00 00 00 01 00 00 00 
f:\dd\vctools\vc7libs\ship\atlmfc\src\mfc\strcore.cpp(141) : {214136} normal block at 0x06C6B3A8, 91 bytes long.
 Data: <, ?xJ   J       > 2C 08 3F 78 4A 00 00 00 4A 00 00 00 01 00 00 00 
f:\dd\vctools\vc7libs\ship\atlmfc\src\mfc\strcore.cpp(141) : {214116} normal block at 0x06C6B258, 30 bytes long.
 Data: <, ?x            > 2C 08 3F 78 0D 00 00 00 0D 00 00 00 01 00 00 00 
{214115} normal block at 0x06C6B208, 20 bytes long.
 Data: <                > B0 AA C6 06 00 AD C6 06 B0 A9 C6 06 00 AE C6 06 


You can put following code at the start of the application. Make sure it is called before the allocation happened. 

int allocateNo = 214137;

_CrtSetBreakAlloc(allocateNo);
 _crtBreakAlloc = allocateNo;

allocateNo = 214136;
_CrtSetBreakAlloc(allocateNo);
 _crtBreakAlloc = allocateNo;

allocateNo = 214116;
_CrtSetBreakAlloc(allocateNo);
 _crtBreakAlloc = allocateNo;

allocateNo = 214115;
_CrtSetBreakAlloc(allocateNo);
 _crtBreakAlloc = allocateNo;

Now when you start application in debug mode, VS will break when the memory that is not properly freed is allocated and you will be able to trace down the object that need to be properly handled.

Thursday, July 23, 2009

webbrowser control DocumentCompleted triggered multiple times

I am struggling with the documentCompleted event of web browser control for quite a little while.

There are many posts about how to decide whether the whole page has been loaded. Unfortunately none of them worked.

The following is one sample.


With following page, the event has never been triggered with the parameter url equals to the browser url.


http://www.canadiantire.ca/browse/product_detail.jsp?PRODUCT<>prd_id=845524441893435&FOLDER<>folder_id=1408474396672077&bmUID=1230691825032

Some people suggest to use a indes in the control navigating event because everytime a new document downloading will trigger the navigating event. However,

The indicator never reaches 0 if you add one to the index in navigating event and minus 1 in the document completed event.

I have also tried the browser ready state and check whether the browser has a ready state equal to Complete.

        private void timer1_Tick(object sender, EventArgs e)
        {

            if (webBrowser1.ReadyState == WebBrowserReadyState.Complete)
            {
                ...

                timer1.Stop();
            }
        }


for the above web page, the ready state changed from Uninitialized to downloading and the interactive and stays as interactive forever.

So far I have not found a valid way to detect whether a page has been fully loaded.



Thursday, July 16, 2009

Cross-thread Operation Not Valid

I am working on a problem that UI response is very slow because the application has to initialize a collection of very complex grid view and the process will take as long as 8 seconds. I am trying to create another thread to initialize the grid views in a parallel thread. Then I came across the problem of cross thread operation not valid error because .Net does not allow the form controls to be updated in a thread other than the thread to create it.

The following is a solution that I can found from web.

http://www.astahost.com/info.php/Needed-Cross-thread-Operation-Valid_t15008.html

However, I noticed that even I can remove the exception, I can not solve the problem of performance because the time consuming operation is still executed in the main thread with invoke.

The following is the code.

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Timers;

namespace UIThread
{

class SampleThread
{
public delegate void TestEventHandler(string sParameter);
public event TestEventHandler TestEvent;

protected System.Timers.Timer TestTimer = new System.Timers.Timer();
protected ISynchronizeInvoke SyncObject;

public SampleThread(ISynchronizeInvoke SyncObject)
{
this.SyncObject = SyncObject;
TestTimer.Elapsed += new ElapsedEventHandler(Tick);
TestTimer.Interval = 300;
}

protected delegate void TickDelegate(object source, ElapsedEventArgs e);
private void Tick(object source, ElapsedEventArgs e)
{
if (SyncObject.InvokeRequired)
{
SyncObject.BeginInvoke(new TickDelegate(Tick), new object[] { source, e });

//the following funciton are executed in the parellel thread
string sParameter = "";
for (int i = 0; i < 2000000; i++)
{
sParameter = sParameter + (i * i * i).ToString();
}
}
else
{
TestEvent(DateTime.Now.ToString());
}
}

public void Go()
{
TestTimer.Enabled = true;
}
}

public partial class Form1 : Form
{
SampleThread sampleThread;

public Form1()
{
InitializeComponent();
sampleThread = new SampleThread(this);
sampleThread.TestEvent += new SampleThread.TestEventHandler(FunctionInMainThread);
}

void FunctionInMainThread(string sParameter)
{
//basically this funciton is called from other thread by executed in main thread.
this.Text = sParameter;

//If following lines are turned on, the UI will become very slow in response.
//for (int i = 0; i < 2000000; i++)
//{
// sParameter = sParameter + (i * i * i).ToString();
//}
}


private void Form1_Load(object sender, EventArgs e)
{
sampleThread.Go();
}
}
}

Friday, July 3, 2009

Communication between managed c++ dll and C# form application

Step 1:

Create a new C++ dynamic library project by default and name it as MCDLL. The default class will be as following.

First file:

// MCDll.h

 

#pragma once

 

using namespace System;

 

namespace MCDll {

 

       public ref class Class1

       {

              // TODO: Add your methods for this class here.

       };

}

 

Second file:

// This is the main DLL file.

 

#include "stdafx.h"

 

#include "MCDll.h"

 

 

 

Step 2:

Now we can add a class in the empty dll as following

// MCDll.h

 

#pragma once

 

using namespace System;

 

namespace MCDll {

 

       public ref class Class1

       {

              // TODO: Add your methods for this class here.

 

       public:

              array<double>^ m_adTestArray;     // array that can be passed to C#

 

              //function to return double array

              array<double>^ GetTestArray(int iLength);

 

       };

}

Second file:

 

// This is the main DLL file.

 

#include "stdafx.h"

 

#include "MCDll.h"

 

array<double>^

MCDll::

Class1::

GetTestArray(int iLength)

{

       m_adTestArray = gcnew array<double>(iLength);

 

       for(int i=0;i<iLength;i++)

       {

              m_adTestArray[i] = i*i*i;

       }

       return m_adTestArray;

}

Compile the MCDLL project and  you will see how it works.

Step 3:

Now we can create a simple window Form application with C#, and the project MCDll as a project reference to our C# form application in order to access the function defined in C++.

The following is a simple code to demo the use of the c++ class.

 

using System;

using System.Collections.Generic;

using System.ComponentModel;

using System.Data;

using System.Drawing;

using System.Text;

using System.Windows.Forms;

 

namespace CSApp

{

      public partial class Form1 : Form

      {

            public Form1()

            {

                  InitializeComponent();

            }

 

            private void button1_Click( object sender, EventArgs e )

            {

                  MCDll.Class1 class1 = new MCDll.Class1();

 

                  double[] adFromC = null;

                  adFromC = class1.GetTestArray( 5 );

 

                  for ( int i = 0; i < adFromC.Length; i++ )

                  {

                        Console.WriteLine( adFromC[ i ].ToString() );

                  }

 

            }

      }

}

 

Compile above application and execute it, we will see that the double array is initialized in C++ dll but is accessible from C# application.

 

A few more cases are demoed here to show how to pass parameters by reference, how to return a dimensional array back and how to return a string array. Full code can be found at following

 

// MCDll.h

 

#pragma once

 

using namespace System;

 

namespace MCDll {

 

       public ref class Class1

       {

              // TODO: Add your methods for this class here.

 

       private:

              array<double>^ m_adTestArray;     // array that can be passed to C#

              array<double,3>^ m_adDimensionalArray;

 

       public:

              //function to return double array

              array<double>^ GetTestArray(int iLength);

 

              //funciton to return a string array

              array<System::String^>^ GetTestStringArray( int iLength );

 

              //function to return double array with dimension

              array<double,3>^ GetMultiDimensionArray(int iLength);

 

              //pass parameter by reference

              double  PassParameterByReference( double dInput, double% dOutput1, double% dOutput2);

 

              //pass parameter by reference

              double  PassParameterByReference2( double dInput, double% dOutput1, double% dOutput2);

 

 

       };

}

 

 

// This is the main DLL file.

 

#include "stdafx.h"

 

#include "MCDll.h"

 

array<double>^

MCDll::

Class1::

GetTestArray(int iLength)

{

       m_adTestArray = gcnew array<double>(iLength);

 

       for(int i=0;i<iLength;i++)

       {

              m_adTestArray[i] = i*i*i;

       }

       return m_adTestArray;

}

 

array<System::String^>^

MCDll::

Class1::

GetTestStringArray(int iLength )

{

       array<System::String^>^ astrArray = gcnew array<System::String^>(iLength);

 

       for ( int i = 0; i < iLength; i++ )

       {

              System::String^ str;

 

              str = gcnew System::String( i.ToString() );

 

              astrArray[i] = str; 

       }

      

       return astrArray;

}

 

 

array<double, 3>^

MCDll::

Class1::

GetMultiDimensionArray(int iLength )

{

       m_adDimensionalArray = gcnew array<double,3>(iLength, iLength, iLength);

       for(int i=0;i<iLength;i++)

       {

              for(int j=0;j<iLength;j++)

              {

                     for(int k=0;k<iLength;k++)

                     {

                           m_adDimensionalArray[i,j,k]=i*100+j*10+k;

                     }

              }

       }

 

       return m_adDimensionalArray;

}

 

 

// pass parameters by reference

double 

MCDll::

Class1::

PassParameterByReference( double dInput, double% dOutput1, double% dOutput2)

{

       dOutput1 =dInput * dInput;

       dOutput2 =dInput * dInput *2;

 

       return PassParameterByReference2(dInput,dOutput1,dOutput2);

 

}

 

// pass parameters by reference and called

double 

MCDll::

Class1::

PassParameterByReference2( double dInput, double% dOutput1, double% dOutput2)

{

       dOutput1 =dInput * dInput * dInput;

       dOutput2 =dInput * dInput * dInput*2 ;

 

       return dOutput2 *2;

 

}

 

The following code shows how to call the dll functions.

              private static void TestFunctions()

              {

                     MCDll.Class1 class1 = new MCDll.Class1();

 

                     double[] adFromC = null;

                     adFromC = class1.GetTestArray( 5 );

 

                     for ( int i = 0; i < adFromC.Length; i++ )

                     {

                           Console.WriteLine( adFromC[ i ].ToString() );

                     }

 

                     string[] astrArray = class1.GetTestStringArray( 10 );

 

                     for ( int i = 0; i < astrArray.Length; i++ )

                     {

                           Console.WriteLine( astrArray[ i ] );

                     }

 

 

                     int iLength = 5;

                     double[ , , ] adDimensionArray = class1.GetMultiDimensionArray( iLength );

 

                     for ( int i = 0; i < iLength; i++ )

                     {

                           for ( int j = 0; j < iLength; j++ )

                           {

                                  for ( int k = 0; k < iLength; k++ )

                                  {

                                         Console.Write( adDimensionArray[ i, j, k ] );

                                         Console.Write( "\t" );

                                  }

                                  Console.Write( "\n" );

                           }

                           Console.Write( "\n" );

                     }

 

                     double dInput = 3.0;

                     double dOutput1 = 0;

                     double dOutput2 = 0;

                    double dReturn = class1.PassParameterByReference( dInput, ref dOutput1, ref dOutput2 );

                     Console.WriteLine( "Input: " + dInput.ToString() );

                     Console.WriteLine( "dOutput1: " + dOutput1.ToString() );

                     Console.WriteLine( "dOutput2: " + dOutput2.ToString() );

                     Console.WriteLine( "dReturn: " + dReturn.ToString() );

              }

Thursday, July 2, 2009

Use the free REST web service to extact geo information from IP address

The following sample provides a class to extract geo information from any IP address using a completely free web service provided by www.IPInfoDB.com.

This is the code for a simple windows form application in C#. The class IPInfor will construct itself from an IP address and return available attributes as properties.



using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using System.IO;
using System.Net;
using System.Xml;

namespace test
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}

/// <summary>
/// A class respresent the data structure from IPInfoDB REST web service
/// </summary>
public class IPInfor
{
private string m_sIP;
private string m_sCountryCode;
private string m_sCountryName;
private string m_sRegionCode;
private string m_sRegionName;
private string m_sCityName;
private string m_sZipPostCode;
private double m_dLatitude;
private double m_dLongigitude;
private double m_dGmtoffset;
private double m_dDstoffset;


public string IP
{
get
{
return m_sIP;
}
}

public string CountryCode
{
get
{
return m_sCountryCode;
}
}

public string CountryName
{
get
{
return m_sCountryName;
}
}

public string RegionCode
{
get
{
return m_sRegionCode;
}
}

public string RegionName
{
get
{
return m_sRegionName;
}
}

public string CityName
{
get
{
return m_sCityName;
}
}

public string ZipPostCode
{
get
{
return m_sZipPostCode;
}
}

public double Latitude
{
get
{
return m_dLatitude;
}
}

public double Longigitude
{
get
{
return m_dLongigitude;
}
}

public double Gmtoffset
{
get
{
return m_dGmtoffset;
}
}

public double Dstoffset
{
get
{
return m_dDstoffset;
}
}



///
/// return the orignal REST response returned by "http://ipinfodb.com/ip_query.php?ip=" + sIP;
///
///
/// the original response string from web service based on the
private static string ShowOriginalResponse( StreamReader streamReader, bool bNoTranslation )
{
//directly return it;
if ( bNoTranslation == true )
{
return streamReader.ReadToEnd();
}

using ( XmlTextReader reader = new XmlTextReader( streamReader ) )
{
StringBuilder sb = new StringBuilder();
sb.Append( "\n ================ Start ===========\n" );
while ( reader.Read() )
{
switch ( reader.NodeType )
{
case XmlNodeType.Element: // The node is an Element.
sb.Append( "<" + reader.Name );

while ( reader.MoveToNextAttribute() ) // Read attributes.
sb.Append( " " + reader.Name + "='" + reader.Value + "'" );
sb.Append( ">" );
sb.Append( ">\n" );
break;
case XmlNodeType.Text: //Display the text in each element.
sb.Append( reader.Value );
sb.Append( "\n" );
break;
case XmlNodeType.EndElement: //Display end of element.
sb.Append( "<"+"/" + reader.Name );
sb.Append( ">\n" );
break;
}
}
sb.Append( "\n ================ End ===========\n" );

return sb.ToString();
}
}


//extract REST response to the IPInfor data structure
private void GetDataFromStreamReader( StreamReader streamReader )
{
XmlTextReader reader = new XmlTextReader( streamReader );
string sCurrentName = "";
while ( reader.Read() )
{
switch ( reader.NodeType )
{
case XmlNodeType.Element: // The node is an Element.

while ( reader.MoveToNextAttribute() )
{
}// Read attributes.
sCurrentName = reader.Name;
break;
case XmlNodeType.Text: //Display the text in each element.
switch ( sCurrentName )
{
case "Ip":
m_sIP = reader.Value;
break;
case "CountryCode":
m_sCountryCode = reader.Value;
break;
case "CountryName":
m_sCountryName = reader.Value;
break;
case "RegionCode":
m_sRegionCode = reader.Value;
break;
case "RegionName":
m_sRegionName = reader.Value;
break;
case "City":
m_sCityName = reader.Value;
break;
case "ZipPostalCode":
m_sZipPostCode = reader.Value;
break;
case "Latitude":
try
{
m_dLatitude = Convert.ToDouble( reader.Value );
}
catch ( Exception eee )
{
m_dLatitude = 0;
}
break;
case "Longitude":
try
{
m_dLongigitude = Convert.ToDouble( reader.Value );
}
catch ( Exception eee )
{
m_dLongigitude = 0;
}
break;
case "Gmtoffset":
try
{
m_dGmtoffset = Convert.ToDouble( reader.Value );
}
catch ( Exception eee )
{
m_dGmtoffset = 0;
}
break;
case "Dstoffset":
try
{
m_dDstoffset = Convert.ToDouble( reader.Value );
}
catch ( Exception eee )
{
m_dDstoffset = 0;
}
break;
default:
break;
}
break;
case XmlNodeType.EndElement: //Display end of element.
break;
}
}
}

//default constructor
public IPInfor()
{
}

//get original REST response from
public static string ShowOriginalResponse( string sIP, bool bNoTranslation )
{

try
{
string sQueryMain = "http://ipinfodb.com/ip_query.php?ip=" + sIP;
HttpWebRequest request = WebRequest.Create( sQueryMain ) as HttpWebRequest;

// Get response
using ( HttpWebResponse response = request.GetResponse() as HttpWebResponse )
{
// Get the response stream
StreamReader reader = new StreamReader( response.GetResponseStream() );
return ShowOriginalResponse( reader, bNoTranslation );
}
}
catch ( Exception eee )
{
//using backup service
string sQueryBackup = "http://backup.ipinfodb.com/ip_query.php?ip=" + sIP;
HttpWebRequest request = WebRequest.Create( sQueryBackup ) as HttpWebRequest;

// Get response
using ( HttpWebResponse response = request.GetResponse() as HttpWebResponse )
{
// Get the response stream
StreamReader reader = new StreamReader( response.GetResponseStream() );
return ShowOriginalResponse( reader, bNoTranslation );
}
}
}

//contruct an IPInfor class from IP address using REST web service from IPInforDB
public IPInfor( string sIP )
{

try
{
string sQueryMain = "http://ipinfodb.com/ip_query.php?ip=" + sIP;
HttpWebRequest request = WebRequest.Create( sQueryMain ) as HttpWebRequest;

// Get response
using ( HttpWebResponse response = request.GetResponse() as HttpWebResponse )
{
// Get the response stream
StreamReader reader = new StreamReader( response.GetResponseStream() );
GetDataFromStreamReader( reader );
}
}
catch ( Exception eee )
{
//using backup service
string sQueryBackup = "http://backup.ipinfodb.com/ip_query.php?ip=" + sIP;
HttpWebRequest request = WebRequest.Create( sQueryBackup ) as HttpWebRequest;

// Get response
using ( HttpWebResponse response = request.GetResponse() as HttpWebResponse )
{
// Get the response stream
StreamReader reader = new StreamReader( response.GetResponseStream() );
GetDataFromStreamReader( reader );
return;
}
}

}
}

private void button1_Click( object sender, EventArgs e )
{
//us ip
//string sIP = "74.125.45.100";

string sIP = "202.90.224.90"; //china ip http://www.adsensepublish.com/china-ip-address.html
sIP = "217.8.101.90"; //china ip http://www.adsensepublish.com/china-ip-address.html
sIP = "219.242.247.90"; //china ip http://www.adsensepublish.com/china-ip-address.html
sIP = "59.107.0.127"; //china ip http://www.adsensepublish.com/china-ip-address.html
//local ip
//string sIP = "";

string sQueryMain = "http://ipinfodb.com/ip_query.php?ip=" + sIP;
string sQueryBackup = "http://backup.ipinfodb.com/ip_query.php?ip=" + sIP;

Console.Write( IPInfor.ShowOriginalResponse( sIP, true ) );
//Console.Write( IPInfor.ShowOriginalResponse( sIP, false ) );
IPInfor ipInfo = new IPInfor( sIP );
Console.WriteLine( ipInfo.IP );
Console.WriteLine( ipInfo.CountryName );
Console.WriteLine( ipInfo.CountryCode );
Console.WriteLine( ipInfo.RegionName );
Console.WriteLine( ipInfo.RegionCode );
Console.WriteLine( ipInfo.CityName);
Console.WriteLine( ipInfo.Latitude );
Console.WriteLine( ipInfo.Longigitude);
Console.WriteLine( ipInfo.ZipPostCode );
Console.WriteLine( ipInfo.Gmtoffset );
Console.WriteLine( ipInfo.Dstoffset);

}
}
}