Export (0) Print
Expand All

Walkthrough: Porting an Existing Native C++ Application to Interoperate with .NET Framework Components

Visual Studio .NET 2003

In this walkthrough, you will create a flight-booking application in MFC and then upgrade it to target the .NET Framework and use Managed Extensions for C++.

The MFC application handles booking. The user interface is an MFC application, and the engine is composed of a DLL (native C++) that contains helper functions for company travel policy and a COM object (native C++) that handles flight booking.

Note   To create the application described in this walkthrough, you need access to a database server with a database such as Pubs or Northwind installed.

Architecture of the Unmanaged Application

In this walkthrough, you will:

  • Create an MFC application and modify it with Managed Extensions for C++.
  • Convert the application to managed code and compile with the /clr compiler option.
  • Replace the MFC user interface with Windows Forms.
  • Connect to a SQL Server database to get a list of company employees.
  • Port the reservation engine classes to managed classes.
  • Create a new managed assembly to interoperate with the booking COM object.

When you have finished the walkthrough, the architecture will resemble the following diagram:

Architecture of the Finished Application

Creating an MFC Application

In this section, you will create a native C++ MFC application with three projects (MFC, ATL COM, and MFC DLL) called ReservationDemo, which you will modify in subsequent sections.

To create the MFC project

  1. On the File menu, click New, and then click Project.

    The New Project dialog box appears.

  2. In the Project Types pane, click Visual C++ Projects, and then click MFC. In the Templates pane, click MFC Application. In the Name field, enter ReservationDemo and click OK.

    The MFC Application Wizard appears.

  3. Click Application Type and select Dialog based. Click Finish.
  4. In Class View, right-click the project node. On the shortcut menu, click Add, and then click Add Class.

    The Add Class dialog box appears.

  5. In the Categories pane, expand the Visual C++ Projects folder, and then click the Generic folder. Click Open.

    The Generic C++ Class Wizard appears.

  6. Enter CEmployee for the generic C++ class name. This is a public class with no base class. Repeat steps 4 and 5 to create the generic C++ class CReservation.

To create the ATL COM project

  1. In Solution Explorer, right-click the solution node. On the shortcut menu, click Add, and then click New Project.

    The Add New Project dialog box appears.

  2. In the Project Types pane, expand Visual C++ Projects, and then click ATL. In the Templates pane, click ATL Project. In the Name field, enter BookingObject, and select Add to Solution. Click OK.

    The ATL Project Wizard appears.

  3. Click Finish to accept the default settings.
  4. In Class View, right-click the project node. On the shortcut menu, click Add, and then click Add Class.

    The Add Class dialog box appears.

  5. In the Categories pane, expand the Visual C++ Projects folder, and then click the ATL folder. Select ATL Simple Object and click Open.

    The ATL Simple Object Wizard appears.

  6. In the Short name field, enter Booking, and click OK.
  7. In Solution Explorer, double-click Booking.h, and add the code indicated in bold to the IBooking declaration:
    __interface IBooking : IDispatch
    {
       [id(1), helpstring("method Reserve")] HRESULT Reserve(void);
       [id(2), helpstring("method NewBooking")] HRESULT NewBooking([in] 
       BSTR fromCity, [in] BSTR toCity, [in] BYTE day, [in] BYTE month);
       [id(3), helpstring("method CheckAvailability")] HRESULT 
       CheckAvailability([out,retval] BYTE* result);
    };
    
  8. In Booking.h, add the code indicated in bold to the end of the CBooking declaration:
    class ATL_NO_VTABLE CBooking : public IBooking
    {
    ...
    public:
       STDMETHOD(Reserve)(void);
       STDMETHOD(NewBooking)(BSTR fromCity, BSTR toCity, BYTE day, 
       BYTE month);
       STDMETHOD(CheckAvailability)(BYTE* result);
    
    private:
       CString from;
       CString to;
       int day;
       int month;
    }
    

To create the MFC DLL project

  1. In Solution Explorer, right-click the solution node. On the shortcut menu, click Add, and then click New Project.

    The Add New Project dialog box appears.

  2. In the Project Types pane, expand Visual C++ Projects, and then click MFC. In the Templates pane, click MFC DLL. In the Name field, enter TravelPolicy, and select Add to Solution. Click OK.

    The MFC DLL Wizard appears.

  3. Click Finish to accept the default settings.

Converting Unmanaged Code to Managed Code

In this section, you will convert the existing native C++ MFC application ReservationDemo to managed code and compile it with the /clr compiler option.

To compile with the /clr compiler option

  1. In Solution Explorer, right-click ReservationDemo and click Properties.

    The Property Pages dialog box appears.

  2. Expand the C/C++ node.
  3. In Command Line, enter /clr in the Additional Options box.
  4. In Code Generation, set Basic Runtime Checks to Default and set Enable Minimal Rebuild to No.
  5. In General, select Debug Information Format and change it to Program Database (/Zi).
  6. Click OK to close the dialog box.
  7. On the Build menu, click Build Solution.
  8. On the Debug menu, click Start without debugging.

In this section, you will create a new managed class CFlight in the reservation engine. CFlight will handle flight information and will use the unmanaged CReservation class to handle the actual reservation.

For the managed class to interact with the unmanaged class, you will introduce a managed wrapper (CReservationWrapper) for the unmanaged class.

To create a managed wrapper class

  1. In Class View, right-click the ReservationDemo project node.
  2. On the shortcut menu, click Add, and then click Add Class.

    The Add Class Wizard appears.

  3. Select Generic C++ Class and enter CReservationWrapper as the class name. Click Finish.
  4. In ReservationWrapper.h, add the following #include statement:
    #include "Reservation.h"
    
  5. In ReservationWrapper.h, add the following members to the class:
    CReservation * r;
    int Reserve(void);
    
  6. In ReservationWrapper.cpp, replace the entire contents of the file with the following code:
    #include "StdAfx.h"
    #include ".\reservationwrapper.h"
    #using <mscorlib.dll>
    
    using namespace System;
    using namespace System::Runtime::InteropServices;
    
    CReservationWrapper::CReservationWrapper(void)
    {
       System::String * strFrom = S"from";
       System::String * strTo = S"to";
    
       CString csFromCity ((char*)(void*) 
    Marshal::StringToHGlobalAnsi(strFrom));
       CString csToCity ((char*)(void*) 
    Marshal::StringToHGlobalAnsi(strTo));
    
       r = new CReservation(&csFromCity, &csToCity, 17,1,2,2);
    }
    
    CReservationWrapper::Reserve()
    {
       int res = r->Reserve();
       return res;
    }
    
    CReservationWrapper::~CReservationWrapper(void)
    {
       delete r;
    }
    

To create the managed class

  1. In Class View, right-click the ReservationDemo project node.
  2. On the shortcut menu, click Add, and then click Add Class.

    The Add Class Wizard appears.

  3. Select Generic C++ Class and enter CFlight as the class name. Click Finish.
  4. In Flight.h, modify the class definition to read as follows:
    __gc class CFlight
    {
    public:
       CFlight();
       void Reserve();
    };
    
  5. In Flight.cpp, replace the entire contents of the file with the following code:
    #include "StdAfx.h"
    #include "Flight.h"
    #include "ReservationWrapper.h"
    #using <mscorlib.dll>
    
    CFlight::CFlight(void)
    {
    }
    
    void CFlight::Reserve()
    {
       CReservationWrapper *res = new CReservationWrapper();
       res->Reserve();
    }
    

To modify CEmployee to use CFlight

  1. In Employee.cpp, add the following #include statements:
    #include <vcclr.h>
    #include "Flight.h"
    
  2. In Employee.cpp, modify the implementation of MakeReservation to instantiate a CFlight object as follows (bold code indicates changes). Note how the code uses gcroot to contain the managed class:
    int CEmployee::MakeReservation (CString *from, CString *to, int day1, 
    int month1, int day2, int month2)
    {
       gcroot<CFlight*> flight;
       flight->Reserve();
    }
    
  3. In Reservation.cpp, modify the if statement in the implementation of Reserve as follows (bold code indicates changes):
    if (b->CheckAvailability())
    {
       b->Reserve();
       return res;
    }
    else
       return res + 10;
    }
    
  4. In Solution Explorer, right-click the ReservationDemo project node.
  5. On the shortcut menu, click Build.

In this section, you will create a DLL to provide employee information for two new XML Web services, EmployeeCodes and EmployeeData, to retrieve employee information. The first service gets information about employee IDs. The second service gets employee data. Both will call into one native DLL (EmployeeDLL) using IJW to get the required information.

To create the EmployeeDLL native C++ DLL to provide employee information

  1. On the File menu, click New, and then click Project.

    The New Project dialog box appears.

  2. In the Project Types pane, click Visual C++ Projects, and in the Templates pane, click Win32 Project. Enter EmployeeDLL in the Name box, and select Add to Solution. Click OK.
  3. Modify the code in EmployeeDLL.cpp:
    #include "StdAfx.h"
    
    extern "C" __declspec(dllexport) int EmployeeID(LPSTR name)
    {
       return (int) *name;
    }
    
    BOOL APIENTRY DllMain( HANDLE hModule, DWORD  ul_reason_for_call, 
    LPVOID lpReserved)
    {
       return TRUE;
    }
    
  4. In Solution Explorer, right-click the project. On the shortcut menu, click Build.

To create the EmployeeCodes XML Web service

  1. On the File menu, click New, and then click Project.

    The New Project dialog box appears.

  2. In the Project Types pane, expand Visual C++ Projects, and then click .NET. In the Templates pane, click ASP.NET Web Service. In the Name field, enter EmployeeCodes, and select Add to Solution. Click OK.
  3. EmployeeCodesClass.h appears in Design view. Right-click the Designer, and on the shortcut menu, click View Code. EmployeeDataClass.h appears in Edit view.
  4. Replace the entire contents of EmployeeCodesClass.h with the following code:
    // EmployeeCodesClass.h
    
    #pragma once
    
    using namespace System;
    using namespace System::Web;
    using namespace System::Web::Services;
    
    namespace EmployeeCodes
    {
       public __gc
       class EmployeeCodesClass : public WebService
       {
    
       public:
    
          [System::Web::Services::WebMethod]
          int  EmployeeLevel(String* name);
       };
    }
    
  5. Replace the entire contents of EmployeeCodesClass.cpp with the following code:
    // EmployeeCodesClass.cpp
    
    #include "StdAfx.h"
    #include "EmployeeCodesClass.h"
    #include "Global.asax.h"
    
    using namespace System::Runtime::InteropServices;
    
    [DllImport("EmployeeDLL", CharSet=CharSet::Ansi)]
    extern "C" int EmployeeID(String* name);
    
    namespace EmployeeCodes
    {
       int  EmployeeCodesClass::EmployeeLevel(String* name)
       {
          return EmployeeID(name);
       }
    
    };
    

To create the EmployeeData XML Web service

  1. On the File menu, click New, and then click Project.

    The New Project dialog box appears.

  2. In the Project Types pane, expand Visual C++ Projects, and then click .NET. In the Templates pane, click ASP.NET Web Service. In the Name field, enter EmployeeData, and select Add to Solution. Click OK.
  3. EmployeeDataClass.h appears in Design view. Right-click the Designer, and on the shortcut menu, click View Code. EmployeeDataClass.h appears in Edit view.
  4. Replace the entire contents of EmployeeDataClass.h with the following code:
    // EmployeeDataClass.h
    
    #pragma once
    
    using namespace System;
    using namespace System::Web;
    using namespace System::Web::Services;
    
    namespace EmployeeData
    {
       public __gc
       class EmployeeDataClass : public WebService
       {
    
       public:
          [System::Web::Services::WebMethod]
          int EmployeeLadder(String* name);
       };
    }
    
  5. Replace the entire contents of EmployeeDataClass.cpp with the following code:
    // EmployeeDataClass.cpp
    
    #include "StdAfx.h"
    #include "EmployeeDataClass.h"
    #include "Global.asax.h"
    
    using namespace System::Runtime::InteropServices;
    
    [DllImport("EmployeeDLL", CharSet=CharSet::Ansi)]
    extern "C" int EmployeeID(String* name);
    
    namespace EmployeeData
    {
       int EmployeeDataClass::EmployeeLadder(String* name)
       {
          return EmployeeID(name);
       }
    
    };
    
  6. On the Build menu, click Build Solution. This deploys the XML Web service to the local machine.

To add XML Web service references to the MFC application

  1. In Solution Explorer, right-click the ReservationDemo project.
  2. On the shortcut menu, click Add Web Reference.

    The Add Web Reference dialog box appears.

  3. Click Web services on the local machine and select the XML Web service for EmployeeCodes. The XML Web service description appears in the main pane of the dialog box. Click Add Reference.
  4. Repeat steps 1 through 3 for EmployeeData.
  5. Modify Employee.cpp for use with the XML Web services. Add the following #include statements:
    #define __FLTUSED__
    #include "localhost.h"
    #include "localhost1.h"
    
  6. Modify the CEmployee constructor as follows (bold code indicates changes):
    CEmployee::CEmployee(CString *name)
    {
       employeeName = *name;
       reservation = NULL;
    
       int res;
    
       localhost::EmployeeCodesClass *c = new 
    localhost::EmployeeCodesClass();
       res  = c->EmployeeLevel(S"app");
    
       localhost1::EmployeeDataClass *c1 = new 
    localhost1::EmployeeDataClass();
       res  = c1->EmployeeLadder(S"paa");
    }
    
  7. On the Build menu, click Build Solution.
  8. Copy EmployeeDLL.dll to the Inetpub directory for each XML Web service. For example, if your Inetpub directory resides on the C: drive, the directories would be:
    C:\Inetpub\wwwroot\EmployeeCodes\bin
    C:\Inetpub\wwwroot\EmployeeData\bin
    

Replacing the User Interface with a Windows Form

The next step is to replace the MFC user interface with a Windows Form. In this section, you will create a new Windows Forms application, which will use the reservation engine from the MFC application.

To create the base form

  1. On the File menu, click New, and then click Project.

    The New Project dialog box appears.

  2. In the Project Types pane, expand Visual C++ Projects, and then click .NET. In the Templates pane, click Class Library (.NET). In the Name field, enter ReservationApp, and select Add to Solution. Click OK.
  3. Right-click the ReservationApp project. On the shortcut menu, click Add, and then click Add Item.

    The Add Item dialog box appears.

  4. Click Windows Form (.NET) and name the new form ParentForm. Click Open.
  5. On the View menu, click Toolbox.
  6. In the Toolbox, open the Windows Forms section. Drag a button from the toolbox to the form, and set the Text property for Button1 to Cancel.
  7. Double-click the Cancel button to add an event handler.
  8. Insert the following code in the button1_click event:
    Close();
    
  9. On the Build menu, click Build Solution.

To create an inherited form

  1. On the File menu, click New, and then click Project.

    The New Project dialog box appears.

  2. In the Project Types pane, expand Visual C++ Projects, and then click .NET. In the Templates pane, click Windows Form Application (.NET). In the Name field, enter ChildForm, and select Add to Solution. Click OK.
  3. Make Form1 inherit from the parent form. Open Form1.h and change the following line:
    public __gc class Form1 : public System::Windows::Forms::Form
    

    to:

    public __gc class Form1 : public ReservationApp::ParentForm
    
  4. In Solution Explorer, right-click the ChildForm project. On the shortcut menu, click Add Reference.

    The Add Reference dialog box appears.

  5. Select ReservationApp from the list and click OK.

    ReservationApp appears in the References list in Solution Explorer.

  6. From the Toolbox, drag a button from the toolbox to the ChildForm project's Form1.h in Design view.
  7. In the Properties window, set Button1's Text property to OK.
  8. From the Toolbox, drag three combo box controls and three labels for employee name, source airport, and destination airport.
  9. Drag two DatePicker ActiveX controls: one for the depart date and one for the return date.

To connect the form to the reservation engine

Now that you have created the form layout, you can connect the form to the flight reservation engine.

  1. Copy the following files from the ReservationDemo directory to the ChildForm directory:
    Employee.cpp
    Employee.h
    Flight.cpp
    Flight.h
    Reservation.cpp
    Reservation.h
    localhost.h
    localhost1.h
    
  2. Copy TravelPolicy.dll and BookingObject.dll to the ReservationApp\Debug directory.
  3. In Solution Explorer, right-click the ChildForm project. On the shortcut menu, click Add Existing Item.

    The Add Existing Item dialog box appears.

  4. Select Employee.cpp, Employee.h, Flight.cpp, Flight.h, Reservation.cpp, and Reservation.h. Also add TravelPolicy.lib from ReservationDemo\Debug.
  5. Replace the code in ChildForm\Flight.cpp with the following code:
    #include "StdAfx.h"
    #include "Flight.h"
    #include "Reservation.h"
    #using <mscorlib.dll>
    
    using namespace System;
    using namespace System::Runtime::InteropServices;
    
    CFlight::CFlight(void)
    {
    }
    
    void CFlight::Reserve()
    {
       System::String * strFrom = S"from";
       System::String * strTo = S"to";
    
       CString csFromCity ((char*)(void*) 
    Marshal::StringToHGlobalAnsi(strFrom));
       CString csToCity ((char*)(void*) 
    Marshal::StringToHGlobalAnsi(strTo));
    
       CReservation* r = new CReservation(   &csFromCity, &csToCity, 
    17,1,2,2);
       int res = r->Reserve();
    };
    
  6. In ChildForm\Reservation.h, add:
    #include <atlstr.h>
    
  7. In ChildForm\Form1.h, add the following #include statement:
    #pragma once
    #include Employee.h
    ...
    using namespace System;
    
  8. In Design view for ChildForm\Form1.h, double-click the OK button to add an event handler.
  9. Add the following code to the button2_click handler:
    CString name(comboBox1->Text);
    CEmployee employee(&name);
    
    CString FromCity(comboBox2->Text);
    CString ToCity(comboBox3->Text);
    
    employee.MakeReservation(&FromCity, &ToCity, 
                dateTimePicker1->Value.Day, dateTimePicker1->Value.Month,
                dateTimePicker1->Value.Day, dateTimePicker1->Value.Month);
    
  10. In Solution Explorer, right-click the ChildForm project. On the shortcut menu, click Set as StartUp Project.
  11. Right-click the project again and select Properties.

    The Property Pages dialog box appears.

  12. Select Debugging, and set the Debugger Type to Managed Only. Click OK.
  13. On the Build menu, click Build Solution.
  14. Set a breakpoint at the following line in Flight.cpp:
    int res = r->Reserve();
    
  15. From the Debug menu, click Start to run the application.
  16. Click the OK button.

    The program will run to the breakpoint you set.

  17. When the program hits the breakpoint, click Step Into on the Debug menu and ensure that the application works, and then select StepOut and StepOver. In the QuickWatch window, verify that the value of res is -13.

Connecting to a SQL Server Database

In this section, you will configure the application to fill the employee combo box from a SQL server database. First, you need to create the data connections.

To access data from a database

  1. On the View menu, click Server Explorer.
  2. In Server Explorer, right-click the Data Connections node and select Add Connection.

    The Data Link Properties dialog box appears.

  3. Specify the name of a server on which you have an account or access privileges. Select Use Windows NT Integrated security. Select a database such as Pubs or Northwind. Click OK to create the connection, which will appear in Server Explorer.
  4. In Server Explorer, expand the database node, then the Tables node, and then select an appropriate table (such as Authors) and drag it onto Form1's Design view. This creates two objects: sqlConnection1 and sqlDataAdapter1.
  5. Right-click the sqlDataAdapter1 object and click Configure Data Adapter.

    The Configure Data Adapter dialog box appears.

  6. Click Next. Specify your database, if it is not already the default database, and click Next.
  7. Select Use SQL Statements and click Next.
  8. Choose Query Builder and select the fields with which you want to work. Click OK, and then click Finish.
  9. In Form1's Design view, right-click sqlDataAdapter1 and select Generate Dataset from the context menu.

    The Generate DataSet dialog box appears.

  10. Choose a new dataset named DataSet1 (default) that contains the authors table and ensure that Add this dataset to designer is selected.

To configure the form controls to work with the generated data set

  1. Click the combo box for the employee name in Form1's Design view.
  2. In the Properties window, set DataSource to dataSet11.authors and DisplayMember to au_lname.
  3. Double-click the form to add a Form1_Load event. Add the following code to the event:
    dataSet11->Clear( );
    sqlDataAdapter1->Fill(dataSet11);
    
  4. On the Build menu, click Build Solution.
    Note   If the solution does not build, move #include "Dataset1.h" from stdafx.h to ChildForm\Form1.cpp after #include "stdafx.h.".

Porting the Reservation Engine to Managed Classes

In this section, you will port the reservation engine to be completely managed, to demonstrate interoperability between managed and unmanaged code. You must modify the new managed classes to interoperate with the policy DLL and the booking COM object. To do this, you will need to edit the header and implementation files as described in the following code (bold code indicates changes).

Employee.h

//  Employee.h

#pragma once

#include "reservation.h"

__gc class CEmployee
{
protected:
   CReservation* reservation;
   System::String* employeeName;
public:
   CEmployee(System::String* name);
   ~CEmployee(void);

   virtual int MakeReservation (System::String* from, System::String* to, int day1, int month1, int day2, int month2);
};

Employee.cpp

// Employee.cpp

#include "StdAfx.h"
#include <vcclr.h>
#include "employee.h"
#include "Flight.h"

#define __FLTUSED__
#include "localhost.h"
#include "localhost1.h"

CEmployee::CEmployee(System::String* name)
{
   employeeName = name;
   reservation = NULL;
   int res;

   localhost::EmployeeCodesClass *c = new 
localhost::EmployeeCodesClass();
   res = c->EmployeeLevel(S"app");

   localhost1::EmployeeDataClass *c1 = new 
localhost1::EmployeeDataClass();
   res = c1->EmployeeLadder(S"paa");
}
int CEmployee::MakeReservation (System::String* from, System::String* 
to, int day1, int month1, int day2, int month2)
{
   CFlight* flight;
   flight->Reserve();
   return 1;
}

CEmployee::~CEmployee(void)
{
}

Reservation.h

// Reservation.h

#pragma once

__gc class CReservation
{
protected:
   __gc struct CityInfo {
      System::String* city;
      int departDay;
      int departMonth;
   };
   CityInfo* fromCity, * toCity;
public:
   CReservation(System::String* from, System::String* to, int d1, int 
m1, int d2, int m2);
   ~CReservation(void);
   virtual int Reserve ();
protected:
   virtual int GetFlightPolicy (int level);
};

Reservation.cpp

// Reservation.cpp

#include "StdAfx.h"
#include "reservation.h"

#import "..\\bookingobject\\_bookingobject.tlb" no_namespace

#using <mscorlib.dll>
using namespace System;
using namespace System::Runtime::InteropServices;

CReservation::CReservation(System::String* from, System::String* to, 
int d1, int m1, int d2, int m2)
{
   fromCity = new CityInfo();
   toCity = new CityInfo();
   fromCity->city = from;
   fromCity->departDay = d1;
   fromCity->departMonth = m1;
   toCity->city = to;
   toCity->departDay = d2;
   toCity->departMonth = m2;
}

CReservation::~CReservation(void)
{
}

int CReservation::Reserve ()
{
   IBookingPtr b(__uuidof(CBooking));

   BSTR bstrFromCity = static_cast<BSTR> 
(Marshal::StringToBSTR(fromCity->city).ToPointer());
   BSTR bstrToCity = static_cast<BSTR> (Marshal::StringToBSTR(
toCity->city).ToPointer());

   b->NewBooking(bstrFromCity, bstrToCity, fromCity->departDay, 
fromCity->departMonth);

   int res = GetFlightPolicy(1);

   if (b->CheckAvailability())
   {
      b->Reserve();
      return res;
   }
   else
      return res + 10;

}

__declspec( dllimport ) int GetFlightClass (int, int);

int CReservation::GetFlightPolicy (int level)
{
   return GetFlightClass(level, fromCity->departDay-toCity->departDay);
}

Flight.cpp

// Flight.cpp

#include "StdAfx.h"
#include ".\\flight.h"
#include "Reservation.h"
#using <mscorlib.dll>

using namespace System;
using namespace System::Runtime::InteropServices;

CFlight::CFlight(void)
{
}

void CFlight::Reserve()
{
   System::String * strFrom = S"from";
   System::String * strTo = S"to";

   CReservation* r = new CReservation(   strFrom, strTo, 17, 1, 2, 2);
   int res = r->Reserve();
}

Form1.h

// ChildForm\Form1.h
// button2_click handler

   System::String* name(comboBox1->Text);
   CEmployee* employee = new CEmployee(name);

   System::String* FromCity(comboBox2->Text);
   System::String* ToCity(comboBox3->Text);

   employee->MakeReservation(FromCity, ToCity, 
      dateTimePicker1->Value.Day, dateTimePicker1->Value.Month,
      dateTimePicker1->Value.Day, dateTimePicker1->Value.Month);


Show:
© 2014 Microsoft