And here is one way you can create a managed data structure which you can easily marshal to the unmanaged world.
[StructLayout(LayoutKind::Explicit, Size=18, CharSet=CharSet::Auto)]
public __gc class UsbDeviceDescriptor
{
public:
[FieldOffset(0)] System::Byte bLength;
[FieldOffset(1)] System::Byte bDescriptorType;
[FieldOffset(2)] System::UInt16 bcdUSB;
[FieldOffset(4)] System::Byte bDeviceClass;
[FieldOffset(5)] System::Byte bDeviceSubClass;
[FieldOffset(6)] System::Byte bDeviceProtocol;
[FieldOffset(7)] System::Byte bMaxPacketSize0;
[FieldOffset(8)] System::UInt16 idVendor;
[FieldOffset(10)] System::UInt16 idProduct;
[FieldOffset(12)] System::UInt16 bcdDevice;
[FieldOffset(14)] System::Byte iManufacturer;
[FieldOffset(15)] System::Byte iProduct;
[FieldOffset(16)] System::Byte iSerialNumber;
[FieldOffset(17)] System::Byte bNumConfigurations;
};
Use LayoutKind::Explicit
in the attribute and FieldOffset
in front of each field to make sure the memory layout is the same as the unmanaged stuct that this one will be marshaled to. Using CharSet=CharSet::Auto
will let the compiler choose between Ansi
or Unicode
. The CharSet
probably doesn't matter on this structure since there are no strings involved.
Here is how you define a managed class in C++:
public __gc class Usb
Now I will show you some bits and pieces of the code which is contained in the Usb class. If you want to see how it all fits together, take a look at the code in the download.
I wanted to report Win32 errors to my GUI layer, so I added an event. In hindsight it would have been better to use exceptions for this. Being able to trigger events in C++ is useful, so I will discuss this here instead of converting the code to use exceptions. Here is the C++ syntax for your delegate & event, which goes in the public section of your class.
__delegate void ErrorMessage( Object* sender, ErrorEventArgs* e );
__event ErrorMessage* OnErrorMessage;
Here is the code to fire the event:
void ReportError( String* msg )
{
if( OnErrorMessage )
{
OnErrorMessage( this, new ErrorEventArgs( msg ) );
}
}
void CheckWin32Error()
{
int errCode = GetLastError();
if( errCode != 0 )
{
char msg[256];
FormatMessage( FORMAT_MESSAGE_FROM_SYSTEM, NULL, errCode, 0, msg, 256, NULL );
ReportError( msg );
}
}
ReportError
and CheckWin32Error
, are declared as private. Note that the GetLastError
and FormatMessage
functions from the Win32 API is being used here. The ability to mix managed and unmanaged code like this is one of the reasons for using managed C++ instead of C# for this code.
If you were writing this code in C# you would want to use:
int errCode = Marshal.GetLastWin32Error();
to obtain the Win32 last error code. We need to obtain a HANDLE
to the device driver using CreateFile
. The HANDLE
is defined as private:
HANDLE _hEzUsb;
And the public function Open
, which is just a wrapper around the API function CreateFile
, with some error checking.
bool Open( String* driverName )
{
char* lpFileName = new char[driverName->Length + 10];
sprintf( lpFileName, "\\\\.\\%s", driverName );
_hEzUsb = CreateFile( lpFileName,
GENERIC_WRITE,
FILE_SHARE_WRITE,
NULL,
OPEN_EXISTING,
0,
NULL);
if( _hEzUsb == INVALID_HANDLE_VALUE )
{
CheckWin32Error();
}
delete lpFileName;
return( _hEzUsb != INVALID_HANDLE_VALUE );
}
Make sure the HANDLE
gets closed, when you are finished. I have defined a Close
function which can be called from the C# code:
void Close() { CloseHandle( _hEzUsb ); }
If you forget to call Close
, before calling Open
again, strange things will happen. I spent a lot of time trying to figure out why I the code on the EZ-USB FX board wouldn't run a second time. After adding calls to Close
in my test routines, the problem disappeared.
Comments