Dirk Bertels

The greatest malfunction of spirit
is to believe things (Louis Pasteur)

C++/CLI code excerpts

Last updated 27 Jul 2012


Index

Introduction
Delegate Mechanism
USB Mass Storage Device Capture
Spawning your default email client with attachments
Integrating a client database using OleDbDataAdapter
Integrating a client database using OleDbDataReader
The 'Microsoft.Jet.OLEDB.4.0' provider is not registered on the local machine ERROR
Using the DateTime type
Unlocking an image from its picturebox
Write metadata to a JPEG file
Reading metadata from a JPEG file
The Singleton
Passing a form's handle
Passing a pointer by reference
Printing formatted strings
Deploying a Visual Studio Express Project
Links and References
Comments

Introduction

The main intention of this page is to build some kind of archive I can use (and share) for future reference. It is a compilation of various code excerpts in Managed C++ (also called C++/CLI), accumulated during some years of writing software in this language.

Some code excerpts are quite basic, some more complicated. Many topics took quite some research, these include using delegates, intercepting USB devices, TCP communication, using client database, Shell Extensions and integrating a propriety library. All the code has been used and experimentally proven to work - of course this doesn't mean that the solutions are the best ones, so please feel free to comment.

It is certainly not my intention to give a complete rendition on managed C++. Others have done this (and can do it much better than I) - see the link section at the end of this page for some interesting artices and websites.

All the code has been written using the Visual Studio Express 2008 IDE. No fancy extensions such as MFC or ATL are used. C++/CLI is very powerful since it incorporates native C++ and C, thus facilitating integration with embedded systems.

When doing research on C++/CLI, you may encounter the old syntax of C++/CLI which you'll recognise by its multiple underscores at the start of many keywords, such __property and __gc. This has now been replaced by the new, much easier to read syntax.


Delegate Mechanism

Delegates are really type-safe, object oriented function pointers. For more on function pointers in C and C++, go to Function Pointers in C and C++.

Since we are in managed C++, the delegate also needs to take into account that we are working with handles instead of pointers, the location of which are not fixed.

Methods are a little more complicated than functions. When a method is invoked on an instance, 2 pointers are required: a pointer to the location of that method in its class and a (hidden) pointer to the particular instance the method is called upon. This instance contains the state information that is most likely needed. Delegates encapsulate both these requirements. Like function pointers, they reference a method to enable any functionality you want at runtime. A delegate can be assigned any other delegate where the method signatures match.

The following excerpt uses a delegate in order to access a GUI component from another thread:

// delegate function to write to textbox
delegate void writeToTB_del(String^ str);
void myForm::WriteToTB(String^ str)
{
	if(!myForm->textBox1->InvokeRequired){
		myForm->textBox1->Text = str;
	}
	else{
		writeToTB_del^ thisDelegate = gcnew writeToTB_del(this,&myForm::WriteToTB);
		this->Invoke(thisDelegate, str);
	}
}
		

If you didn't use a delegate in the above situation, you would get an error claiming something like Component accessed from a thread other than the one that created it.
You also use delegates when a class needs to access a form that already includes your class - which would otherwise cause circular referencing:

////////////
// Main.h //
////////////
#include "Other.h"
...
namespace ns
{
   class Main
   {
   private:
      Other^ other;
      String^ HandleFunction(String^ str, int mode);
      ...	
   }
}

//////////////
// Main.cpp //
//////////////
...
void StartOther()
{
   other = gcnew Other();
   other->E_other += gcnew EH_other(this, &Main::HandleFunction);
}

String ^ Main::HandleFunction(String ^str, int mode)
{
   ...
   this->AppendFormTextbox(str); //access a form control 
}

delegate void threadTask(String^ str);
void Main::AppendFormTextbox(String^ str)
{
   if(this->someTextbox->InvokeRequired == false){
      this->someTextbox->AppendText(str);
   }
   else{
      threadTask^ thisDelegate = gcnew treadTask(this, &Main::AppendFormTextbox);
      this->invoke(thisDelegate,str);
   }
}	

/////////////
// Other.h //
/////////////
namespace ns
{
	public delegate String^ EH_other(String^ str, int i);

	class cl
	{
	public:
		event EH_other^ other;
		...	
	}
}

///////////////
// Other.cpp //
///////////////
...
{	
	...
	E_other("blah blah", 2);
	...
}
...

Main can include Other, but to avoid circular referencing, Other cannot include Main. An extra delegate mechanism (like the one described in the first part) is included in case a form element is accessed from another thread.

back to index


USB Mass Storage Device Capture

When an application has to capture a USB Mass Storage Device it also needs to surpress the notification window that the operating system pulls up to notify you that a USB device has been plugged in. Cancelling this window uses the Windows QueryCancelAutoPlay message. Capturing USB devices is handled at a lower level, so the software needs to override the Windows Procedure. The links and references section list the details on the book Programming Windows(5th edition), the universally claimed authority on the subject of windows programming.

using namespace System::Security::Permissions;			// to override WndProc()
// ------------------------------------------------------------------------------------------
// METHOD: WndProc 
// Overrides Control::WndProc(Message)
// The WindowProc function is an application defined function that processes messages sent to 
// a window. The WNDPROC type defines a pointer to this callback function. WindowProc is a 
// placeholder for the application defined function name. 
// ------------------------------------------------------------------------------------------
[SecurityPermission(SecurityAction::Demand, Flags=SecurityPermissionFlag::UnmanagedCode)]
void ThisForm::WndProc( Message% m)
{
	//Call the base first, otherwise the values you set later will be lost
	__super::WndProc(m); 

	//find these definitions in dbt.h
	const int DBT_DEVICEARRIVAL = 0x8000;			// New device detected
	const int DBT_DEVICEREMOVECOMPLETE = 0x8004;	// Device Removed
	const int DBT_DEVTYP_VOLUME = 0x00000002;		// Device is a volume

	// Register WindowMessage to make sure Windows sends the "QueryCancelAutoPlay" message to the form 
	// Application window needs to be in the foreground to receive this message!!
	if (queryCancelAutoPlay == 0){
		queryCancelAutoPlay = RegisterWindowMessage("QueryCancelAutoPlay");
	}
	//if the window message id equals the QueryCancelAutoPlay message id
	if ((int)m.Msg == queryCancelAutoPlay){
		m.Result = (IntPtr)1;
	}
	switch (m.Msg){
		case WM_DEVICECHANGE:
			switch(m.WParam.ToInt32()){
				case DBT_DEVICEARRIVAL:
				{
					int devType = Marshal::ReadInt32(m.LParam,4); // read from unmanaged memory
					if(devType == DBT_DEVTYP_VOLUME){
						DEV_BROADCAST_VOLUME vol;
						vol = (DEV_BROADCAST_VOLUME)Marshal::PtrToStructure(m.LParam,VI_MAIN::Form1::DEV_BROADCAST_VOLUME::typeid);
						// 
						// DO YOUR STUFF HERE
						//
					}	// END if(devType == DBT_DEVTYP_VOLUME)
				}		// END case DBT_DEVICEARRIVAL:
				break;

				case DBT_DEVICEREMOVECOMPLETE:
					//Debug::WriteLine("Device Removal detected");
				break;
			} // END SWITCH
		break;
	} // END SWITCH
}

back to index


Spawning your default email client with attachments

To spawn your default email client, a simple line of code like the following can do the deed:

ShellExecute( 	hwndYourWindow, 
				"open", 
            	"mailto:user@domain.com?subject=Subject Here", 
            	NULL, NULL, SW_SHOW );

But opening the email client with attachments ready to go is more difficult. We will use a Shell Extension - which is a COM object that adds some kind of functionality to the Windows shell (Explorer). You can observe what we are trying to do here by right clicking on a folder and selecting Send to → Mail recipient. See the following references for more clarification on the code to follow:

There are two major techniques involved here: One involves the use of a COM object, which is initialised with

CoInitialize(NULL)
The other is simulating a drop to 'drop' the attachments into the default email client window.

To get a feel of what COM is about I recommend Michael Dunn's article Introduction to COM - What It Is and How to Use It. It is concise and very clear - even a 'clodger' like me can understand it. I take no credit for this code, I just managed to make it work for C++/CLI - most of the credit goes to long-time MS employee Raymond Chen. His blogs at Old new thing, the source of most links above, gives a fascinating insight into windows programming. Old new thing is a subsidiary (if that's the right word) of the Microsoft blog site , where many of its employees blog to a public audience.

#include <windows.h>
#include <shlobj.h> // DROPFILES
#include <stdlib.h> // _dupenv_s
using namespace System;
// This simple function takes a path and gets a shell UI object from it. 
// We convert the path to a pidl with SHParseDisplayName, then bind to the 
// pidl's parent with SHBindToParent, then ask the parent for the UI object 
// of the child with IShellFolder::GetUIObjectOf
HRESULT GetUIObjectOfFile(HWND hwnd, LPCWSTR pszPath, REFIID riid, void **ppv)
{
	*ppv = NULL;
	HRESULT hr;
	LPITEMIDLIST pidl;
	SFGAOF sfgao;
	// Translates a Shell namespace object's display name into an item identifier list 
	// and returns the attributes of the object. This function is the preferred method 
	// to convert a string to a PIDL.
	hr = SHParseDisplayName(pszPath, NULL, &pidl,SFGAO_CANCOPY,&sfgao);
	if(SUCCEEDED(hr)){
		IShellFolder *psf;
		LPCITEMIDLIST pidlChild;
		if (SUCCEEDED(hr = SHBindToParent(pidl, IID_IShellFolder,(void**)&psf, &pidlChild))) {
			hr = psf->GetUIObjectOf(hwnd, 1, &pidlChild, riid, NULL, ppv);
			psf->Release();
		}
		CoTaskMemFree(pidl);
	}
	return hr;
}

// This is the method that actually gets called with the path to the filename to attach
int OpenClientWithAttachments(System::String^ pathToFile)
{
	// convert managed string to wchar_t*
	// ref http://msdn.microsoft.com/en-us/library/d1ae6tz5%28VS.80%29.aspx
	// Pin memory so GC can't move it while native function is called
	pin_ptr<const wchar_t> wch = PtrToStringChars(pathToFile);
	const wchar_t* attachmentsStr = wch;
	// convert the path to the mapi file also
	pin_ptr<const wchar_t> wch = PtrToStringChars(GetUserEmailSendToPath());
	const wchar_t* mapiPath = wch;

	if (SUCCEEDED(CoInitialize(NULL))) {
		{	// don't remove scope brackets!!
			// IDataObject: Enables data transfer and notification of changes in data
			// Any object that can receive data, calls the methods in the IDataObject interface.
			IDataObject *pdto;	// attachment object
			if (SUCCEEDED(GetUIObjectOfFile(NULL, attachmentsStr, IID_IDataObject, (void**)&pdto))) {
				IDropTarget *pdt; // target email client application
				if (SUCCEEDED(GetUIObjectOfFile(NULL, mapiPath, IID_IDropTarget, (void**)&pdt))) {
					POINTL pt = { 0, 0 };
					DWORD dwEffect = DROPEFFECT_COPY | DROPEFFECT_LINK; 
					if (SUCCEEDED(pdt->DragEnter(pdto, MK_LBUTTON, pt, &dwEffect))) {
						dwEffect &= DROPEFFECT_COPY | DROPEFFECT_LINK;
						if (dwEffect) {
							// DROPEFFECT dwEffect return should be 1 after the following line
							 pdt->Drop(pdto, MK_LBUTTON, pt, &dwEffect);
						} 
						else {
							 pdt->DragLeave();
						}
					}
					 pdt->Release();
				}
				pdto->Release();
			}
		} // ensure p is destructed before the CoUninit
		CoUninitialize();
	}
	return 0;
}

#undef GetEnvironmentVariable // otherwise the windows API maps this to GetEnvironmentVariableW()
String^ GetUserEmailSendToPath()
{
	String^ tagPath = "";
	// TODO: read http://blogs.msdn.com/b/oldnewthing/archive/2003/11/03/55532.aspx
	// suggests using SHGetSpecialFolderLocation() or Environment::GetFolderPath()
	String^ userProfilePath = System::Environment::GetEnvironmentVariable("USERPROFILE");
	// get OS
	String^ thisVersion = System::Environment::OSVersion->ToString();
	if(thisVersion->Contains("XP")){
		tagPath = "\\SendTo\\Mail Recipient.MAPIMail";
	}
	else if(thisVersion->Contains("NT")){	// vista and windows 7
		tagPath = "\\AppData\\Roaming\\Microsoft\\Windows\\SendTo\\Mail Recipient.MAPIMail";
	}
	else{
		return String::Empty;
	}
	String^ fullPath = String::Concat(userProfilePath, tagPath);
	return fullPath;
}

back to index


Integrating a client database using OleDbDataAdaptor

A client database is a database that is implemented on the client's computer, not on a server. The app comes integrated with a local database included. There are various ways of doing this, but I opted to use an Access database since, being MS, it integrates well with Visual Studio. There's ample documentation about how to do this. Before integrating MS Access into Visual Studio, make sure you read about the database engine being used: The MS Jet Engine. To access the data gotten from several rows in a table:

DataSet^ ds = retrieveDS(query);	// local method that retreives data from a table
if(ds != nullptr && ds->Tables["YourTable"]->Rows->Count != 0){ 
	DataRowCollection^ dr = ds->Tables["YourTable"]->Rows;
	for each(DataRow ^adr in dr){
		String^ str = adr[0]->ToString();
		SomeObject^ thisObject = (SomeObject)adr[1];
		...
	}
}

When retrieving data from just one row, you don't need a DataRowCollection:

DataSet^ ds = retrieveDS(query);	// local method that retreives data from a table
if(ds != nullptr && ds->Tables["YourTable"]->Rows->Count != 0){ 
	DataRow^ dr = ds->Tables["YourTable"]->Rows[0];
	// make sure it isn't empty as would be the case when starting new DB
	if(dr[0]!= DBNull::Value){ 
		String^ str = adr[0]->ToString();
		SomeObject^ thisObject = (SomeObject)adr[1];
		...
	}
}

public:
		static DataSet^ retrieveDS(String^ strAccessSelect, String^ tableName)
		{
			String ^strAccessConn = "Provider=Microsoft.Jet.OLEDB.4.0;Data Source=<path to database file>";
			DataSet^ ds = gcnew DataSet();
			ds->Tables->Add(tableName);
			
			// Create Access objects:
			OleDbConnection ^accessConn = gcnew OleDbConnection(strAccessConn);
			OleDbCommand ^accessCommand = gcnew OleDbCommand(strAccessSelect,accessConn);
			OleDbDataAdapter ^dataAdapter = gcnew OleDbDataAdapter(accessCommand);

			try{
				accessConn->Open();
				dataAdapter->Fill(ds,tableName);
			}
			catch(Exception ^e){
				Debug::WriteLine(e->Message);
				return nullptr;
			}
			finally{
				accessConn->Close();
			}
			return ds;
		}

back to index


Integrating a client database using OleDbDataReader

Here's a code excerpt using a OleDbDataReader, it also shows how to reformat a DateTime:

String ^ strConnection = "Provider=Microsoft.Jet.OLEDB.4.0;Data Source=yourPath\\yourDatabase.mdb";
String ^ strQuery = "SELECT Last_Name, Initials, First_Name, Mobile, Email, DOB, Address FROM VI_Patient";
oleDbConnection = gcnew OleDbConnection(strConnection);
//sqlConnection = gcnew SqlConnection(strConnection);
oleDbConnection->Open();
//sqlConnection->Open();
oleDbCommand = gcnew OleDbCommand(strQuery, oleDbConnection);
//sqlCommand = gcnew SqlCommand(strQuery, sqlConnection);
oleDbDataReader = oleDbCommand->ExecuteReader();
//sqlReader = sqlCommand->ExecuteReader();
//while (sqlReader->Read()){
while(oleDbDataReader->Read()){
   //Reformat dateTime
   String ^ thisDate = String::Empty;
   if (oleDbDataReader->GetFieldType(5)->ToString() =="System.DateTime"){
	  if (oleDbDataReader[5] != System::DBNull::Value){
		 DateTime^ dt = (DateTime^)oleDbDataReader[5];
		 thisDate = dt->ToString("dd MMM yyyy");
	  }
   }
   array<System::Object^>^ row = { oleDbDataReader[0], oleDbDataReader[1], oleDbDataReader[2], 
	              oleDbDataReader[3], oleDbDataReader[4], thisDate, oleDbDataReader[6] };
   this->dataGridView1->Rows->Add(row);	
}	

back to index


The 'Microsoft.Jet.OLEDB.4.0' provider is not registered on the local machine ERROR

This thread provides solutions for the said error. One solution that addresses the error in relation to the Visual Studio Express edition has proven to work in my case:

There is no 64 bit Jet version of the database engine. For those people with a Visual Studio Express Edition that want to compile in x86 (since VS Express doesn't expose the option) do the following:

  1. In Windows Explorer, right-click your project file (*.csproj, *.vbproj, etc..) and Open With Notepad (the project file is an XML document)
  2. Find the section <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' "> and <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
  3. Find (or add) the element <PlatformTarget>x86</PlatformTarget>
  4. Save and close Notepad
  5. Reload your project and compile

back to index


Using the DateTime type

Get the system's DateTime:

//get computer's time
System::DateTime now = System::DateTime::Now;

An example showing how to reformat a DateTime once you get it from a DataSet's DataRow:

DataRowCollection ^dra1 = ds1->Tables["aTable"]->Rows;	//get the row
for each(DataRow ^dr1 in dra1){
	String^ thisDate = ((DateTime)dr1[0]).ToString("dd MMM yyyy");
	...
}

And to convert from a string to a DateTime:

DateTime^ thisDate = gcnew DateTime();
thisDate = Convert::ToDateTime(dr[0]);

Alternatively, you could use Split() and Convert::ToInt32() to convert text fields to ints, and use the values in the DateTime constructor:

DateTime^ dt = gcnew DateTime(yearField, monthField, dayField, hourField, minuteField, secondField);

To use DateTime in a query (in this case for an Access database):

// use datetime in a query (for Access database) - note the hash tags
String^ query = "SELECT MAX(someColumn) From someTable WHERE someField = " 
+ aValue
+ " AND someDateTimeField = #" 
+ someDateTime->ToString("MM'/'dd'/'yy")
+ "#";

If you can, use a DateTimePicker. It simplifies things and ensures you can't get situations where dates don't exist or are wrongly formatted.

Converting a DateTime to a String:

String ^dob = dateTimePicker1->Value.ToString("dd/MM/yyyy");	// D.O.B.

Getting the DateTime from the dateTimePicker:

DateTime dob = 	patient->dob_dt = dateTimePicker1->Value;

You may want to have the option of a blank value in the DateTimePicker because the user may choose not to select a datetime. The following code could be used in a Checkbox event:

// blank datetimepicker (checkbox ticked event)
dateTimePicker1->Format = DateTimePickerFormat::Custom;
dateTimePicker1->CustomFormat = " ";
// revert back to format (checkbox not ticked event)
dateTimePicker1->Format = DateTimePickerFormat::Custom;
dateTimePicker1->CustomFormat = "dd/MMM/yyyy";

back to index


Unlocking an image from its picturebox

When an image is allocated to a picturebox, the component tends to lock this image resulting in an error when trying to erase this image from the pictureBox. A way around this is to create a temporary copy:

// using automatic storage semantics
Image^ i = Image::FromFile("pathToImage");			//open the file
Image^ t = gcnew Bitmap(i->Width, i->Height);		//create temporary
Graphics^ g = Graphics::FromImage(t);				//get graphics

g->DrawImage(i,0,0,i->Width, i->Height);
i->~Image();
pb->Image = t;
pb->SizeMode = PictureBoxSizeMode::StretchImage;	//adjust image size to picturebox

back to index


Write metadata to a JPEG file

Following example writes metadata to the UserComments, DateTimeOriginal and CreateDate filetags in the JPEG (EXIF) Header. Refer to reading and writing metadata and propertyitem documentation

public: 
static bool WriteMetaDataToJpeg(DateTime someDateTime, String^ pathFromImg, String^ pathToImg)
{
   Image^ im = Image::FromFile(pathFromImg);	//open the file
   // WRITE DATA TO USERCOMMENT DATATAG
   // ---------------------------------
   int userCommentsId = 0x9286; 
   String^ imgData = //"ASCII   Your Heading Text: "  
	+ " someName=" + someValue
	+ " | anotherName=" + anotherValue
	...;

   PropertyItem^ pi = im->PropertyItems[0];	
   SetProperty(pi, userCommentsId, imgData);
   im->SetPropertyItem(pi);
   
   // WRITE DATE TO DATETIMEORIGINAL FILETAG
   // --------------------------------------
   // reformat date
   String ^ sDate = someDateTime->ToString("yyyy:MM:dd HH:mm:ss");
   int datetimeOriginalId = 0x9003;
   PropertyItem^ pi2 = im->PropertyItems[0];
   SetProperty(pi2, datetimeOriginalId, sDate);
   im->SetPropertyItem(pi2);

   // WRITE DATE TO CREATEDATE FILETAG
   // --------------------------------
   int createDateId = 0x9004;
   PropertyItem^ pi3 = im->PropertyItems[0];
   SetProperty(pi3, createDateId, sDate);
   im->SetPropertyItem(pi3);

   // SAVE FILE
   // ---------
   try{
      im->Save(pathToImg);
   }
   catch (Exception^ e){
      Debug::WriteLine(e->Message);
      return false;
   }
   im->~Image();
   return true;
}

// for ASCII property items only
// Exif 2.2 requires that ASCII property items terminate with a null (0x00).
private: 
static void SetProperty(Imaging::PropertyItem ^prop, int iId, String ^ sTxt)
{
	int iLen = sTxt->Length + 1;
	array<unsigned char,1>^ c = gcnew array<unsigned char,1>(iLen);
	for (int i = 0; i < iLen - 1; i++){
		c[i] = sTxt[i];
	}
	c[iLen -1] = 0x00;
	prop->Id = iId;
	prop->Type = 2;
	prop->Value = c;
	prop->Len = iLen;
 }

back to index


Reading metadata from a JPEG file

This example reads the UserComments field. To read metadata from a JPEG, the GetProperty() method is used:

public: 
static String^ ReadMetaDataFromJpeg(String^ pathToImg)
{
	String^ s; 
	int userCommentsId = 0x9286; 
	Image^ im = Image::FromFile(pathToImg);		
    try{
       PropertyItem^ thisPropertyItem = im->GetPropertyItem(userCommentsId);
       int iLen = thisPropertyItem->Len;
       array^ c = gcnew array(iLen);
       c = thisPropertyItem->Value;
       System::Text::Encoding^ enc = System::Text::Encoding::UTF8; 
       s = enc->GetString(c);
       return s;
    }
	catch (Exception^ e){
       return String::Empty;
	}
}

back to index


The Singleton

The Singleton is one of the simplest design patterns based on the mathematical concept of a Singleton (a set with only one element). It ensures that only one instance of a class is realised in order to coordinate actions accross a system. For that reason it is often frowned upon as being ... GASP ... anti Object Oriented! Nevertheless I've found it quite useful in situations where only one object is needed - for example you may want to establish just one object that implements networking functionality for other objects to access. The Singleton structure works as follows:

//File1.h
public ref class File1
{
   public:
      static File1 file1Instance;
      static File1^ getInstance();
      ...
}

//File1.cpp
#include "Form1.h"	// can do
File1^ getInstance();
{
   return %file1Instance;
}

//Form1.h
#include "File1.h"
public ref class Form1 : ...
{
   public:
      File1^ file1;
      ...
}

// Form1.cpp
...
file1 = File1->getInstance();
file1-> ...
...

back to index


Passing a form's handle

This is just an example to show that a form can simply pass a handle to itself for the called object to access:

// Form1
...
Form2 ^ form2 = gcnew Form2(this);
form2->Show();
...

// Form2
private Form1^ pForm1;
Form2(Form1^ pf)
{
   pForm1 = pf;
}

SomeMethod()
{
   pForm1-> ...
  ...
}	

back to index


Passing a pointer by reference

The argument in a function such as

	
fn(XmlDocument ^xdoc)

will result in the fn doing its thing to a local copy of xdoc. However, if you want to do things to the actual object, you need to pass a reference to a reference. To do this use the following syntax:

fn(XmlDocument %^xdoc)

The calling syntax stays the same. In standard C++, to modify a pointer passed as an argument, the function would be

fn(SomeObject **pp)

and the caller would use

fn(&pp)

back to index


Printing formatted strings

While you can print formatted strings on the Console, such as

		
Console::WriteLine(A->ToString("R", ",", "{", "},\n", "}\n"));

there is no such facility for printing to the Debug output using Debug::WriteLine(), instead use Debug::Print(), e.g.

Debug::Print("contents of {0}: ", filename);

back to index


Deploying a Visual Studio Express Project

I had quite a few issues with deploying a visual Studio 2008 project so my application could be installed on all PC systems from the lowly XP 32 bit SP3 to the Windows 7 64 bit. Finally had success after porting the whole project to Visual Studio 2010 and deploying it with Framework 4.0 (which has a 40 MB footprint) and the VS C++ Redistributable Package. These 2 packages together with a Propriety DLL installer were successfully integrated into one installer package, NSIS.

A side note on Visual Studio Express 2010: IT HAS NO INTELLISENSE! So if you're used to working with intellisense it comes as quite a shock to realise how much you actually rely on it.

NSIS (Nullsoft Scriptable Install System) is a professional open source system to create Windows installers. At first, its script language seems a bit tedius, but it is very powerful and there are many examples on the net. I plan to cover NSIS in a separate chapter...

back to index


Links and References

My main source of information is the MSDN Website. The Library section is a good API reference.

Programming Windows(5th edition) by Charles Petzold (ISBN 13: 978-1-57231-995-0) is the universal bible on the fascinating topic of windows programming. Almost 1500 pages, but surprisingly lucid.

The Functionx website has a series of practical tutorials.

The freely available book Expert C++/CLI is a good read, especially if you want to delve a little deeper into the subject.

Manning's ebook C++/CLI in Action used to be freely available, but not anymore it seems.


Comments