Tutorial: How to Get a List of Available Network Interfaces

Posted by Matt | Filed under , , , ,

If you're creating a network application, often you need to know if you have an active network connection to a LAN.  (Note that this is not the same thing as determining if you have a valid Internet connection.)  Winsock has a function which will let you get a list of available network interfaces:  WSAIoctl.

Using WSAIoctl, you can get a list of interfaces on the computer as well as some status information about them.  For example, it will tell you if the connection is up, loopback, point-to-point, supports multicasting, or supports broadcasting.

To get the list, you would use:

INTERFACE_INFO interfaces[32];
unsigned long nReturned = 0;
int nRet = WSAIoctl(s, // socket handle
    SIO_GET_INTERFACE_LIST, 
    0, 
    0, 
    &interfaces,
    sizeof(INTERFACE_INFO) * 32, 
    &nReturned, 
    0, 
    0);

if (nRet == SOCKET_ERROR) 
{
    int nError = WSAGetLastError();
    _tprintf(_T("Error getting interface list: %i\n"), nError);
    return;
}

After this, your list of interfaces will be in the array and the number of interfaces can be determined by:

int nNumInterfaces = nReturned / sizeof(INTERFACE_INFO);

From here, you can iterate through the interfaces checking for a connected interface.

for (int i = 0; i < nNumInterfaces; ++i) 
{
    INTERFACE_INFO *pIf = &interfaces[i];
    if ((pIf->iiFlags & IFF_LOOPBACK) != 0)
        continue;

    if ((pIf->iiFlags & IFF_UP) == 0)
        continue;

    // If you get here, you have a valid
    // interface to the LAN.
}

In the above case, we're looking for an interface that is up and not a loopback.

There is some other useful information in the INTERFACE_INFO structure:  ip address of the interface, netmask, broadcast mask, whether the connection is point-to-point, and supports multicasting or broadcasting.

I will reiterate that this does not tell you if you are connected to the Internet, nor will it tell you whether there's anything on the other end to communicate with.

Tutorial: Reading and Writing Files in Silverlight

Posted by Matt | Filed under , , ,

Note: This article as some inaccuracies.  Please read this follow up article for additional information: Reading Local Files in Silverlight

It's a pair of very easy questions:

How do I write to the hard drive using Silverlight?

and

How do I read from the hard drive using Silverlight?

The answer is also very easy.  You can't. [Matt: This statement is incorrect.]

However, there is an alternative, which may be good enough for many people.

Silverlight enables local storage in an isolated area.  This means that you can store files locally on the user's computer, but not in their normal file system.  Microsoft sets aside a special set of files for Silverlight applications.  It's almost like a private file system per Silverlight application.  So each application has access to it's files, and only it's files.  You cannot cross-access files between Silverlight applications, nor can you read/write to the user's normal file system. [Matt: This paragraph has some inaccuracies.]

Now, if this isn't stopping you, here's how you read and write files.

There's a namespace available to Silverlight applications called System.IO.IsolatedStorage.  This namespace has some special classes used to access files in Silverlight.

First, you need to access an IsolatedStorageFile.  This is done by calling IsolatedStorageFile.GetUserStoreForApplication() or IsolatedStorageFile.GetUserStoreForSite().  There are some differences between the functions, but either will get you a private file system in which you can read and write files.

Next, you need create or open your file almost as you would normally.  Instead of using FileStream, you would use IsolatedStorageFileStream (which is derived from FileStream).  This means that once you have your stream, you can use it anywhere you would have normally used a normal FileStream.

Next, you read or write as you normally would.

Here is an example of creating a text file and then reading from it.

// Get the storage file for the application
IsolatedStorageFile isf =
    IsolatedStorageFile.GetUserStoreForApplication();

// Open/Create the file for writing
Stream stream = new IsolatedStorageFileStrea("Silverlight.txt",
    FileMode.Create, FileAccess.Write, isf);

// Use the stream normally in a TextWriter
TextWriter writer = new StreamWriter(stream);
writer.WriteLine("Written to isolated storage");
writer.Close(); // Close the writer so data is flushed
stream.Close(); // Close the stream too

// Open the file for reading
stream = new IsolatedStorageFileStream("Silverlight.txt",
    FileMode.Open, FileAccess.Read, isf);

// Use the stream normally in a TextReader
TextReader reader = new StreamReader(stream);
string sLine = reader.ReadLine();
reader.Close(); // Close the reader
stream.Close(); // Close the stream

You can also use BinaryReader and BinaryWriter in the same way.  The only difference is the initial opening of the file.  If you want to read or write Xml data, you can use an IsolatedStorageFileStream along with part 1 and part 2 of my tutorial on Xml Serialization.

An additional note: it's possible for the user to disable local storage.  Your application should be aware of this.  You can configure Silverlight isolated storage by right clicking on a Silverlight Application and selecting "Silverlight Configuration" and clicking on the "Application Storage" tab.

image

Below are some additional references.

A follow up article has been written to address some additional information:  Reading Local Files in Silverlight

Tutorial: Adding Controls to CFileDialog in Vista

Posted by Matt | Filed under , , ,

Download Project: VistaFileOpenSample.zip (68.88 kb)

You may have noticed that after you've migrated your application to Visual Studio 2008 from Visual Studio 2005 or earlier, that your application has this great new File Open dialog on Vista.  However, you soon notice that your custom template was not added to the dialog like it was with the old compiler.

As it turns out, if your application uses CFileDialog, moving to the new compiler automatically converted your File Open dialog on Vista to the new Vista-Style dialog.  This is because Microsoft added a new default parameter to the CFileDialog contructor:  bVistaStyle = TRUE.

On Vista, if bVistaStyle is TRUE, then CFileDialog will attempt to use the IFileDialog COM interface behind the scenes.  You don't have to lift a finger to take advantage of it.  However, if you're File Open dialog has a custom template, then you need to do 1 of 2 things:

  1. Deliberately set bVistaStyle to FALSE in the constructor.  This will totally disable the Vista dialogs on Vista, but you'll get your custom template back, or
  2. Detect when CFileDialog is using the Vista COM objects and add your custom controls using that system.

This article will show you how to do #2.

Once you have your CFileDialog created, call CFileDialog::GetIFileOpenDialog() to get the COM interface.

CFileDialog dlg(TRUE);
IFileOpenDialog *p = dlg.GetIFileOpenDialog();

Test p against NULL.  If p is NULL, then either (a) you're not on Vista, or (b) the COM object cannot be created.  I've had the latter happen to be before when I was initializing COM like this:

CoInitializeEx(NULL,COINIT_MULTITHREADED);

CFileDialog didn't like it, so I had to change it to:

CoInitializeEx(NULL,COINIT_APARTMENTTHREADED);

Anyways, back to the topic at hand.  If p is not NULL, then you can query the IFileDialogCustomize interface against p.

IFileDialogCustomize *pC = NULL;
HRESULT hr = p->QueryInterface(
    __uuidof(IFileDialogCustomize), 
    (LPVOID*)&pC);

Once you have this interface, you can add all the controls you want.

The full interface is described here.  But below are some examples.

To add a checkbox to the dialog:

HRESULT hr = pCustomize->AddCheckButton(107, L"Read-only", FALSE);

To add a combobox to the dialog and fill it with some choices:

HRESULT hr = pCustomize->AddComboBox(101);
hr = pCustomize->AddControlItem(101, 0, L"Choice A");
hr = pCustomize->AddControlItem(101, 1, L"Choice B");
hr = pCustomize->SetSelectedControlItem(101, 0);

To add a button to the bottom of the dialog (next to "Open" and "Cancel"):

HRESULT hr = pCustomize->AddPushButton(106, L"Another button");
hr = pCustomize->MakeProminent(106);

You can only have one "prominent" control.

After you have added all of your controls, call CFileDialog::DoModal() like you normally would.

Some additional notes:

  • You cannot control the layout or sizes of the controls.  You can control the order in which they appear on the dialog, but not where they appear.  This is because they move around as the dialog is resized.
  • You can use IFileDialogCustomize::StartVisualGroup() and IFileDialogCustomize::EndVisualGroup() to group controls together so as they move around, they are moved together.

Responding to control events requires creating a class derived from IFileDialogControlEvents and passing a reference to this class to IFileDialog::Advise().

To handle a button click, you could have the following in your IFileDialogControlEvents-derived class:

virtual HRESULT STDMETHODCALLTYPE OnButtonClicked(
    /* [in] */ __RPC__in_opt IFileDialogCustomize *pfdc,
    /* [in] */ DWORD dwIDCtl)
{
    if (dwIDCtl == 102)
    {
        CWnd *pWnd = CWnd::FromHandle(GetHwnd());
        CSettingsDialog dlg(pWnd);
        dlg.DoModal();
    }

    return S_OK;
}

where dwIDCtl will match the id of the control you created (the id parameter to AddPushButton());

A sample project can be downloaded at the top of this article.

If you have additional questions, please let me know.

Tutorial: Adding a Custom Place to CFileDialog

Posted by Matt | Filed under , , , ,

Earlier versions of Microsoft Windows and Microsoft Office introduced the idea of "Places":  shortcuts along the left side of the File Open and File Save dialogs.

Windows Vista completely changed the common file dialogs and when they did that, they moved the common file dialogs to be COM objects.  If you look at the CFileDialog class which comes with Visual Studio 2008 (and maybe Visual Studio 2005 with the Vista service pack), you'll notice that internally it attempts to use the COM object if it's present, and if the COM object is not present, it reverts to the old mechanism.

The Vista COM object allows customization of the common file dialogs, including adding a custom "Place" to the shortcuts on the left side of the dialog.

  1. First, you need to get the IFileOpenDialog interface from the CFileDialog object.
    CFileDialog dlg(TRUE);
    IFileOpenDialog *p = dlg.GetIFileOpenDialog();
  2. Next, you need to create (or have available) an IShellItem-derived object.  Depending on what type of place you want to add, you could use SHCreateItemInKnownFolder, SHCreateItemFromParsingName, or any number of other functions.
    IShellItem *pItem = NULL;
    HRESULT hr = SHCreateItemInKnownFolder(FOLDERID_Documents, 
        0, L"My Place", IID_PPV_ARGS(&pItem));
  3. Then you need to add your IShellItem to the dialog as a place.
    p->AddPlace(pItem, FDAP_TOP);

The following sample adds a sub-directory of the user's Documents folder to the top of the Places on the File Open dialog in Vista (note that I am using CComPtr as smart pointers for my COM objects):

// Create your dialog and get the COM interface
CFileDialog dlg(TRUE);
IFileOpenDialog *p = dlg.GetIFileOpenDialog();
if (p != NULL)
{
    CComPtr<IFileOpenDialog> pDlg;
    pDlg.Attach(p);

    // Create the item we want to add
    // In this case, Documents\My Place
    CComPtr<IShellItem> pItem;
    HRESULT hr = SHCreateItemInKnownFolder(FOLDERID_Documents, 
        0, L"My Place", IID_PPV_ARGS(&pItem));
    if (SUCCEEDED(hr))
        pDlg->AddPlace(pItem, FDAP_TOP);
}

// Run the dialog normally
int result = dlg.DoModal();

Some additional notes:

  • Your place must exist in the file system otherwise it won't be added.
  • You can add a place to the File Save dialog by getting the IFileSaveDialog interface instead, but otherwise, it's the same code.
  • You can add a place relative to a known folder with SHCreateItemInKnownFolder.
  • You can add any place in the file system using SHCreateItemFromParsingName.
  • The CFileDialog constructor has an additional parameter to specify whether to attempt to use the Vista dialog.  This must be set to True.

Tutorial: Creating a User Control in Silverlight 2.0

Posted by Matt | Filed under , , , , , ,

Project Files: SilverlightApplication1.zip (487.22 kb)

At first glance, Silverlight applications have some similarities to WPF.  In reality, Silverlight is a stripped down version of WPF. As such, there are some limitations to what you can do in Silverlight.  In the future, I hope that Microsoft will add more functionality from WPF into Silverlight.

The following tutorial is using Visual Studio 2008 and Silverlight 2.0 Beta 2.  I assume that you are not new to Visual Studio, so I won't go through every step that's not Silverlight-specific.

1.  Create a new Silverlight application.

Once the Silverlight files have been installed, you can create a new Silverlight project just like you'd create a WPF application project.

New Project

Silverlight applications must run in a web browser.  Because of this, when you create a new project, Visual Studio 2008 will ask you whether you want to create a web application for the project.

Add Silverlight Application

  In this case, I usually go with the default choice of creating a new web application to host the control.  However, you can add the new Silverlight application into an existing project if that is what you require.  In the end, moving the Silverlight application from one project to another is quite easy:  you just need to modify some HTML.  Below is my project newly created in Visual Studio 2008.

New Project

2.  Create a new Silverlight User Control

Add to your project a new Silverlight User Control.

Add User Control

A user control is not a fully custom control: it is a control which groups/displays existing controls in a reusable collection.  This is useful if you want to create a "dialog" to display to the user.  You can create a user control which represents the dialog and place the controls (text boxes, buttons, checkboxes, etc.) on the user control that you need.

We are going to place a button and a text box on our control and link them together.  First, you need to edit your user control's Xaml file.

<UserControl x:Class="SilverlightApplication1.SilverlightControl1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
    <Grid x:Name="LayoutRoot" Background="White"
          HorizontalAlignment="Center">
        <StackPanel Orientation="Horizontal">
            <TextBox Width="200" Height="30" />
            <Button Content="Push Me" Width="100" Height="50"/>
        </StackPanel>
    </Grid>
</UserControl>

What I changed from the default user control generated:

  1. I added a StackPanel, which has a Horizontal orientation
  2. To the StackPanel, I added a TextBox and a Button
  3. I made the grid centre-aligned
  4. I removed the width and height from the UserControl itself so that it will "fit" around the controls.

My control looks like this in the Visual Studio 2008 visualizer:

User Control

3.  Place the new control on your page

Placing your control on your page is a 2 step process:

  1. Add the namespace for your control to your page.  This is done by adding the following attribute to your page's Xaml:
    xmlns:local="clr-namespace:SilverlightApplication1"
    Visual Studio's IntelliSense helps you out a bit with this one.
  2. Insert the control into your page's grid:
    <Grid x:Name="LayoutRoot" Background="White">
        <local:SilverlightControl1 />
    </Grid>

My page's Xaml looks like this:

<UserControl x:Class="SilverlightApplication1.Page"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
    xmlns:local="clr-namespace:SilverlightApplication1"
    Width="400" Height="300">
    <Grid x:Name="LayoutRoot" Background="White">
        <local:SilverlightControl1 />
    </Grid>
</UserControl>

and the visualizer shows:

Page View

 

If we compile and run the application now, we'll get the text box and button in our browser.  However, they don't do much.

First Version

4.  Do something when the user clicks the button

I want the text box data to convert to uppercase when I click the button.  There are a couple of ways to do this.  It can be done with data binding, but I'm going to do it with C# code.

In order to access the text box, we must give it a name:

<TextBox Width="200" Height="30" x:Name="MyText"/>

On my user control, if I start typing "Click=" in my button, Visual Studio 2008 IntelliSense will prompt me to create a new event handler.

Add Handler

The Xaml looks like this:

<Button Content="Push Me" Width="100" Height="50"
    Click="Button_Click"/>

and a new function was created in the C# code:

private void Button_Click(object sender, RoutedEventArgs e)
{

}

Just like in a WPF application, or a Windows Forms application, we just need to add code to this function to perform the actions for when the user clicks the button.

private void Button_Click(object sender, RoutedEventArgs e)
{
    string sText = MyText.Text;
    sText = sText.ToUpper();
    MyText.Text = sText;
}

Notice that we are accessing the text box data by the name that we gave the text box in the Xaml code.

Now, when we run this in the browser, we get:

Input

and clicking "Push Me" will produce:

Result

There you have it.  You now have a custom User Control that pretty much does nothing useful.  But the steps here should give you an idea of what's needed to customize your own user control for your Silverlight application.

In an upcoming tutorial, I will go through creating a "true" custom control (or in-as-much as you can with Silverlight 2.0).

Tutorial: Xml Serialization, Part 2

Posted by Matt | Filed under , , ,

Project: XmlSerialization.zip (11.03 kb)

In part 1, we introduced Xml Serialization.  In that post, we created a Family class, added some family members to it and serialized it to an Xml file.

But what if we need to specialize the family members?  What if we want a special Father class and Mother classes?  This can be done too:

[Serializable]
public class Mother : FamilyMember
{
  public override short Age
  {
    get { return 29; }
    set { /* never ages */ }
  }
}

[Serializable]
public class Father : FamilyMember
{
  public short GolfHandicap { get; set; }
}

If we construct our family:

Family f = new Family();
f.Name = "Smith";
f.Members.Add(new Father() { Name = "John", 
    Age = 50, GolfHandicap = 12 });
f.Members.Add(new Mother() { Name = "Mary", 
    Age = 50 });

and write, we get:

image_thumb2

We get an error.  Xml serialization does not like this.  This is because when it is serializing the Family object, it does not know about the Father and Mother classes.  The Members member is just a list of FamilyMembers.

There are actually 2 fixes for this, each produces different results.

The first solution is to add the [XmlInclude] attribute before the FamilyMember class, once for each sub-class.  This notifies the Xml serialization engine about the sub-classes.

[Serializable]
[XmlInclude(typeof(Father)), 
  XmlInclude(typeof(Mother))]
public class FamilyMember
{
...
	

Now, if we try writing again, we'll get:

<Family xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xmlns:xsd="http://www.w3.org/2001/XMLSchema"
        Name="Smith">
  <Members>
    <FamilyMember xsi:type="Father">
      <Name>John</Name>
      <Age>50</Age>
      <GolfHandicap>12</GolfHandicap>
    </FamilyMember>
    <FamilyMember xsi:type="Mother">
      <Name>Mary</Name>
      <Age>29</Age>
    </FamilyMember>
  </Members>
</Family>

and this will correctly read as well.

The second solution is to instead use the [XmlArrayElement] attribute on the Members member of the Family class.

[XmlArrayItem(typeof(Father)),
  XmlArrayItem(typeof(Mother))]
public List<FamilyMember> Members
{
...

If we write using this, we'll get:

<Family xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xmlns:xsd="http://www.w3.org/2001/XMLSchema"
        Name="Smith">
  <Members>
    <Father>
      <Name>John</Name>
      <Age>50</Age>
      <GolfHandicap>12</GolfHandicap>
    </Father>
    <Mother>
      <Name>Mary</Name>
      <Age>29</Age>
    </Mother>
  </Members>
</Family>

If you have existing Xml files to be read, the second solution will more likely match your data.

So there you have it.

There's many more details and attributes that you can get into for working with Xml files, but this will give you a start for many common cases you may require.

Xml serialization in C# is very easy.  If you've read the code above, you'll notice that there isn't a single bit of code dealing with actual parsing of Xml.  All that is handled automatically by magic the C# engine.  Reading an existing Xml file is as simple as creating a set of classes for the different elements in the data and using attributes to tweak the reading a bit.

I've included the project above.  It's based on C# 3.5 using Visual Studio 2008, but can easily be modified for C# 2.0.

Tutorial: Xml Serialization, Part 1

Posted by Matt | Filed under , , ,

The best feature about C# has got to be Xml Serialization.

Creating an Xml reader/writer in C++ using MSXML or LibXml takes significant time and debugging.  Creating the same Xml reader/writer in C# using classes is very easy.  Admittedly, if I am using C++, I have no problems using LibXml, but if given a choice, I'd use C# first.

To make a class Xml Serializable, you need to:

  1. make the class public, and
  2. give it the Serializable attribute.

The follow class can be read/written to Xml:

[Serializable]
public class Family
{
}

This class won't do anything, but you can use the follow code to write it to file:

Family f = new Family();
// TODO: Fill the family object

XmlSerializer xmlSerializer = new XmlSerializer(typeof(Family));
TextWriter writer = new StreamWriter("Family.xml");
xmlSerializer.Serialize(writer, f);
writer.Close();

and the following code to read from file:

XmlSerializer xs = new XmlSerializer(typeof(Family));
TextReader reader = new StreamReader("Family.xml");
Family f = (Family)xs.Deserialize(reader);

The resulting Xml file will look like this:

<?xml version="1.0" encoding="utf-8"?>
<Family xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xmlns:xsd="http://www.w3.org/2001/XMLSchema" />

Pretty basic and not much fun.

Every family I know has a family name, so let's add a name to the family:

[Serializable]
public class Family
{
  public string Name { get; set; }
}

(Please note that I am using C# 3.5 short-notation for members.)

If we set the family name and serialize it, we'll get the following:

<?xml version="1.0" encoding="utf-8"?>
<Family xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xmlns:xsd="http://www.w3.org/2001/XMLSchema">
  <Name>Smith</Name>
</Family>

Notice that the Name member of Family became a child element of Family in the Xml tree.  This is the default behaviour of all public members.  By default, C# will serialize all public members of a class as sub-elements.

In my case, I'd prefer Name to be an attribute of family instead of a sub-element.  To do this, I use the [XmlAttribute] attribute on the Name member:

[Serializable]
public class Family
{
  [XmlAttribute]
  public string Name { get; set; }
}

to get:

<Family xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xmlns:xsd="http://www.w3.org/2001/XMLSchema"
        Name="Smith" />

This is now more to my liking.

Let's add some family members to this family.  First, define a family member class:

[Serializable]
public class FamilyMember
{
  public string Name { get; set; }
  public short Age { get; set; }
}

and add a list of family members to our family class:

private List<FamilyMember> _members = new List<FamilyMember>();
public List<FamilyMember> Members
{
  get { return _members; }
  set { _members = value; }
}

If we add some members to our family, and serialize, we get the following:

<Family xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xmlns:xsd="http://www.w3.org/2001/XMLSchema"
        Name="Smith">
  <Members>
    <FamilyMember>
      <Name>John</Name>
      <Age>51</Age>
    </FamilyMember>
    <FamilyMember>
      <Name>Mary</Name>
      <Age>50</Age>
    </FamilyMember>
  </Members>
</Family>

You'll notice that we have 2 family members each with their own data.

In part 2, we will specialize the family members into derived classes of FamilyMember.

Tutorial: What is a Copy Constructor?

Posted by Matt | Filed under , ,

Take a look at this C++ class:

class CMyClass
{
private:
	int *m_pPtr;
public:
	CMyClass()
	:	m_pPtr(NULL)
	{
	}

	void Set(int *pPtr)
	{
		// Clean up
		if (m_pPtr != NULL)
		{
			delete m_pPtr;
			m_pPtr = NULL;
		}

		// Assign
		if (pPtr != NULL)
		{
			// Make a copy
			m_pPtr = new int;
			*m_pPtr = *pPtr;
		}
	}

	int *Get() const
	{
		return m_pPtr;
	}

	~CMyClass()
	{
		// Clean up
		if (m_pPtr != NULL)
		{
			delete m_pPtr;
			m_pPtr = NULL;
		}
	}
};

You'll notice that there is a pointer member variable.

If you use the class as follows, you won't have any problems:

void MyFunction()
{
	CMyClass var;
	int i = 5;
	var.Set(&i);
	int *pPtr = var.Get();
}

However, you will have problems if you do this:

void MyFunction(CMyClass var2)
{
	int *pPtr = var2.Get();
}

void MyOtherFuncion()
{
	CMyClass var;
	int i = 5;
	var.Set(&i);
	MyFunction(var);
	int *pPtr = var.Get();
}

Why?

Because when MyFunction() is called, the variable var is copied to var2.  When this happens, if you inspect the pointer of var and var2, you'll notice that the m_pPtr member variables not only contain the same value (5, as is correct to think), but they also point to the exact same address in memory (not expected).

In the above example, because the m_pPtr member variables point to the same address in memory, that memory will be deleted twice:  once by var2 when exiting MyFunction(), but also by var when exiting MyOtherFunction().  This should result in a crash.

The solution?  Copy constructors.

If you add the following function to the above class, your problem is solved:

class CMyClass
{
	// Other functions

	CMyClass(const CMyClass &other)
	:	m_pPtr(NULL)
	{
		Set(other.m_pPtr);
	}
};

If you run the previous problematic code with the new copy constructor, the program won't have the same issues anymore.  The m_pPtr member variables will continue to contain the same value (5), but they will point to different addresses in memory.  This will allow each to delete it's own copy in it's destructor.

Why pass other by reference (by using &) ?

If other was not passed by reference, then the copy constructor would be called when calling the copy constructor and this would lead to a (potential) infinite loop.

Why is this different than operator = ?

While the operator = and the copy constructor may perform very similar functionality, they are each called in different contexts.

CMyClass CreateVar()
{
	CMyClass var;
	int i = 10;
	var.Set(&i);
	return var; // Copy constructor
}

void MyFunction()
{
	CMyClass var;
	int i = 5;
	var.Set(&i);
	CMyClass var2(var); // Copy Constructor
	CMyClass var3 = var; // operator =
	CMyClass var4;
	var4 = var; // operator =
	CMyClass var5 = CreateVar(); // operator =
}

This is assuming that the compiler doesn't change things by optimizing (especially in the call to CreateVar()).

I hope this helps.

Thus endeth the lesson.