Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

HOWTO: Combine Managed and Unmanaged Projects into a Single Visual Studio Solution

0.00/5 (No votes)
8 Apr 2004 1  
This article describes how to combine managed and unmanaged projects into a single Visual Studio .NET solution.

The information in this article applies to:

  • Microsoft Visual Studio .NET 2003
  • Microsoft eMbedded Visual C++ 4

Summary

This article describes how to combine managed and unmanaged projects into a single Visual Studio .NET solution, so that you can edit, build and deploy the entire solution within a single environment. This is then combined with a setup project to automatically generate an MSI for installing the solution on multiple devices.

More Information

Microsoft Visual Studio .NET 2003 gives us the ability to create managed applications for smart devices (e.g., Pocket PCs), using the .NET Compact Framework.

However, this framework is a subset of the full-blown .NET framework - Microsoft has reduced its footprint to fit it into mobile devices. With the full size framework, it is sometimes necessary to call into unmanaged code to access services not available directly in .NET; this is even more the case with the Compact Framework, where many useful features are simply not available to the .NET programmer. Fortunately, the unmanaged development environment for mobile devices, namely eMbedded Visual C++, has great support for mobile devices.

Typically, when developing with the .NET Compact Framework, you will have a mixture of managed (e.g., C#) and unmanaged (eMbedded Visual C++) projects, with the managed code using Platform Invoke to call into the unmanaged code.

When you create a new Smart Device Application in Microsoft Visual Studio .NET 2003, two project configurations are created for you: Debug and Release. Neither of these is specific to any mobile hardware, because .NET by design is platform neutral, as long as the Common Language Runtime (CLR) has been ported to the target device. This is great news for developers, because we only need to create a single binary (the assembly), which can be used on multiple compact devices. The CLR hides the hardware differences from our .NET programs, which talk to a virtual machine (the CLR), and not the native device.

.NET also allows us to use Platform Invoke to call unmanaged code so we can access features not directly exposed within the Compact Framework. Any unmanaged code binaries are typically added to the .NET project, so that they will be deployed to the device at the same time as the .NET assembly during debugging.

This deployment demonstrates a significant problem: when you add a device specific unmanaged binary (EXE or DLL) to a .NET project, the project as a whole becomes platform specific, because you can only deploy and debug it on the platform at which the unmanaged binary is targeted. However, there is no automatic recognition of this device dependency in Visual Studio – you still only have the standard Debug and Release device independent project configurations.

When I first encountered this limitation, I quickly came up with a solution that I have seen suggested elsewhere – to create a custom build step in the unmanaged project, which copies the unmanaged binary to the appropriate location in the managed solution. The custom build step is the same for all configurations in the unmanaged project, that is, irrespective of the different devices targeted by the different configurations, they all target their output to the same location in the managed solution. For example:

Managed Project
    Unmanaged.dll

Unmanaged Project
    emulatorDbg
        Unmanaged.dll -> copy to -> Managed Project\Unmanaged.dll
    emulatorRel
        Unmanaged.dll -> copy to -> Managed Project\Unmanaged.dll
    ARMV4Dbg
        Unmanaged.dll -> copy to -> Managed Project\Unmanaged.dll
    ARMV4Rel
        Unmanaged.dll -> copy to -> Managed Project\Unmanaged.dll

The target device for the overall solution then depends on which version of the unmanaged binary happens to be in the managed project. To target a different device requires opening up the unmanaged project in eMbedded Visual C++ 4, selecting the corresponding project configuration for the target device, and building that configuration. The custom build step ensures that the resulting binary is copied over to the managed project.

There are two big disadvantages with this approach:

  • It requires a manual build step totally divorced from the Visual Studio .NET development environment, and it can be easy to forget which version of the unmanaged binary is currently in the managed project.
  • It uses a model where the producer project (the unmanaged project) is responsible for pushing its output to the consumer project (the managed project). If you create a new .NET project that requires the unmanaged component, then you have to change the unmanaged project so that it copies its output to the new .NET project. This is the reverse of what we would logically expect – the .NET project should be able to fetch all the components it needs, without relying on external build events it knows nothing about.

I have now ditched this approach for a far better technique that enables me to have device specific configurations within my .NET projects, and to build both managed and unmanaged projects in a single step. I will call this combination of managed and unmanaged projects a Mixed Project Solution; the rest of this article is a step-by-step example of how to create such a mixed project solution.

Step-by-Step Example

For our mixed project solution, we will create an application called MakeDemo, which consists of a single form with a Press Me button; clicking the button displays a message box with the words That tickles.

The form is implemented in C#, but the message box (for demonstration purposes) is displayed from an unmanaged DLL written in eMbedded Visual C++. The Press Me button uses Platform Invoke to call from managed to unmanaged code.

  1. Create a new Smart Device Application in Microsoft Visual Studio .NET 2003: select New from the File menu, and then Project. In the New Project dialog, select Visual C# Projects from the Project Types, and Smart Device Application from the Templates. Enter the name of the project as Managed, and a Location of directory\MakeDemo.

    Click OK.

  2. In the Smart Device Application Wizard, choose Pocket PC for the target platform, and Windows Application for the project type.

    Click OK to generate the project.

  3. In the Solution Explorer, rename the Form1.cs file to MakeDemoForm.cs.
  4. The initial code for MakeDemoForm.cs looks like this:
    using System;
    using System.Drawing;
    using System.Collections;
    using System.Windows.Forms;
    using System.Data;
    
    namespace Managed
    {
        public class MakeDemoForm : System.Windows.Forms.Form
        {
            private System.Windows.Forms.Button PressMe;
    
            public MakeDemoForm()
            {
                this.PressMe = new System.Windows.Forms.Button();
                this.PressMe.Location = new System.Drawing.Point(80, 112);
                this.PressMe.Text = "Press Me";
                this.PressMe.Click += new System.EventHandler(this.OnPressMe);
                this.Controls.Add(this.PressMe);
                this.Text = "Make Demo";
            }
    
            protected override void Dispose( bool disposing )
            {
                base.Dispose( disposing );
            }
    
            static void Main() 
            {
                Application.Run(new MakeDemoForm());
            }
    
            private void OnPressMe(object sender, System.EventArgs e)
            {      
            }
        }
    }
  5. Now create the unmanaged project in eMbedded Visual C++ 4: select New from the File menu, and on the Projects tab, select WCE Dynamic-Link Library. Enter the Project name as Unmanaged, and the Location as directory\MakeDemo\Unmanaged. Finally select the CPUs you wish to target – which for this example will be Win32 (WCE ARMV4) and Win32 (WCE emulator):

    Click OK. You should now have a MakeDemo folder with Managed and Unmanaged sub-folders.

  6. On the WCE Dynamic Link Library dialog, choose to create A DLL that exports some symbols, and click Finish, and then OK to the New Project Information dialog.

    This will create an Unmanaged.cpp and Unmanaged.h, which contain an exported function called fnUnmanaged, amongst other things.

  7. Change the fnUnmanaged function in the Unmanaged.cpp file to the following:
    UNMANAGED_API int fnUnmanaged(LPCTSTR lpszMessage)
    {
        return MessageBox(NULL, lpszMessage, _T("Unmanaged"), MB_OK);
    }

    When we call this function from our .NET project, we will pass in a string that the function will display in a message box.

    Also change the declaration of the function in the Unmanaged.h file:

    #ifdef __cplusplus
    extern "C"
    {
    #endif
    
    UNMANAGED_API int fnUnmanaged(LPCTSTR lpszMessage);
    
    #ifdef __cplusplus
    } // extern "C"
    #endif

    We use the extern "C" declaration to ensure we do not get any C++ name mangling for our exported function.

  8. From the Build menu, select Batch Build..., and make sure all the project configurations are ticked, then click Rebuild All.

    This step is not strictly necessary, but it is useful to demonstrate that the build process automatically creates a sub-directory for each of the project configurations, namely:

    Win32 (WCE emulator) Release

    emulatorRel

    Win32 (WCE emulator) Debug

    emulatorDbg

    Win32 (WCE ARMV4) Release

    ARMV4Rel

    Win32 (WCE ARMV4) Debug

    ARMV4Dbg

  9. Return to the managed project in Microsoft Visual Studio .NET 2003, and make the following code changes in MakeDemoForm.cs.

    Add the following line at the top of the file, to give us access to the Platform Invoke namespace:

    using System.Runtime.InteropServices;

    Change the OnPressMe method as follows:

    private void OnPressMe(object sender, System.EventArgs e)
    {
        fnUnmanaged("That tickles");
    }

    And add the following lines to the end of the Managed class. This declaration enables us to call the fnUnmanaged function in the Unmanaged.dll.

    [DllImport("Unmanaged.dll")]
    private static extern int fnUnmanaged(string message);
  10. We now need to add the Unmanaged.dll to the project, so it will be deployed to the target device along with the managed assembly. In the Solution Explorer, right click the Managed project, and select Add and then Add Existing Item.... Navigate to the Unmanaged\emulatorDbg folder, change the Files of type to All Files (*.*), select the Unmanaged.dll, and click Open.

    This will physically copy the Unmanaged.dll from the Unmanaged\emulatorDbg folder, to the Managed folder, so you now have two copies of the DLL.

  11. In the Standard toolbar, select Debug for the project configuration, and in the Device toolbar, make sure Pocket PC 2003 Emulator is selected. Hit F5 to deploy and run the assembly on the emulator through the Visual Studio debugger.

    When you click the Press Me button, the call should be made to the Unmanaged.dll to display the message box.

  12. So far we have followed standard practice in setting up our managed and unmanaged projects. If we want to make a change to the unmanaged project (perhaps to display an icon in the message box) then we need to swap to the eMbedded Visual C++ environment, change the source code, rebuild the project, and then copy the resulting DLL to our managed project. Also, if we want to target a different device, we have to manually copy the appropriate unmanaged DLL to the managed project.

    The following steps describe how we can combine our separate managed and unmanaged projects into a single Mixed Project Solution.

  13. In the Microsoft Visual Studio .NET 2003 Solution Explorer, right click on Solution 'Managed' and select Add, and then New Project... from the pop-up menus. In the Add New Project dialog, expand the Visual C++ Projects in the Project Types; highlight General, and then in the Templates, highlight Makefile Project. Change the Location to MakeDemo and the Name to Unmanaged (to match the name of our unmanaged project).

    Click OK, and then Finish. This will create a new Unmanaged project in Solution Explorer with a corresponding Microsoft Visual Studio .NET 2003 project file called Unmanaged.vcproj in our existing Unmanaged folder.

  14. The Unmanaged project in Solution Explorer has three automatically generated folders: Source Files, Header Files, and Resource Files. Add the existing files in the unmanaged project to the appropriate folder (e.g., add StdAfx.cpp and Unmanaged.cpp to the Source Files folder). To add a file to a folder, right click the folder in Solution Explorer, and select Add, and Add Existing Item... from the pop-up menu. You can multi-select files in the Add Existing Item dialog by using the Ctrl key and clicking with the mouse.

    The unmanaged project does not have any resource files, but if it did then we would also add these – into the Resource Files folder.

    Your Solution Explorer window should now look a bit like this:

    We can now edit all the files in the unmanaged project, including resource files, using the Microsoft Visual Studio .NET 2003 environment.

    The next, and most important step, is to also build the unmanaged project from within Microsoft Visual Studio .NET 2003.

  15. We are now going to create configurations for the unmanaged project in Visual Studio .NET that match the existing configurations in the eMbedded Visual C++ project. From the Build menu, select the Configuration Manager.... In the Configuration column for the Unmanaged project, select <Edit...> in the combo box, and then Rename to rename the existing Debug and Release solution configurations to emulatorDbg and emulatorRel. These names must match the output folder names in the eMbedded Visual C++ project for emulator builds.

    Close the Edit Project Configurations dialog box.

  16. Now add configurations for the other devices we are targeting, that is ARMV4, by selecting <New...> from the combo box for the Unmanaged project configuration. For the Project Configuration Name enter ARMV4Dbg, to match the output folder name in the eMbedded Visual C++ project for an ARMV4 debug build. Change Copy Settings from to emulatorDbg. Make sure Also create new solution configuration(s) is not ticked.

    Repeat this process for ARMV4Rel, copying the settings from emulatorRel.

    We should now have four configurations for the Unmanaged project, and just two for the Managed project.

    In the Configuration Manager, when you choose an Active Solution Configuration, the individual project configurations should change to match it. So if the Active Solution Configuration is Debug, then the Managed project configuration should also be Debug, and the Unmanaged project configuration should be either emulatorDbg or ARMV4Dbg. To target a different device, simply open the Configuration Manager, and change the Unmanaged project configuration to the appropriate device.

    Close the Configuration Manager.

  17. In the Solution Explorer, right click the Unmanaged project, and choose Properties... from the pop-up menu. In the Unmanaged Property Pages dialog, set the Configuration to All Configurations, and select NMake from the Configuration Properties. Set the NMake options as follows:

    The $(ConfigurationName) macro will be set depending on which configuration we are building, i.e., it will be one of emulatorDbg, emulatorRel, ARMV4Dbg, or ARMV4Rel.

    The next step describes the Make command.

    Click the OK button.

  18. The Make command is implemented as a good old-fashioned batch file. In the Solution Explorer, right click the Unmanaged project, and choose Add, and Add New Item... from the pop-up menu. In the Add New Item dialog, select the Text File (*.txt) template, and a Name of Make.bat; the Location should be MakeDemo\Unmanaged:

    Click Open. The Make.bat file will probably be added to the Source Files folder. I prefer to drag it to the main Unmanaged folder.

  19. The Make.bat file can contain any commands required to build the unmanaged project. Generally, you will want to execute a command to build the unmanaged project, and then copy the resulting output to the managed project. The following example Make.bat uses the eMbedded Visual C++ environment, EVC.EXE, to build the project, and then copies the output.

    I have highlighted in italics the parts of the Make.bat file that you will need to change for your own environment and project. It is also important to note that the Config variable will be set to the current value of the $(ConfigurationName) macro which is passed to the batch file on the command line. The Config variable determines which configuration will be built, and where the output will be copied from. For the copying, it is assumed that the name of the output folder is exactly the same as the Config variable, i.e., the current configuration name. This is why it is important that the configuration names in the solution match the names of the output folders in the unmanaged project. If you do not want to reuse the output folder names like this, then you need to change the Make.bat file so that it maps the solution configuration names to output folders, in the same way it already maps configuration names to the corresponding configuration in the unmanaged project.

    if "%1"=="" goto errNoArg
    
    set Config=%1
    set Arg=%2
    
    REM Change to the location of EVC.EXE on your machine.
    set EVCPath="D:\Program Files\Microsoft eMbedded C++ 4.0\Common\EVC\Bin\EVC.EXE"
    
    REM The configuration names are CASE sensitive,
    REM e.g., "emulatorDbg" must be the same case
    REM as the configuration name in Configuration Manager.
    if "%1"=="emulatorDbg" set ConfigEx=Win32 (WCE emulator) Debug
    if "%1"=="emulatorRel" set ConfigEx=Win32 (WCE emulator) Release
    if "%1"=="ARMV4Dbg" set ConfigEx=Win32 (WCE ARMV4) Debug
    if "%1"=="ARMV4Rel" set ConfigEx=Win32 (WCE ARMV4) Release
     
    REM Change to the location and name of your unmanaged project.
    echo %EVCPath% "E:\PROJECTS\PocketPC\MakeDemo\Unmanaged\Unmanaged.vcp" 
                               /make "Unmanaged - %ConfigEx%" %Arg%
    %EVCPath% "E:\PROJECTS\PocketPC\MakeDemo\Unmanaged\Unmanaged.vcp" 
                               /make "Unmanaged - %ConfigEx%" %Arg%
     
    if "%Arg%"=="/clean" goto end
     
    REM Change to the name of your unmanaged DLL and managed project.
    echo copy %Config%\Unmanaged.dll ..\Managed
    copy %Config%\Unmanaged.dll ..\Managed
     
    goto end
     
    :errNoArg
    echo "Invalid project configuration"
     
    :end
  20. Now we need to make sure that the unmanaged project gets built before the managed project. In the Solution Explorer, right click the solution and select Project Dependencies... from the pop-up menu. Select the Managed project in the Projects combo box, and tick Depends on the Unmanaged project.

  21. The mixed project solution is now set up. To try it out, open the Unmanaged.cpp file in the Unmanaged project. Change the fnUnmanaged function to:
    UNMANAGED_API int fnUnmanaged(LPCTSTR lpszMessage)
    {
        return MessageBox(NULL, lpszMessage, _T("Unmanaged"), 
                                     MB_OK | MB_ICONEXCLAMATION);
    }

    Ensure that the deployment device for the Managed project is Pocket PC 2003 Emulator and the current solution configuration is Debug. Build and deploy the solution by hitting F5. Clicking the Press Me button will now display a slightly different message box – the solution has automatically incorporated the changes we made to the unmanaged DLL.

  22. To debug on a hardware device, change the deployment device for the Managed project to Pocket PC Device. In the Configuration Manager, change the Unmanaged project configuration to e.g., ARMV4Dbg. Close the Configuration Manager and hit F5 to launch the application on the device.

You will find that you can now use Visual Studio .NET to edit source code and resources in both the managed and unmanaged projects, and have them build and deploy as you would expect. The only real restrictions are that if you make changes that affect the unmanaged project file itself (e.g., add new files to the project), then you need to make these changes in both eMbedded Visual C++ and Visual Studio .NET. In addition, any compiler and linker options can only be applied within eMbedded Visual C++. Remember to save the project in eMbedded Visual C++ before trying to use it from Visual Studio .NET.

Finally, it is not possible to debug from the managed project into the unmanaged code. To debug the unmanaged code, you need to use eMbedded Visual C++ as the debugging environment; in the Debug settings for the unmanaged project, set the local and remote executable to the name of the managed application. Alternatively, you can set the local executable to Visual Studio .NET itself - when you start debugging with eMbedded Visual C++, Visual Studio .NET will launch, which enables you to also debug your managed code.

Mixed Project Solutions and Setup Applications

There is a good article on Code Project from the MSDN team, which discusses Developing and Deploying Pocket PC Setup Applications. The article describes how to automatically generate cab files for multiple devices as part of the build step. The techniques work well with mixed project solutions, but the article does not explain how you add such a setup to an existing solution.

The following briefly describes how to add setup to our MakeDemo solution.

  1. Install the Deployment sample from the Developing and Deploying Pocket PC Setup Applications article.
  2. Copy the CustomInstaller and Setup projects in the sample to the MakeDemo folder, and add the two projects to the MakeDemo solution.
  3. Copy the BuildCabs folder from the sample PocketApp folder to the MakeDemo\Managed folder. Rename MakeDemo\Managed \BuildCabs\PocketApp_PPC.inf to Managed.inf. In the Solution Explorer, create a new BuildCabs folder in the Managed project, and add MakeDemo\Managed\BuildCabs\BuildCab.bat and MakeDemo\Managed\BuildCabs\Managed.inf to it.
  4. Copy the app.ico and Setup.ini files from the PocketApp folder to the MakeDemo\Managed folder, and add the files to the Managed project in Solution Explorer. Set the app.ico Build Action property to None. Set the Managed project Application Icon to app.ico. Add the following lines to the MakeDemoForm constructor in MakeDemoForm.cs to set the icon in the form to be the same as the Application Icon (app.ico):
    System.Resources.ResourceManager resources = 
       new System.Resources.ResourceManager(typeof(MakeDemoForm));
    this.Icon = ((System.Drawing.Icon)(resources.GetObject("$this.Icon")));
  5. Set the Project Dependencies as follows:
    CustomInstaller depends on Managed
    Managed depends on Unmanaged
    Setup depends on CustomInstaller

    The build order should then be: Unmanaged, Managed, CustomInstaller, Setup

  6. In the CustomInstaller project properties, set the Build Events to ..\..\..\Managed\BuildCabs\BuildCab.bat.
  7. In Configuration Manager, delete the Debug configuration for the CustomInstaller and Setup projects. For the Debug Active Solution Configuration, do not build the CustomInstaller and Setup projects. Make sure both are built for the Release Active Solution Configuration.
  8. In the Setup project properties, set the Output file name to Release\Managed Setup.msi for the Release configuration.
  9. Edit the MakeDemo\Managed\BuildCabs\BuildCab.bat to change file locations and the name of the inf file. You can also specify the processor types you are targeting. For simplicity, I am only targeting Emulator and ARMV4.

    You will need to build unmanaged DLLs for each of the target processors.

  10. Edit MakeDemo\Managed\BuildCabs\BuildCab.inf:
    • Change the SourceDiskNames section so that the entry points to the release output folder of your managed project.
    • Add a SourceDiskNames section for each target processor. This should point to the release output folder of the unmanaged project for the corresponding processor.
    • Add a Files section for each target processor. This should refer to the unmanaged component.
    • Add to the DestinationDirs section an entry for each Files section.
  11. Edit MakeDemo\Managed\Setup.ini to change the name of the cab files, e.g., Managed.ARMV4.CAB,Managed.Emulator.CAB.
  12. With the solution configuration set to Release, rebuild the CustomInstaller project. This will generate the cab files, which you can now add to the Setup project. In Solution Explorer, delete any existing cab files in the Setup project, and delete the Setup.ini file and CustomInstaller.dll file too (as these refer to the PocketApp project). Add the Setup.ini file from the MakeDemo\Managed folder and the cab files from the MakeDemo\Managed\BuildCabs\Cabs folder.
  13. Double click the Setup.ini file in the Setup project in Solution Explorer. This should open a File System on Target Machine tree view. Drag the Setup.ini file and cab files from the Application Folder to the Application Folder\Setup folder.
  14. Right click on the Setup project in Solution Explorer, and choose View, Custom Actions. Right click the Install custom action, choose Add Custom Action... from the pop-up menu, and in the Look in combo box, select Setup. Click Add File... and select the MakeDemo\CustomInstaller\bin\Release\CustomInstaller.dll file, and click Open and then OK. Repeat for the Uninstall custom action, but you should be able to pick the CustomInstaller.dll from the Select Item In Project dialog, and not have to go to Add File....
  15. Select the Setup project in Solution Explorer, and view its properties. Change the following properties: AddRemoveProgramsIcon, Author, Manufacturer, ProductName (e.g., Managed), and Title (Managed again). Also change ProductCode and UpgradeCode by clicking on the ... and generating a new code; this ensures your setup project does not get confused with the original Setup project that you copied.
  16. Rebuild the entire solution. The MakeDemo\Setup\Release folder should now contain a Managed Setup.msi that can be used to install the application on the target devices.

Other Environments

I have only tried the techniques in this article with Visual Studio .NET 2003 and eMbedded Visual C++ 4, but in principle, they should work with earlier versions of both products. I would be interested in anyone's experience with this.

History

09 April 2004 - New article.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here