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<const wchar_t> prfn = PtrToStringChars(RemoteFileName);
LPCWSTR pRemoteFileName = prfn;
pin_ptr<const wchar_t> 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.