28 December 2008

Make a bootable USB Flash Drive from a floppy image file (VB.Net)

To do this you just need a handle to the drive (the device handle), and then use WriteFile to copy the image to the drive. Here's a rough example.

At the moment it rounds the byte array containing the image file up to the nearest 512 bytes, as otherwise it returns "invalid parameter".

Imports System.IO
Imports System.Text

Public Class Form1

    Private sourceFile As FileInfo

    Private Sub Form1_Load(ByVal sender As Object, ByVal e As EventArgs) Handles MyBase.Load
        RefreshDrives()
    End Sub

    Private Sub btnBrowse_Click(ByVal sender As Object, ByVal e As EventArgs) _
        Handles btnBrowse.Click
        Using ofd As New OpenFileDialog
            ofd.Title = "Select disk image file"
            ofd.Multiselect = False
            Dim result As DialogResult = ofd.ShowDialog
            If result = Windows.Forms.DialogResult.OK Then
                Try
                    sourceFile = New FileInfo(ofd.FileName)
                Catch ex As Exception
                    MessageBox.Show(ex.Message)
                    sourceFile = Nothing
                End Try
            End If
        End Using
        If sourceFile IsNot Nothing Then
            Me.TextBox1.Text = sourceFile.ToString
        End If
        RefreshBtn_Nuke()
    End Sub

    Private Sub btnRefresh_Click(ByVal sender As Object, ByVal e As EventArgs) _
        Handles btnRefresh.Click
        RefreshDrives()
    End Sub

    Private Sub RefreshDrives()
        Me.ComboBox1.Items.Clear()
        For Each di As DriveInfo In DriveInfo.GetDrives
            If di.DriveType = DriveType.Removable Then
                Me.ComboBox1.Items.Add(di)
            End If
        Next
        Me.ComboBox1.SelectedIndex = 0
        RefreshBtn_Nuke()
    End Sub

    Private Sub RefreshBtn_Nuke()
        btnNuke.Enabled = (sourceFile IsNot Nothing) AndAlso _
            Me.ComboBox1.Items.Count > 0
    End Sub

    Private Sub btnNuke_Click(ByVal sender As Object, ByVal e As EventArgs) Handles btnNuke.Click
        Dim result As DialogResult
        Dim drive As String = Me.ComboBox1.Text
        Dim sb As New StringBuilder
        sb.AppendLine("Do you really want to erase drive " & drive & "?")
        sb.AppendLine("Doing so will DESTROY ALL EXISTING DATA ON DRIVE " & drive & "!")
        result = MessageBox.Show(sb.ToString, _
                                 "Destroy Drive " & drive & " ?", _
                                 MessageBoxButtons.YesNo, _
                                MessageBoxIcon.Exclamation, _
                                 MessageBoxDefaultButton.Button2)
        If result = Windows.Forms.DialogResult.Yes Then
            Dim dest As DriveInfo = TryCast(Me.ComboBox1.SelectedItem, DriveInfo)
            If sourceFile IsNot Nothing AndAlso dest IsNot Nothing Then
                Dim written As Integer = Win32.CopyToDevice(sourceFile, dest)
                MessageBox.Show("Wrote: " & written & " bytes to " & drive, "Done", _
                                MessageBoxButtons.OK, MessageBoxIcon.Information)
                Application.Exit()
            Else
                RefreshBtn_Nuke()
            End If
        End If
    End Sub

End Class

 

Option Strict On
Option Explicit On

Imports Microsoft.Win32.SafeHandles
Imports System.ComponentModel
Imports System.IO
Imports System.Runtime.InteropServices

Public Class Win32

    Private Const GenericRead As UInteger = &H80000000UI
    Private Const GenericWrite As UInteger = &H40000000
    Private Const FileShareRead As UInteger = 1
    Private Const Filesharewrite As UInteger = 2
    Private Const OpenExisting As UInteger = 3
    Private Const IoctlVolumeGetVolumeDiskExtents As UInteger = &H560000
    Private Const IncorrectFunction As UInteger = 1
    Private Const ErrorInsufficientBuffer As UInteger = 122

    Private Class NativeMethods
        <DllImport("kernel32", CharSet:=CharSet.Unicode, SetLastError:=True)> _
        Public Shared Function CreateFile( _
            ByVal fileName As String, _
            ByVal desiredAccess As UInteger, _
            ByVal shareMode As UInteger, _
            ByVal securityAttributes As IntPtr, _
            ByVal creationDisposition As UInteger, _
            ByVal flagsAndAttributes As UInteger, _
            ByVal hTemplateFile As IntPtr) As SafeFileHandle
        End Function

        <DllImport("kernel32", SetLastError:=True)> _
        Public Shared Function DeviceIoControl( _
            ByVal hVol As SafeFileHandle, _
            ByVal controlCode As UInteger, _
            ByVal inBuffer As IntPtr, _
            ByVal inBufferSize As Integer, _
            ByRef outBuffer As DiskExtents, _
            ByVal outBufferSize As Integer, _
            ByRef bytesReturned As Integer, _
            ByVal overlapped As IntPtr) As <MarshalAs(UnmanagedType.Bool)> Boolean
        End Function

        <DllImport("kernel32", SetLastError:=True)> _
        Public Shared Function DeviceIoControl( _
            ByVal hVol As SafeFileHandle, _
            ByVal controlCode As UInteger, _
            ByVal inBuffer As IntPtr, _
            ByVal inBufferSize As Integer, _
            ByVal outBuffer As IntPtr, _
            ByVal outBufferSize As Integer, _
            ByRef bytesReturned As Integer, _
            ByVal overlapped As IntPtr) As <MarshalAs(UnmanagedType.Bool)> Boolean
        End Function

        <DllImport("kernel32", SetLastError:=True)> _
        Public Shared Function SetFilePointer( _
            ByVal hFile As SafeFileHandle, _
            ByVal lDistanceToMove As Integer, _
            ByRef lpDistanceToMoveHigh As Integer, _
            ByVal dwMoveMethod As Integer) As UInteger
        End Function

        ' you must write the whole inBuffer.
        <DllImport("kernel32", SetLastError:=True, CharSet:=CharSet.Ansi)> _
        Public Shared Function WriteFile( _
          ByVal hVol As SafeFileHandle, _
          <MarshalAs(UnmanagedType.LPArray, SizeParamIndex:=3)> _
          ByVal inBuffer() As Byte, _
          ByVal numberOfBytesToWrite As Integer, _
          ByRef numberOfBytesWritten As Integer, _
          ByVal overlapped As IntPtr) As <MarshalAs(UnmanagedType.Bool)> Boolean
        End Function

    End Class

    ' DISK_EXTENT in the msdn.
    <StructLayout(LayoutKind.Sequential)> _
    Private Structure DiskExtent
        Public DiskNumber As Integer
        Public StartingOffset As Long
        Public ExtentLength As Long
    End Structure

    ' DISK_EXTENTS
    <StructLayout(LayoutKind.Sequential)> _
    Private Structure DiskExtents
        Public numberOfExtents As Integer
        Public first As DiskExtent ' We can't marhsal an array if we don't know its size.
    End Structure

    Public Shared Function CopyToDevice(ByVal source As FileInfo, _
                                        ByVal destination As DriveInfo) As Integer
        Dim physicalDrives As List(Of String) = GetPhysicalDriveStrings(destination)
        If physicalDrives.Count > 1 Then
            Throw New Exception("The volume spans multiple disks!")
        End If
        If physicalDrives.Count = 0 Then
            Throw New Exception("Could not find the physical drive!")
        End If
        Dim physicalDrive As String = physicalDrives(0)
        Dim sourceFile As String = source.FullName
        Dim driveHandle As SafeFileHandle = Nothing
        Try
            driveHandle = NativeMethods.CreateFile( _
                physicalDrive, GenericRead Or GenericWrite, _
                FileShareRead Or Filesharewrite, _
                IntPtr.Zero, OpenExisting, 0, IntPtr.Zero)
            Dim bytes() As Byte = My.Computer.FileSystem.ReadAllBytes(source.FullName)
            Dim remainder As Integer = 512 - (bytes.Length Mod 512)
            If remainder < 512 Then
                ReDim Preserve bytes(bytes.Length + remainder - 1)
            End If
            Dim written As Integer = 0
            NativeMethods.SetFilePointer(driveHandle, 0, 0, 0)
            Dim result As Boolean = NativeMethods.WriteFile(driveHandle, _
                bytes, bytes.Length, written, IntPtr.Zero)
            If result = False Then Throw New Win32Exception
            Return written
        Finally
            If driveHandle IsNot Nothing Then
                If driveHandle.IsInvalid = False Then
                    If driveHandle.IsClosed = False Then
                        driveHandle.Close()
                    End If
                End If
                driveHandle.Dispose()
            End If
        End Try

    End Function

    ' A Volume could be on many physical drives.
    ' Returns a list of string containing each physical drive the volume uses.
    ' For CD Drives with no disc in it will return an empty list.
    Public Shared Function GetPhysicalDriveStrings(ByVal driveInfo As DriveInfo) As List(Of String)
        Dim sfh As SafeFileHandle = Nothing
        Dim physicalDrives As New List(Of String)(1)
        Dim path As String = "\\.\" & driveInfo.RootDirectory.ToString.TrimEnd("\"c)
        Try
            sfh = NativeMethods.CreateFile(path, GenericRead, FileShareRead Or Filesharewrite, _
                                           IntPtr.Zero, OpenExisting, 0, IntPtr.Zero)
            Dim bytesReturned As Integer
            Dim de1 As DiskExtents = Nothing
            Dim numDiskExtents As Integer = 0
            Dim result As Boolean = NativeMethods.DeviceIoControl(sfh, _
                IoctlVolumeGetVolumeDiskExtents, IntPtr.Zero, 0, de1, Marshal.SizeOf(de1), _
                bytesReturned, IntPtr.Zero)
            If result = True Then
                ' there was only one disk extent. So the volume lies on 1 physical drive.
                physicalDrives.Add("\\.\PhysicalDrive" & de1.first.DiskNumber.ToString)
                Return physicalDrives
            End If
            If Marshal.GetLastWin32Error = IncorrectFunction Then
                ' The drive is removable and removed, like a CDRom with nothing in it.
                Return physicalDrives
            End If
            If Marshal.GetLastWin32Error <> ErrorInsufficientBuffer Then
                Throw New Win32Exception
            End If
            ' The volume is on multiple disks.
            ' Untested...
            ' We need a blob of memory for the DISK_EXTENTS structure, and all the DISK_EXTENTS
            Dim blobSize As Integer = Marshal.SizeOf(GetType(DiskExtents)) + _
                                      (de1.numberOfExtents - 1) * _
                                      Marshal.SizeOf(GetType(DiskExtent))
            Dim pBlob As IntPtr = Marshal.AllocHGlobal(blobSize)
            result = NativeMethods.DeviceIoControl(sfh, IoctlVolumeGetVolumeDiskExtents, _
                IntPtr.Zero, 0, pBlob, blobSize, bytesReturned, IntPtr.Zero)
            If result = False Then Throw New Win32Exception
            ' Read them out one at a time.
            Dim pNext As New IntPtr(pBlob.ToInt32 + 4) ' is this always ok on 64 bit OSes? ToInt64?
            For i As Integer = 0 To de1.numberOfExtents - 1
                Dim diskExtentN As DiskExtent = DirectCast(Marshal.PtrToStructure(pNext, _
                    GetType(DiskExtent)), DiskExtent)
                physicalDrives.Add("\\.\PhysicalDrive" & diskExtentN.DiskNumber.ToString)
                pNext = New IntPtr(pNext.ToInt32 + Marshal.SizeOf(GetType(DiskExtent)))
            Next
            Return physicalDrives
        Finally
            If sfh IsNot Nothing Then
                If sfh.IsInvalid = False Then
                    sfh.Close()
                End If
                sfh.Dispose()
            End If
        End Try
    End Function


End Class

24 December 2008

VB.Net enumerate the system tray icons/buttons

Another msdn post. This one asked how to get the paths to the icons in the system tray. This sounds pretty simple, but isn't. You can start off by finding the window with Spy++, and then getting a handle to it. Then SendMessage with TB_BUTTONCOUNT to get the number of buttons. (TBBUTTON = TaskBarButton). Then you send TB_GETBUTTON repeatedly and get back TBBUTTON structures with information about the buttons. (See the last blog post for info on the x86/x64 mess)

typedef struct _TBBUTTON {    int        iBitmap;
    int        idCommand;
    BYTE     fsState;
    BYTE     fsStyle;
#ifdef _WIN64
    BYTE     bReserved[6]     // padding for alignment
#elif defined(_WIN32)
    BYTE     bReserved[2]     // padding for alignment
#endif
   DWORD_PTR   dwData;
    INT_PTR          iString;
} TBBUTTON, NEAR *PTBBUTTON *LPTBBUTTON;

The dwData field points to a NOTIFYICONDATA structure with some more gobbledegook:

typedef struct _NOTIFYICONDATA {
    DWORD cbSize;
    HWND hWnd;
    UINT uID;
    UINT uFlags;
    UINT uCallbackMessage;
    HICON hIcon;
    TCHAR szTip[64];
    DWORD dwState;
    DWORD dwStateMask;
    TCHAR szInfo[256];
    union {
        UINT uTimeout;
        UINT uVersion;
    };
    TCHAR szInfoTitle[64];
    DWORD dwInfoFlags;
    GUID guidItem;
    HICON hBalloonIcon;
} NOTIFYICONDATA, *PNOTIFYICONDATA;

We don't need all that, so we could trim it down. Earlier versions of windows used a smaller version anyway.

The difficulty lies in the fact that some of the pointers don't point anywhere useful. Say my process has a button at address 100. That address is local to my process, it has its own virtual memory space. If another process reads that pointer, then the value - 100 - will not mean anything as the other process has its own virtual memory space that maps to a different block of real memory. In our program, we SendMessage to the process hosting the tray buttons asking it to stick a TBBUTTON structure into some memory that is allocated in our home process. Unfortunately the pointer to the memory has no meaning to the other process, so the button goes astray.

To get around this, we ask the other process to create a blob of memory big enough for a tray button using VirtualAllocEx. Then we SendMessage, asking the other process to put the button into this blob of memory. Finally we read the contents of that memory with ReadProcessMemory:

Dim tbb As New TBBUTTON32
Dim friendlyTB As New TrayButton
friendlyTB.SetTrayIndex(index)
Dim pButton As IntPtr = IntPtr.Zero
Try
    ' Create memory in the explorer process to store a TBBUTTON strucutre:
    pButton = VirtualAllocEx(hProcess, Nothing, tbb.Size, MEM_COMMIT, PAGE_READWRITE)
    ' Use SendMessage to request that the memory is filled with the TBBUTTON @ index.
    Dim bResult As Boolean = SendMessage(hwndTrayToolbar, TB_GETBUTTON, index, pButton)
    If bResult = False Then Throw New Win32Exception
    ' And read the memory from the other process:
    Dim bytesReturned As Integer
    bResult = ReadProcessMemory(hProcess, pButton, tbb, tbb.Size, bytesReturned)
    If bResult = False Then Throw New Win32Exception

And here's an app that doesn't work too well as there are many complications to do with animated icons, OSes, x86/x64 that I haven't touched...

Structures that vary in size on x86/x64 - what to do?

Some structures can be difficult to marshal, as their size varies on x86 compared to x64:

typedef struct _TBBUTTON {
   int         iBitmap;
   int         idCommand;
   BYTE     fsState;
   BYTE     fsStyle;
#ifdef _WIN64
   BYTE     bReserved[6]     // padding for alignment
#elif defined(_WIN32)
   BYTE     bReserved[2]     // padding for alignment
#endif
   DWORD_PTR   dwData;
   INT_PTR          iString;
} TBBUTTON, NEAR *PTBBUTTON *LPTBBUTTON;
 
The layouts in x86 and x64 would look like this:
 
image 
 
In C the standard is for, say, an int to stay within a 4 byte boundary of the start of the structure. A short would
not cross a 2 byte boundary. Bytes can go where they like (well, they shouldn't cross the byte boundary, which I
guess they could by having bits spread over 2 bytes!) 
A pointer on x86 is a 4 byte value, and shouldn't cross a 4 byte boundary. A pointer on x64 is an 8 byte value, and
shouldn't cross an 8 byte boundary. Phew. Enough about byte boundaries...
TBBUTTON causes problems as it has the two BYTE fields in the middle. We need to pad, to get the dwData field
to start at a suitable location. Once it is in the correct place, iString will be fine.
With .Net we have a few ways to arrange structures. We can use StructLayout(LayoutKind.Sequential, Pack:=x).
Sequential means that .Net is not allowed to move the fields around at runtime, we want them to be arranged
in memory in the order that they appear in our declaration, this will match the C behaviour. Pack allows you to
specify the boundary to align the fields against. Often you would use sequential and pack = 1, then add fields of
padding to get it aligned.
Alternatively, you can use StructLayout(LayoutKind.Explicit) and hard-code where the fields should go. But,
this will only work if the structures have the same layout in x86 and x64. (Unfortunately you must use a
constant for the field offset, so you can't calculate it at runtime).
For TBBUTTON we are stuffed. There is no arrangement of pack and padding so that "one structure fits all", we will
need 2 declarations. And then we'll choose between them at runtime. (I think it might also be possible to just
compile it for x86 and run it under WOW64 on x64...)
<StructLayout(LayoutKind.Sequential, Pack:=1)> _
Public Structure TBBUTTON32
   Public bitmapIndex As Integer
   Public command As Integer
   Public state As TBStates
   Public style As TBStyles
   Public padding As UShort
   Public data As IntPtr
   Public iString As IntPtr
   Public Function Size() As Integer
       Return Marshal.SizeOf(Me)
   End Function
End Structure

<StructLayout(LayoutKind.Sequential, Pack:=1)> _
Public Structure TBBUTTON64
   Public bitmapIndex As Integer
   Public command As Integer
   Public state As TBStates
   Public style As TBStyles
   Public padding1 As Integer
   Public padding2 As UShort
   Public data As IntPtr
   Public iString As IntPtr
   Public Function Size() As Integer
       Return Marshal.SizeOf(Me)
   End Function
End Structure
The function is there for convenience. The 6 bytes of padding in the 64 bit version are spread across two variables.
We could equally use field offsets, but it takes a bit longer as you have to calculate them all. Unless you brain finds
that easy. My brain prefers arranging them in blocks and having padding bytes, and doesn't consider the numbers.

One thing that you think might work is the conditional compile thingy in VB.Net:
#if Platform="x86"
but this depends on the build target, it's not determined at runtime. So again, you would end up with two builds.

23 December 2008

So, ...

I must stop this. Looking back at my posts, and at forum posts it seems I start far too many new paragraphs with "So, ...". I wonder if it crept in to my TMAs?

Therefore, I will keep an eye out for it.

IOCTL_STORAGE_GET_MEDIA_SERIAL_NUMBER - VB.Net - might be incorrect

For anyone trying to get IOCTL_STORAGE_GET_MEDIA_SERIAL_NUMBER to work. The following would return an error about the buffer being too small, if the device supports the call, and if I haven't made a mistake. I get "The request is not supported", which seems to be what other folks are saying. It seems no USB Flash drives actually support this call, I've tried with a secure sandisk one.
Option Strict On
Option Explicit On
Option Infer Off

Imports Microsoft.Win32.SafeHandles
Imports System.Runtime.InteropServices
Imports System.ComponentModel

Public Class Form1

  Private Const IOCTL_STORAGE_GET_MEDIA_SERIAL_NUMBER As UInteger = &HED0000

  <Flags()> _
  Private Enum AccessRights As UInteger
      GenericRead = &H80000000UI
      GenericWrite = &H40000000
      GenericExecute = &H20000000
      GenericAll = &H10000000
  End Enum

  <Flags()> _
  Private Enum ShareModes As UInteger
      FileShareRead = 1
      FileShareWrite = 2
      FileShareDelete = 4
  End Enum

  Private Enum CreationDisposition As UInteger
      CreateNew = 1
      CreateAlways = 2
      OpenExisting = 3
      OpenAlways = 4
      TruncateExisting = 5
  End Enum

  <StructLayout(LayoutKind.Sequential)> _
  Private Structure MediaSerialNumberData
      Public SerialNumberLength As UInteger
      Public Result As UInteger
      Public Reserved As UInt64
      Public SerialNumberData As Byte ' We would get the 1st byte, if any UFDs actually worked.
      Public Function Size() As Integer
          Return Marshal.SizeOf(Me)
      End Function
  End Structure

  <DllImport("kernel32", CharSet:=CharSet.Unicode, SetLastError:=True)> _
  Private Shared Function CreateFile( _
  ByVal fileName As String, _
  ByVal desiredAccess As AccessRights, _
  ByVal shareMode As ShareModes, _
  ByVal securityAttributes As IntPtr, _
  ByVal creationDisposition As CreationDisposition, _
  ByVal flagsAndAttributes As Integer, _
  ByVal hTemplateFile As IntPtr) As SafeFileHandle
  End Function

  <DllImport("kernel32", SetLastError:=True)> _
  Private Shared Function DeviceIoControl( _
  ByVal hVol As SafeFileHandle, _
  ByVal controlCode As UInteger, _
  ByVal inBuffer As IntPtr, _
  ByVal inBufferSize As Integer, _
  ByRef outBuffer As MediaSerialNumberData, _
  ByVal outBufferSize As Integer, _
  ByRef bytesReturned As Integer, _
  ByVal overlapped As IntPtr) As <MarshalAs(UnmanagedType.Bool)> Boolean
  End Function

  Private Sub Form1_Load(ByVal sender As Object, ByVal e As EventArgs) Handles MyBase.Load
      Dim sfh As SafeFileHandle = Nothing
      Try
          sfh = CreateFile("//./H:", AccessRights.GenericRead, _
                           ShareModes.FileShareRead Or _
                           ShareModes.FileShareWrite, _
                           IntPtr.Zero, _
                           CreationDisposition.OpenExisting, _
                           0, IntPtr.Zero)
          Dim msnd As New MediaSerialNumberData
          Dim returned As Integer
          Dim result As Boolean = DeviceIoControl(sfh, IOCTL_STORAGE_GET_MEDIA_SERIAL_NUMBER, _
                                                  IntPtr.Zero, 0, msnd, msnd.Size, returned, IntPtr.Zero)
          If result = False Then
              Console.WriteLine((New Win32Exception).Message)
          End If
      Catch ex As Exception
          Console.WriteLine(ex.Message)
      Finally
          If sfh IsNot Nothing Then
              If sfh.IsInvalid = False Then
                  If sfh.IsClosed = False Then
                      sfh.Close()
                  End If
              End If
              sfh.Dispose()
          End If
      End Try

  End Sub

End Class

22 December 2008

M450 Project

Well, I've signed up for my final 3 open university courses for the B29 degree. M450 (project), M359 (database), M363 (mishmash - seems to be mainly about requirements gathering, planning and design). And I'm straight in at the deep end, as there are only a few days left to come up with an M450 project idea.

The M450 bumph says we should spend about 300 hours on the course. The project must have some challenges - challenges that require looking at some papers! The 300 hours includes the time to answer the TMAs and do the research. So - nothing too complicated, and nothing too simple. It's hard to get something "just right". It also seems to be hinting that it is useful to have a client, or imaginary-client, so that we can assess the stages of development.

I had kept notes of interesting ideas whilst doing the AI course. I was interested in the idea of using a neural network to do colour quantisation - reduce the amount of colour information in a picture. But, I've discovered that is old-hat and it is even part of the java library. Groan.

Then I thought about doing a genetic algorithm to control some arcade game - snake and lunar lander were ideas as they are self-contained and simple games. But they have both been done. I kinda hoped to do a solver for the Times Killer Sudoku (soduku itself has been done to death), but there are now several solvers for that out there too. That would have been interesting - to compare all the different search and rules techniques. Then I thought about maybe rotating photos round the correct way using a neural network to detect the correct orientation. I figured that it wouldn't work for any old photo, so I would narrow it down to portraits or pictures of houses. For portraits, hopefully it would get the general idea that the blob of face colour should be somewhere in the top middle. For houses I thought of the scenario of an estate agent wanting to automatically rotate the images as they come in from the camera. (Why don't cameras have a built in spirit level and tag the images with the orientation? Patent the idea quick!!). The nn would be able to detect the block of sky. But - nope, that's been done to death too. I'm thinking that maybe finding any application for AI is a bit tricky.

So, that leaves the other choice - M362. There we could have some threading thing or go for a distributed application. I wrote a program to track patients psa blood test results. The idea is that the clinic is clogged up with gentlemen who come in, see the doctor, who glances at the blood results (which are taken to ensure that cancer has not recurred, if it has then the levels will be high), (s)he sees they are normal, has a bit of a chat about the weather and waves goodbye to the patient. This is a waste of time, "clinics cost money", and there are - of course - waiting lists to consider. The consultant needs to see the blood test, but not the patient. So, enter the program, it tracks the blood results and creates documents for the GP - to tell them the patient has entered remote follow up, to request that blood be taken, and to recall patients to clinic to be seen by the consultant. I wrote the standalone program a few years ago, and it was nominated for the HSJ awards this year (black tie dinner on Park Lane, don't'cha'know). We didn't win, I think we were short-listed to encourage other small projects that have a big effect (the program has saved the NHS at least £100,000), most of the other projects were on a bigger scale.

So, my proposal would be to investigate the use of JavaEE for the program. The hospital is interested in getting the program on the network, so I think that it would be a good opportunity to evaluate JavaEE. As the program itself is quite large, I would write a simplified version for the OU project - I would be creating a prototype n-tier version of the program. I would create a database, simplified logic, and concentrate on the web and client tier. None of the original program would be used, as it is in the wrong language, and old. But is this enough? What's the challenge? Which papers etc would I reference and work through? <sigh> Is creating a database application enough of a "challenge". Getting a program right is hard work, and I'll have to learn lots of new stuff - but I'll be investigating well-trodden paths and following well written instructions -- so is that good enough?

I've found an old board game that has little written about it on the web. I could only find 1 AI implementation for it, and that's a mac program - alien to me. Maybe I'll just do that. It might be more interesting. Research would be needed - all the papers on making AIs for chess, checkers, etc would be helpful. Writing the game itself would be half the challenge. There would be plenty of stuff to do and to talk about.

Time is running out, and Xmas is eating in to all my spare time.

Read SD Card CID (serial number)

I just moved blogs, so here's an overview of the SD card CID stuff. I've had some success reading the CID from an SD Card:

Where:

1) The OS is XP or Vista 2) The device is not attached via any USB gizmo. It works only in a card reader attached directly to the PCI bus. (Can scsi pass through get around this? It's not an ATA or SCSI command...) 3) Admin privileges...

What not to do:

1) IOCTL_DISK_GET_STORAGEID 2) GetFileInformationByHandle

As both these return a number assigned when the volume is created. It will change next time the drive is formatted. What I did:

Use IOCTL_SFFDISK_DEVICE_COMMAND to send command 10 (see the SD spec).

Read Secure Digital (SD) Card Serial Number from CID

SD Card Musings

Read CID and CSD C# implementation

Pocket PC?

I don't think it works.

The Future?

IEEE 1667

Paste from Visual studio

0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789

That's using the paste from visual studio plug-in for microsoft's live writer. Tells me the width of this blog. I can then make a guide line appear in the IDE using a registry hack, see

Sarah Ford's weblog - "Guidelines – a hidden feature for the Visual Studio Editor"

(I remember why I didn't use blogger originally -- there's not much room, and I can't be bothered to mess with the templates).

Moving to blogger

I'm sick of Live spaces, as nothing turns up on google searches. Lets see how blogger compares. I'll probably use some of the live spaces facilities such as the file storage...

There will be a brief period of frenzied activity as I move some old posts here.

(Edit: Ok, moved and back dated, which is why this post makes little sense.)

16 December 2008

Determine the layout of a structure for .Net Platform Invoke

I was trying to create a signature for NOTIFYICONDATA, and decided to see what I could find out about it with C++.

1) start a new c++ CLR Console project in visual studio. Name it whatever you like. 2) Alter stdafx.h:

#pragma once
#define STRICT
#include <windows.h>   // common Win32 stuff
#include <commctrl.h>  // defines the structure we are interseted in
#include <stddef.h>    // we use offsetof
using namespace System;  // .Net types

3) in main, you can create a structure and determine its size. You can also find the offset for each field in the structure. Then you can swap the build to x64 and see what size they should be on x64 systems. This is definitely useful for when you want to create a managed C# or VB.Net version of the structure.

#include "stdafx.h"

using namespace System;

int main(array<System::String ^> ^args)
{
    NOTIFYICONDATA tbb;
    int i = sizeof(tbb);
    Console::WriteLine(i);   
    Console::WriteLine(offsetof(NOTIFYICONDATA,cbSize));
    Console::WriteLine(offsetof(NOTIFYICONDATA,hWnd));
    Console::WriteLine(offsetof(NOTIFYICONDATA,uID));
    Console::WriteLine(offsetof(NOTIFYICONDATA,uFlags));
    Console::WriteLine(offsetof(NOTIFYICONDATA,uCallbackMessage));
    Console::WriteLine(offsetof(NOTIFYICONDATA,hIcon));
    Console::WriteLine(offsetof(NOTIFYICONDATA,szTip));
    Console::WriteLine(offsetof(NOTIFYICONDATA,dwState));
    Console::WriteLine(offsetof(NOTIFYICONDATA,dwStateMask));
    Console::WriteLine(offsetof(NOTIFYICONDATA,szInfo));
    Console::WriteLine(offsetof(NOTIFYICONDATA,uTimeout));
    Console::WriteLine(offsetof(NOTIFYICONDATA,szInfoTitle));
    Console::WriteLine(offsetof(NOTIFYICONDATA,dwInfoFlags));
    Console::WriteLine(offsetof(NOTIFYICONDATA,guidItem));
    Console::WriteLine(offsetof(NOTIFYICONDATA,hBalloonIcon));
    Console::ReadKey();
    return 0;
}
Ugly, but it works.

13 December 2008

Open University - story so far

I can never find this:
Open university first class server address: oufcnt2.open.ac.uk This is handy:
My results so far...
(OES/OCAS)
T175 "Networked living: exploring information and communication technologies" - (89/94)
MST121 "Using Mathematics" - (94/92)
M150 "Data, computing and information" - (OCAS 95)
M253 "Team working in distributed environments" - (78/86)
M255 "Object-oriented programming with Java" - (91/96) Distinction M256 "Software development with Java" - (86/95) Distinction M257 "Putting Java to work" - (87/96) Distinction
M263 "Building blocks of software" - (82/97) Distinction M362 "Developing concurrent distributed systems" - (88/95) Distinction
M366 "Natural and Artificial Intelligence" - (93/96) Distinction

Starting early 2009: M359 "Relational databases: theory and practice" M363 "Software engineering with objects" M450 "The computing project"

10 December 2008

VB.Net Global keyboard hook to detect "print screen" keypress

As described...

Option Strict On
Option Explicit On

Imports System.Runtime.InteropServices

Public Class Form1

    Private Const WH_KEYBOARD_LL As Integer = 13
    Private Const WM_KEYUP As Integer = &H101
    Private Const WM_SYSKEYUP As Integer = &H105
    Private proc As LowLevelKeyboardProcDelegate = AddressOf HookCallback
    Private hookID As IntPtr

    Private Delegate Function LowLevelKeyboardProcDelegate(ByVal nCode As Integer, ByVal wParam As IntPtr, _
        ByVal lParam As IntPtr) As IntPtr

    <DllImport("user32")> _
    Private Shared Function SetWindowsHookEx(ByVal idHook As Integer, ByVal lpfn As LowLevelKeyboardProcDelegate, _
        ByVal hMod As IntPtr, ByVal dwThreadId As UInteger) As IntPtr
    End Function

    <DllImport("user32.dll")> _
    Private Shared Function UnhookWindowsHookEx(ByVal hhk As IntPtr) As <MarshalAs(UnmanagedType.Bool)> Boolean
    End Function

    <DllImport("user32.dll")> _
    Private Shared Function CallNextHookEx(ByVal hhk As IntPtr, ByVal nCode As Integer, ByVal wParam As IntPtr, _
        ByVal lParam As IntPtr) As IntPtr
    End Function

    <DllImport("kernel32.dll", CharSet:=CharSet.Unicode)> _
    Private Shared Function GetModuleHandle(ByVal lpModuleName As String) As IntPtr
    End Function

    Sub New()
        ' This call is required by the Windows Form Designer.
        InitializeComponent()
        ' Add any initialization after the InitializeComponent() call.
        hookID = SetHook(proc)
    End Sub

    Private Sub Form1_FormClosing(ByVal sender As Object, ByVal e As FormClosingEventArgs) Handles Me.FormClosing
        UnhookWindowsHookEx(hookID)
    End Sub

    Private Function SetHook(ByVal proc As LowLevelKeyboardProcDelegate) As IntPtr
        Using curProcess As Process = Process.GetCurrentProcess()
            Using curModule As ProcessModule = curProcess.MainModule
                Return SetWindowsHookEx(WH_KEYBOARD_LL, proc, GetModuleHandle(curModule.ModuleName), 0)
            End Using
        End Using
    End Function

    Private Function HookCallback(ByVal nCode As Integer, ByVal wParam As IntPtr, ByVal lParam As IntPtr) As IntPtr
        ' we check keyup for standard printscreen, and syskeyup incase it is alt+printscreen
        ' we aren't checking keydown, as that fires before the screenshot is taken.
        If nCode >= 0 AndAlso (wParam.ToInt32 = WM_KEYUP OrElse wParam.ToInt32 = WM_SYSKEYUP) Then
            Dim vkCode As Integer = Marshal.ReadInt32(lParam)
            If vkCode = Keys.PrintScreen Then
                Dim data As IDataObject = Clipboard.GetDataObject()
                If data.GetDataPresent(GetType(Bitmap)) Then
                    Me.BackgroundImage = DirectCast(data.GetData(GetType(Bitmap)), Bitmap)
                End If
            End If
        End If
        Return CallNextHookEx(hookID, nCode, wParam, lParam)
    End Function

End Class

SetClipboardViewer API VB.NET

This registers a form as a clipboard viewer. It then recieves notification when something happens to the clipboard. Some post on the msdn forum. It mentioned: http://www.radsoftware.com.au/articles/ClipboardMonitor_VB.txt, which I've tidied up to my liking (a stray long, a strange cast removed, you can't override Dispose (could you before?))
Option Strict On
Option Explicit On

Imports System.Runtime.InteropServices

Public Class Form1

   Private Const WM_DRAWCLIPBOARD As Integer = &H308
   Private Const WM_CHANGECBCHAIN As Integer = &H30D

   Private mNextClipBoardViewerHWnd As IntPtr
   Private Event OnClipboardChanged()

   <DllImport("user32")> _
   Private Shared Function SetClipboardViewer(ByVal hWnd As IntPtr) As IntPtr
   End Function

   <DllImport("user32")> _
   Private Shared Function ChangeClipboardChain(ByVal hWnd As IntPtr, ByVal hWndNext As IntPtr) As _
       <MarshalAs(UnmanagedType.Bool)> Boolean
   End Function

   <DllImport("user32")> _
   Private Shared Function SendMessage(ByVal hWnd As IntPtr, ByVal msg As Integer, ByVal wParam As IntPtr, _
       ByVal lParam As IntPtr) As IntPtr
   End Function

   Sub New()
       InitializeComponent()
       mNextClipBoardViewerHWnd = SetClipboardViewer(Me.Handle)
       AddHandler Me.OnClipboardChanged, AddressOf ClipBoardChanged
   End Sub

   Protected Overrides Sub WndProc(ByRef m As Message)
       Select Case m.Msg
           Case WM_DRAWCLIPBOARD
               RaiseEvent OnClipboardChanged()
               SendMessage(mNextClipBoardViewerHWnd, m.Msg, m.WParam, m.LParam)

           Case WM_CHANGECBCHAIN
               If m.WParam.Equals(mNextClipBoardViewerHWnd) Then
                   mNextClipBoardViewerHWnd = m.LParam
               Else
                   SendMessage(mNextClipBoardViewerHWnd, m.Msg, m.WParam, m.LParam)
               End If
       End Select
       MyBase.WndProc(m)
   End Sub

   Private Sub ClipBoardChanged()
       Dim data As IDataObject = Clipboard.GetDataObject()
       If data.GetDataPresent(GetType(Bitmap)) Then
           Me.BackgroundImage = DirectCast(data.GetData(GetType(Bitmap)), Bitmap)
       End If
   End Sub

   Private Sub Form1_FormClosing(ByVal sender As Object, ByVal e As FormClosingEventArgs) Handles Me.FormClosing
       ChangeClipboardChain(Me.Handle, mNextClipBoardViewerHWnd)
   End Sub

End Class

To use: start a few instances of the program. Copy an image into the clipboard - the backgrounds of the forms should change. Then close one of the forms and change the image in the clipboard - all the backgrounds should change.