Codwiz51's Wiki

This is a short article about porting to C++/CLI. I use a function taken from OpenNETCF'sOpenNETCF's Desktop communication assembly (OpenNETCF.Desktop.Communication.dll) as an example. The assembly is written in C#, using the SharpDevelopSharpDevelop IDE. The reason I chose this code is that it is similar to a lot of C# code that utilizes P/Invoke for API calls. In the case of this code, two API's are utilized: Windows and ActiveSync/RAPI.

Here is the C# code that I will convert to managed C++:
// P\Invoke declarations{BR}
[DllImport("rapi.dll", CharSet=CharSet.Unicode, SetLastError=true)]
internal static extern IntPtr CeCreateFile(
	string lpFileName, 
	uint dwDesiredAccess,
	int dwShareMode,
	int lpSecurityAttributes,
	int dwCreationDisposition,
	int dwFlagsAndAttributes,
	int hTemplateFile);


[DllImport("rapi.dll", CharSet=CharSet.Unicode, SetLastError=true)]
internal static extern int CeWriteFile(
	IntPtr hFile,
	byte[] lpBuffer,
	int nNumberOfbytesToWrite,
	ref int lpNumberOfbytesWritten,
	int lpOverlapped);

[DllImport("rapi.dll", CharSet=CharSet.Unicode, SetLastError=true)]
internal static extern int CeReadFile(
	IntPtr hFile,
	byte[] lpBuffer,
	int nNumberOfbytesToRead,
	ref int lpNumberOfbytesRead,
	int lpOverlapped);

[DllImport("rapi.dll", CharSet=CharSet.Unicode)]
internal static extern int CeCloseHandle(IntPtr hObject); 


This code calls the platform invoke declarations.


/// <summary>
/// Copy a device file to the connected PC
/// </summary>
/// <param name="LocalFileName">Name of destination file on PC</param>
/// <param name="RemoteFileName">Name of source file on device</param>
/// <param name="Overwrite">Overwrites existing file on the device if <b>true</b>, fails if <b>false</b></param>
public void CopyFileFromDevice(string LocalFileName, string RemoteFileName, bool Overwrite)
{
	// check for connection
	CheckConnection();

	FileStream localFile;
	IntPtr remoteFile = IntPtr.Zero;
	int bytesread = 0;
	int create = Overwrite ? CREATE_ALWAYS : CREATE_NEW;
	byte[] buffer = new byte[0x1000]; // 4k transfer buffer

	// open the remote (device) file
	remoteFile = CeCreateFile(RemoteFileName, GENERIC_READ, 0, 0, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0);

	// check for success
	if ((int)remoteFile == INVALID_HANDLE_VALUE)
	{
		throw new RAPIException("Could not open remote file");
	}

	// create the local file
	localFile = new FileStream(LocalFileName, Overwrite ? FileMode.Create : FileMode.CreateNew, FileAccess.Write);

	// read data from remote file into buffer
	CeReadFile(remoteFile, buffer, 0x1000, ref bytesread, 0);
	while(bytesread > 0)
	{
		// write it into local file
		localFile.Write(buffer, 0, bytesread);

		// ***************************************************************		
		// I am suspicious that the next call results in unnecessary overhead.
		// The buffer must be managed loaded from the unmanaged code and
		// then transferred to the managed heap for processing in the 
		// the localFile.Write call.
		// ***************************************************************		

		// get more data
		if(! Convert.ToBoolean(CeReadFile(remoteFile, buffer, 0x1000, ref bytesread, 0)))
		{
			CeCloseHandle(remoteFile);
			localFile.Close();
			throw new RAPIException("Failed to read device data");
		}
	}

	// close the remote file
	CeCloseHandle(remoteFile);

	localFile.Flush();

	// close the local file
	localFile.Close();
}

As I examined this code, I realized there can be quite a bit of context switching between managed and unmanaged code. Inside the while loop, the read write buffer byte[] buffer is moved from managed code to native code by InteropServices often for big files. No parameters are passed back to the caller, so this appeared to be a good example of when to use mixed mode coding. I am able to isolate the managed code to a very short section used to translate the parameters to native code, and the rest of the code runs in native mode, unless an exception occurs.

Here is my translation of the above code to C++/CLI.

1. Simply start a Visual C++ CLR Class Library Project. This will create a .h and .cpp file. It will also create an stdafx.h and .cpp file.

2. Next, add some include file statements and definitions to the stdafx.h. Not all of these are necessary for this example.

// This is a Windows XP SP2 specific program
#define WINVER	0x0502
#define _WIN32_WINNT	0x0502
#define _WIN32_IE	0x0700
#define _RICHEDIT_VER	0x0300

#ifndef _WIN32_DCOM 
#define _WIN32_DCOM 
#endif

#include <atlbase.h>
#include <atlstr.h>
#include <vcclr.h>
#include <rapi.h>

3. All of the DllImport attributes and imported API calls can be removed. They are no longer needed for this code.

4. The method call ends up looking like this:

#define BUFFERSIZE 0x1000
void RAPI::CopyFileFromDevice(String^ LocalFileName, String^ RemoteFileName, Boolean Overwrite)
{
	// Throw an exception if there is no connection
	CheckConnection();
	
	// Native variables for file copy
	DWORD nBytesRead = 0;
	DWORD nBytesWritten = 0;
	BYTE FileCopyBuffer[BUFFERSIZE];
	LPBYTE pFileCopyBuffer = FileCopyBuffer;
	BOOL bOverWrite = Overwrite;
	
	DWORD dwCreateDisposition = (Overwrite ? CREATE_ALWAYS : CREATE_NEW);

	// Convert managed parameters to native
	pin_ptr prfn = PtrToStringChars(RemoteFileName);
	LPCWSTR pRemoteFileName = prfn;
	pin_ptr plfn = PtrToStringChars(LocalFileName);
	LPCWSTR pLocalFileName = plfn;

	// Begin native code

	// Open a file to source on the device
	HANDLE HRemoteFile = ::CeCreateFile(pRemoteFileName, GENERIC_READ, NULL, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
	
	// Throw an exception if file create failed.
	if (INVALID_HANDLE_VALUE == HRemoteFile)
	{
		throw gcnew RAPIException("Could not open remote file") ;
	}
	
	// Open a file to the target on the PC
	HANDLE HLocalFile = ::CreateFile(pLocalFileName, GENERIC_WRITE, NULL, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
	
	// Throw an exception if file create failed.
	if (INVALID_HANDLE_VALUE == HLocalFile)
	{
		throw gcnew RAPIException("Could not create/open local file") ;
	}
	
	// Read the first chunk from the source file
	::CeReadFile(HRemoteFile, pFileCopyBuffer, BUFFERSIZE, &nBytesRead, NULL);

	// Loop, reading/writing file chunks until EOF is reached
	while ((nBytesRead > 0))
	{
		// Write the buffer to the target file
		if (::WriteFile(HLocalFile, pFileCopyBuffer, nBytesRead, &nBytesWritten, NULL))
		{
			// Write failed. Close all handles and throw
			::CeCloseHandle(HRemoteFile);
			::CloseHandle(HLocalFile);
			throw gcnew RAPIException("Failed to write target file data") ;
		}
		// Read the next chuck of the source file
		if (!::CeReadFile(HRemoteFile, pFileCopyBuffer, BUFFERSIZE, &nBytesRead, 0))
		{
			// Read failed. Close all handles and throw
			::CeCloseHandle(HRemoteFile);
			::CloseHandle(HLocalFile);
			throw gcnew RAPIException("Failed to read device data") ;
		}
	}
	// No errors, close file handles	
	::CeCloseHandle(HRemoteFile);
	::CloseHandle(HLocalFile);
}

This code manages to get around all of the InteropServices calls. The code in this instance will not be substantially faster than the C# code for two reasons: disk i/o and write across a USB link to the external device. However, there is some small improvement because the buffer used to translate data is not managed by InteropServices.


ScrewTurn Wiki version 2.0.36. Some of the icons created by FamFamFam.