Further Windows Programming - 10 Lessons About C++ (2015)

10 Lessons About C++(2015)

Chapter 2: Further Windows Programming

2.1 Building a web browser

Creating a Web browser in Visual C++ is remarkably easy. That's because Microsoft Internet Explorer comes with Visual C++ and by using a few simple steps, it can be used as a Windows control similar to any other. The process of creating a simple Web browser can be summarized as: add a Microsoft Web Browser control to your Windows program, create a member control variable for it, and use the Navigate method to browse the online location of your choice. Then add an edit control to specify the URL of the web site you wish to browse.

Use the AppWizard to create a new dialog-based application called WebBrowser.

Make sure the Microsoft Web Browser is available for use in your application. From the Toolbox window, right-click and select Choose Items… (For older versions of Microsoft Visual Studio you may need to access this option by instead selecting Project > Add to Project > Components and Controls and then double-clicking the entry marked Registered ActiveX Controls.) When the dialog to choose Toolbox items appears, select the COM Components tab, and select the Microsoft Web Browser control:

The browser control will then appear in the Toolbox window:

In your Resource View, drag this newly added control onto your dialog window while resizing it according to your preference. Then add an edit control with which to enter a URL string variable, as well as a button to your dialog with the caption Browse (you can modify the OK button that is automatically generated by setting its properties). Right-click the OK button, select Properties, and rename the caption from OK to Browse. Rename its ID from IDOK to ID_BROWSE:

Now create the member variables for each of the controls that have been added. Create a member variable for the Web Browser control by right-clicking on the Web Browser control and selecting Add Variable. In the Add Member Variable Wizard that appears, give it a name such asm_WebBrowser and click OK:

In the same manner, create a member variable for edit control as well: right-click the edit control and select Add Variable, and give this control a name such as m_URL:

Now create an event-handler for the Browse button, so that when clicked the application navigates the Web Browser to the location entered in the edit control. Double-click on the Browse button in the Resource View and this will automatically generate the event-handling method for you. In this method we simply add the few lines of code that will navigate our Web browser to the desired internet location:

void CWebBrowserDlg::OnBnClickedButton()

{

CString l_strURL;

m_URL.GetWindowTextA( l_strURL );

m_WebBrowser.Navigate( l_strURL, 0, 0, 0, 0 );

}

Run the program as shown: enter the desired internet location and click the Browse button:

2.2 Downloading files via File Transfer Protocol (FTP)

File Transfer Protocol (FTP) is a protocol for transferring data files over the Internet. This next example will now demonstrate how to use FTP to download a file from an FTP site. On many FTP sites, files seem to come and go but there is one file that has remained for years and looks set to stay that way: the text file of the FTP standards itself, rfc959.txt:

ftp://ftp.funet.fi/pub/standards/RFC/rfc959.txt

We will download rfc959.txt in our example program. This dialog will display a button named “Download” and a read-only text box to display the status of the download.

Use the AppWizard to create a dialog-based application named FTP. In this dialog we will need just one button (labeled Download) and three edit boxes. Two of the edit boxes will be editable: one to enter the location of the FTP site and another to enter the file path. Another read-only edit box will be added to display the status/success of the download:

To set the read-only status of an edit control, right-click the control, select Properties, and set the Read Only property. This shows the Read-only property of the FTP address edit control:

Create a member variable for the first edit control. We will use this to enter the FTP file location. And in the Resource View, right-click the control, select Add Variable and give it a name such as m_FileLocation:

Repeat for the next edit control, the one we will use to enter the path location. Name it m_FilePath:

For the third read-only edit control, we will also add a variable, this time calling it m_StatusText:

In the OnInitDialog method, after the comment telling you to add your own initialization code, we will set some defaults for the default FTP address of the file we wish to download:

CString l_strFtpLocation = "ftp.funet.fi";

m_FtpLocation.SetWindowTextA( l_strFtpLocation );

CString l_strFilePath = "pub/standards/RFC/rfc959.txt";

m_FilePath.SetWindowTextA( l_strFilePath );

Double click the Download button to automatically generate the event handling code when this button is clicked. On clicking the Download button, this routine will first obtain the FTP location and file path from the edit controls. Then it creates a new Internet session object pointer, and if successful it creates an FTP object pointer using the FTP location passed to it, which in this example is “ftp.funet.fi”.

Finally it will attempt to download the file by passing the FTP file path location and writing the file to the chosen destination, which in this program has been hard coded to C:\Temp.

void CFTPDlg::OnBnClickedDownload()

{

CString strFtpLocation, strFilePath;

m_FtpLocation.GetWindowTextA( strFtpLocation );

m_FilePath.GetWindowTextA( strFilePath );

int nPosition = strFilePath.ReverseFind( '/' );

CString strFileName = nPosition == -1 ?

strFilePath : strFilePath.Mid( nPosition + 1 );

CString strFtpFilePath = strFtpLocation + "/" + strFilePath;

CString strDestinationPath = "C:\\temp";

CInternetSession* pInternetSession = new CInternetSession();

CFtpConnection* pFTPConnection = NULL;

if ( NULL == pInternetSession )

{

MessageBox( NULL, "Could not establish Internet session", MB_OK );

}

pFTPConnection = pInternetSession->GetFtpConnection( strFtpLocation );

if ( NULL == pFTPConnection )

{

MessageBox( NULL, "Could not establish FTP connection", MB_OK );

}

Sleep( 5000 );

BOOL success = pFTPConnection->GetFile(

strFilePath,

strDestinationPath + "\\" + strFileName,

FALSE,

FILE_ATTRIBUTE_NORMAL,

FTP_TRANSFER_TYPE_ASCII,

INTERNET_FLAG_DONT_CACHE );

if ( TRUE == success )

{

m_FtpLocation.SetWindowTextA( strFtpFilePath );

CString strDownloadMessage = strFileName +

" successfully downloaded to " +

strDestinationPath;

m_StatusText.SetWindowTextA( strDownloadMessage );

}

else

{

m_StatusText.SetWindowTextA( "Download failed." );

}

UpdateData( FALSE );

pFTPConnection->Close();

pInternetSession->Close();

}

Running the application accesses the file from the specified FTP site and writes it to the chosen destination:

2.3 Playing Media Files using DirectShow

DirectShow is a framework consisting of a suite of APIs that can be used by software developers for media streaming on Windows platforms. Using DirectShow enables you to play or capture audio and video streams or edit media. This section is a high-level introduction to using DirectShow that shows you how to play an audio or a video file.

There are some pre-requisites for using DirectShow, however. All applications using DirectShow must include the Dshow.h header file and link to the static library file strmiids.lib. These are required for all DirectShow applications:

#include <dshow.h>

#pragma comment(lib,"Strmiids.lib")

Given that DirectShow is based on the Component Object Model (COM), some knowledge of applying COM client programming is necessary to write a DirectShow application. (Should you at some point wish to extend DirectShow to support new formats or develop your own custom effects, you would need to write your own components and implement them as COM objects.)

It is necessary to initialize the COM library beforehand, using CoInitialize() and then checking the result:

HRESULT hr = CoInitialize(NULL);

if (FAILED(hr))

{

std::cout << "Could not initialize COM library" << std::endl;

return;

}

When you are finished using COM you must also uninitialize it using CoUninitialize(), meaning that all DirectShow function calls take place between COM CoInitialize() and CoUninitialize().

Define the DirectShow interfaces you will need for this application by building the filter graph and then controlling media streaming and media event handling:

IGraphBuilder *pGraph = NULL;

IMediaControl *pControl = NULL;

IMediaEvent *pEvent = NULL;

Call CoCreateInstance() to create the Filter Graph Manager. A filter graph in processing media is a directed graph whose nodes represent data processing stages and whose edges represent uni-directional data flow. The Filter Graph Manager also handles synchronization, event notification, and other aspects of the controlling the filter graph.

hr = CoCreateInstance(

CLSID_FilterGraph,

NULL,

CLSCTX_INPROC_SERVER,

IID_IGraphBuilder,

(void **)&pGraph );

Once the instance of the Filter Graph Manager is created, the real work starts. Use the returned IGraphBuilder pointer to query for the IMediaControl and IMediaEvent interfaces:

hr = pGraph->QueryInterface(IID_IMediaControl, (void **)&pControl);

hr = pGraph->QueryInterface(IID_IMediaEvent, (void **)&pEvent);

Then use IGraphBuilder::RenderFile to build the filter graph automatically for the media supplied to it - the Filter Graph manager will connect the appropriate filters for the specified media file. This is otherwise known as "Intelligent connect" in DirectShow:

hr = pGraph->RenderFile( L"C:\\temp\\Song.mp4", NULL );

Use IMediaControl::Run to start the data flow in the filter graph. IMediaControl provides the methods to run, pause or stop the media.

Use IMediaEvent::WaitForCompletion to wait for it to complete (caution: using INFINITE in a real application could cause it to block indefinitely.)

pControl->Run();

pEvent->WaitForCompletion( INFINITE, &evCode );

Full code listing:

#include <dshow.h>

#include <iostream>

#pragma comment(lib,"Strmiids.lib")

void main()

{

IGraphBuilder *pGraph = NULL;

IMediaControl *pControl = NULL;

IMediaEvent *pEvent = NULL;

// Initialize the COM library.

HRESULT hr = CoInitialize( NULL );

if ( FAILED( hr ) )

{

std::cout << "Could not initialize COM library" << std::endl;

return;

}

// Create the filter graph manager and query for interfaces.

hr = CoCreateInstance(

CLSID_FilterGraph,

NULL,

CLSCTX_INPROC_SERVER,

IID_IGraphBuilder,

(void **)&pGraph );

if ( FAILED( hr ) )

{

std::cout << "Could not create the Filter Graph Manager."

<< std::endl;

return;

}

hr = pGraph->QueryInterface( IID_IMediaControl, (void **)&pControl );

hr = pGraph->QueryInterface( IID_IMediaEvent, (void **)&pEvent);

// Build and run graph. NOTE: change path to an actual file on your system

hr = pGraph->RenderFile( L"C:\\temp\\Song.mp4", NULL );

if ( SUCCEEDED( hr ) )

{

hr = pControl->Run();

if ( SUCCEEDED( hr ) )

{

long evCode;

pEvent->WaitForCompletion( INFINITE, &evCode );

}

}

pControl->Release();

pEvent->Release();

pGraph->Release();

CoUninitialize();

}

2.4 Creating a dynamic link library (DLL)

A DLL is a code or data library that can be used by other applications or libraries. For example, common methods for logging, processing or working with user interface controls can be made available in libraries so that several applications can use the same functionality. This not only reduces the need to create the same code multiple times, but also ensures commonality (the same dialog is used across different applications). This section describes how to create your own DLL and utilize its routines from an application created elsewhere.

Create a new dll project

From the File menu, select New and then Project. In the Project types pane, under Visual C++, select Win32 > Win32 Console Application. In the Name field choose a name for the project, such as MathFuncsDll. And in the Solution name field, choose a name for the solution, such as DynamicLibrary. This initial creation is summarized by the following screenshot:

Now press OK to start the Win32 application wizard. Press Next and in the Application Settings page set DLL as the Application type if available or Console application if DLL is not available. Some versions of Visual Studio do not support creating a DLL project using wizards. (If so, it is possible to change this later to make your project compile into a DLL.) In Additional options select Empty project. This is summarized as follows:

Press Finish to create the project.

Add class(es) to the dynamic link library

Select the Project menu > Add New Item and in the Categories pane that appears, under the Visual C++ section of Installed Templates, select Code. Select Header File (.h) and choose a name for the header file, such as MathFuncsDll.h:

Then press Add. A blank file will be displayed. Paste in the example code:

// MathFuncsDll.h

namespace MathFuncs

{

class MyMathFuncs

{

public:

// Returns a + b

static __declspec(dllexport) double Add(double a, double b);

// Returns a - b

static __declspec(dllexport) double Subtract(double a, double b);

// Returns a * b

static __declspec(dllexport) double Multiply(double a, double b);

// Returns a / b

// Throws DivideByZeroException if b is 0

static __declspec(dllexport) double Divide(double a, double b);

};

}

Note the __declspec(dllexport) modifiers in the method declarations above. These modifiers enable the method to be exported by the DLL so they can be used by other applications.

Create the source code for the class(es)

From the Project menu, select Add New Item. The Add New Item dialog will be displayed. From the Categories pane, under Visual C++, select Code. Select C++ File (.cpp). Choose a name for the source file, such as MathFuncsDll.cpp.

Press Add. A blank .cpp file will be displayed. Paste in this example code:

// MathFuncsDll.cpp

// compile with: /EHsc /LD

#include "MathFuncsDll.h"

#include <stdexcept>

using namespace std;

namespace MathFuncs

{

double MyMathFuncs::Add(double a, double b)

{

return a + b;

}

double MyMathFuncs::Subtract(double a, double b)

{

return a - b;

}

double MyMathFuncs::Multiply(double a, double b)

{

return a * b;

}

double MyMathFuncs::Divide(double a, double b)

{

if (b == 0)

{

throw new invalid_argument("b cannot be zero!");

}

return a / b;

}

}

Build the project into a DLL

From the Project menu, select MathFuncsDll Properties, and under Configuration Properties, select General. In the right pane, under Project Defaults, change the Configuration Type to Dynamic Library (.dll).

Press OK to save the changes.

Compile the DLL

Select Build Solution from the Build menu. This creates a DLL that can be used by other programs.

Create a separate application that references the DLL

Now we will demonstrate how to reference the DLL we created from other applications. From the File menu, select New and then Project. From the Project types pane, under Visual C++, select Win32. In the Templates pane, select Win32 Console Application. Choose a name for the project, such as MyExecRefsDll, and enter it in the Name field. Next to Solution, select Add to Solution from the drop down list. This will add the new project to the same solution as the dynamic link library. The summary is shown in the following screenshot:

Press OK to start the Win32 Application Wizard. From the Overview page of the Win32 Application Wizard, press Next. From the Application Settings page of the Win32 Application Wizard, under Application type, select Console application. From the Application Settings page of the Win32 Application Wizard, under Additional options, deselect Precompiled header:

Press Finish to create the project.

Start using the DLL functionality

After creating the new Console Application, an empty program is created for you which is named the same as the name you chose for the project above. In this example, MyExecRefsDll.cpp.

To use the routines you created in the DLL, you reference it as follows: from the Project menu, select References. And from the Property Pages dialog, expand the Common Properties node and select Framework and References.

Select the Add New Reference button. This will display the Add Reference dialog, listing all the libraries that you can reference. From the Projects tab, select MathFuncsDll, then select OK:

Reference the DLL header files

From the Property Pages dialog, expand the Configuration Properties node, then the C/C++ node, and select General. Next to Additional Include Directories, type in the path to the location of the MathFuncsDll.h header file:

Tell the system where to locate the DLLs at runtime

From the Property Pages dialog, expand the Configuration Properties node and select Debugging. Next to Environment, type in the following: PATH= , where the blank is replaced with the actual location of your MathFuncsDll.dll.

Press OK to save all the changes made.

Start using the actual DLL components

Now replace the contents of MyExecRefsDll.cpp with the following code:

// MyExecRefsDll.cpp

// compile with: /EHsc /link MathFuncsDll.lib

#include <iostream>

#include "MathFuncsDll.h"

using namespace std;

int main()

{

double a = 7.4;

int b = 99;

cout << "a + b = " << MathFuncs::MyMathFuncs::Add(a, b) << endl;

cout << "a - b = " << MathFuncs::MyMathFuncs::Subtract(a, b) << endl;

cout << "a * b = " << MathFuncs::MyMathFuncs::Multiply(a, b) << endl;

cout << "a / b = " << MathFuncs::MyMathFuncs::Divide(a, b) << endl;

return 0;

}

Right-click the MyExecRefsDll project and ‘Select as startup project’. Right click on your solution, do a complete clean and rebuild and then run.

2.5 Serializing Your Data

This section shows you how to utilize the built-in file handling capabilities that MFC has to serialize your data. That means reading from the data or writing it to a disk.

Using the MFC document object

One way is to use the MFC document object to do the serializing for you. This will be demonstrated by creating a simple SDI (single document interface) using the AppWizard. Select File > New > Project and select the Visual C++ > MFC Application as the choice of installed template:

In the Application Type, select Single Document:

In the Advanced Features section, uncheck the Advanced frame panes:

For this example, we will let the program accept and display input from the keyboard.

The first bit of coding is to store the characters the user types in using a CString object, m_strData in the document class that is created in SerializeSDIexampleDoc.h:

class CSerializeSDIexampleDoc : public CDocument

{

protected: // create from serialization only

CSerializeSDIexampleDoc();

DECLARE_DYNCREATE(CSerializeSDIexampleDoc)

// Attributes

public:

CString m_strData;

...

And initialise the value of m_strData in the document’s constructor:

CSerializeSDIexampleDoc::CSerializeSDIexampleDoc()

{

m_strData = "";

}

We then need to write some code to handle the WM_CHAR Windows messages that are received when the user inputs characters from the keyboard. You can either just write this code yourself, or use the Class Wizard. To use the Class Wizard, right-click the project folder and select Class Wizard…

Ensure the CSerializeSDIexampleView class name is selected. Select the Message tab and then select the ‘Add Handler…’ button.

Then press Apply. Click the Edit Code button so we can write the code to store the characters the user types in.

In addition, this handler checks to see if the user has entered backspace, in which case it deletes the last character by shortening the string data member by 1:

void CSerializeSDIexampleView::OnChar(UINT nChar, UINT nRepCnt, UINT nFlags)

{

// TODO: Add your message handler code here and/or call default

CSerializeSDIexampleDoc* pDoc = GetDocument();

ASSERT_VALID(pDoc);

if ( nChar == 8 )

{

int length = pDoc->m_strData.GetLength();

CString str = pDoc->m_strData.Left( length - 1 );

pDoc->m_strData = str;

}

else

{

CString str;

str.Format( TEXT( "%c" ), nChar );

pDoc->m_strData += str;

}

Invalidate();

CView::OnChar(nChar, nRepCnt, nFlags);

}

Invalidate() is used to invalidate the view when the user types a new character. We then add an message handler to display the new text string in OnDraw() method inside CSerializeSDIexampleView, which is automatically created. Uncomment CDC* /*pDC*/ and add the code to display the text:

void CSerializeSDIexampleView::OnDraw(CDC* pDC)

{

CSerializeSDIexampleDoc* pDoc = GetDocument();

ASSERT_VALID(pDoc);

if (!pDoc) return;

pDC->TextOut( 0, 0, pDoc->m_strData );

}

Notice that the CSerializeSDIexampleDoc class already has a Serialize() method. This is where we serialize the m_strData string. Use the CArchive reference passed to it in the same as you would use std::cout or std::cin to read and write to the disk respectively:

void CSerializeSDIexampleDoc::Serialize(CArchive& ar)

{

if (ar.IsStoring())

{

ar << m_strData;

}

else

{

ar >> m_strData;

}

}

One last modification we make to the project is to ensure the application remembers when the user has added more data to the m_strData object. This means the application will prompt the user with a “Save changes to document” message when the user attempts to exit the application.

In CSerializeSDIexampleView::OnChar indicate that the document has changed by setting the modified flag:

void CSerializeSDIexampleView::OnChar(UINT nChar, UINT nRepCnt, UINT nFlags)

{

// TODO: Add your message handler code here and/or call default

CSerializeSDIexampleDoc* pDoc = GetDocument();

ASSERT_VALID(pDoc);

if ( nChar == 8 )

{

int length = pDoc->m_strData.GetLength();

CString str = pDoc->m_strData.Left( length - 1 );

pDoc->m_strData = str;

}

else

{

CString str;

str.Format( TEXT( "%c" ), nChar );

pDoc->m_strData += str;

}

Invalidate();

pDoc->SetModifiedFlag();

CView::OnChar(nChar, nRepCnt, nFlags);

}

When running the program we can enter characters or delete them using the backspace:

Now try closing the application without saving the text, so that you are prompted if you wish to save:

Give the file a name and then save:

Now restart the application, open the file Serialize1.txt in order to re-open:

Chapter Summary

To summarize this master lesson, you can now:

·Create your own web browser.

·Download files via the File Transfer Protocol.

·Play or capture media files using DirectShow.

·Create a dynamic link library (DLL) and reference it from another application.

·Apply the serialization mechanism provided by MFC to store and retrieve the member variables of your application on a storage medium such as a hard disk.