Tuesday, December 21, 2004

If you're a CF developer READ THIS ARTICLE.  Once done, read it again. 

12/21/2004 8:48:03 PM (Eastern Standard Time, UTC-05:00)  #    Comments [1]  | 

So I finally ordered Halo II.  I put it off becasue I've had an unbelievably busy month and I knew that if I ordered it, productivity in getting the new house unpacked and all the other work I need to do would suffer, But I finally opened it up Friday night.  I finished the game last night - not bad considering I put in about 10 hours of coding on Sunday and didn't play at all.

Best part of the game?  Laura Prepon does the voiceover for a Marine and when she called one of the Covenant a jackass after shooting him I just about fell out of my chair.  I had no idea she had done a voice in the game, but it was unmistakably her.

12/21/2004 12:41:08 PM (Eastern Standard Time, UTC-05:00)  #    Comments [2]  | 

Following Mike and Neil, I took the “What OS Am I” online quiz and found that I'm Slackware Linux.

Interesting.  I used to be a diehard Slackware user in '93-'94, in fact in my recent move I found the old distribution CDs.

12/21/2004 12:34:12 PM (Eastern Standard Time, UTC-05:00)  #    Comments [0]  | 
 Wednesday, November 10, 2004

If you follow by blog, you've probably noticed one of my side projects is moving toward a device driver with managed code.  Why?  Because most any sane person will tell you it's either not possible or a bad idea.  But hey, I always want to know the why behind those statements.  “It will be too slow.”  Well, how slow, exactly?  “It won't be deterministic.”  What if I don't need it to be, or what if I can get around that axiom?

Well as a new motivator I had someone ask about using the SPI bus on an Applied Data Systems device using C#.  It's a slow bus, and determinism isn't really necessary, so this would be a classic opportunity.  Add to that the fact they don't need to to be a tru driver running under the auspices of device.exe - simply controlling the bus from their app is fine.

So last night I sat down and hammered out a general purpose physical address accessor class, which I'll post below and add to the SDF.  Then today I did a quick implementation test with it just to see how it does.  I didn't do the SPI driver because 1. it would take a while and 2. they need to understand how it works and it will do them some good to write it.  What I did do is use the class to control a GPIO on the PXA255 which is connected to an LED on my board.  This provided a quick visual check that I was indeed able to set the GPIO register values.  It also allowed me to use a scope to see how fast it would go.

Well, as a test I put it in a tight loop toggling the LED state as fast as possible, just a while(true) {on, off } kind of thing, and I did the same logic in a pure C app.  The C app had pulse durations ~500ns, plus or minus a pretty large margin because I didn't fiddle with thread priorities or anything.  The C# app had pulse durations ~1us.  So you can look at it glass-half-empty and say wow, it's twice as slow as native code, or glass-half-full and say, wow, that's well below millisecond resolution and quite suitable for a lot of stuff.

I love managed code.

//==========================================================================================
//
// OpenNETCF.IO.PhysicalAddressPointer
// Copyright (c) 2004, OpenNETCF.org
//
// This library is free software; you can redistribute it and/or modify it under
// the terms of the OpenNETCF.org Shared Source License.
//
// This library is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
// FITNESS FOR A PARTICULAR PURPOSE. See the OpenNETCF.org Shared Source License
// for more details.
//
// You should have received a copy of the OpenNETCF.org Shared Source License
// along with this library; if not, email
licensing@opennetcf.org to request a copy.
//
// If you wish to contact the OpenNETCF Advisory Board to discuss licensing, please
// email
licensing@opennetcf.org.
//
// For general enquiries, email
enquiries@opennetcf.org or visit our website at:
//
http://www.opennetcf.org
//
//==========================================================================================
using System;
using System.Runtime.InteropServices;

namespace OpenNETCF.IO
{
 /// <summary>
 /// This class is used to access memory mapped addresses
 /// !!! DANGER WILL ROBINSON !!  You can cause serious problems using this class without knowing what you're doing!
 /// We reiterate the statement in our license that OpenNETCF provides absolutely no warranty on this code and you use it at your own risk
 /// </summary>
 public class PhysicalAddressPointer
 {
  // use 4k pages
  private const uint PAGE_SIZE  = 0x1000;

  // consts from winnt.h
  private const uint MEM_RESERVE  = 0x2000;
  private const uint PAGE_NOACCESS = 0x0001;
  private const uint PAGE_READWRITE = 0x0004;
  private const uint PAGE_NOCACHE  = 0x200;
  private const uint PAGE_PHYSICAL = 0x400;
  private const uint MEM_RELEASE  = 0x8000;

  private IntPtr m_virtualAddress = IntPtr.Zero;
  private IntPtr m_addressPointer = IntPtr.Zero;

  /// <summary>
  /// An accessor class to a physical memory address.
  /// </summary>
  /// <param name="physicalAddress">Physical Address to map</param>
  /// <param name="size">Minimum size of the desired allocation</param>
  /// <remarks>The physical address does not need to be aligned as the PhysicalAddressPointer will handle alignment
  /// The size value will aligned to the next multiple of 4k internally, so the actual allocation may be larger than the requested value</remarks>
  public PhysicalAddressPointer(uint physicalAddress, uint size)
  {
   m_addressPointer = MapPhysicalAddress(physicalAddress, size);
  }

  ~PhysicalAddressPointer()
  {
   if(m_virtualAddress != IntPtr.Zero)
   {
    VirtualFree(m_virtualAddress, 0, MEM_RELEASE);
   }
  }

  /// <summary>
  /// Write an array of bytes to the mapped physical address
  /// </summary>
  /// <param name="bytes">data to write</param>
  public void WriteBytes(byte[] bytes)
  {
   Marshal.Copy(bytes, 0, m_addressPointer, bytes.Length);
  }

  /// <summary>
  /// Write a 32-bit value to the mapped address
  /// </summary>
  /// <param name="data">data to write</param>
  public void WriteInt32(int data)
  {
   Marshal.WriteInt32(m_addressPointer, data);
  }

  /// <summary>
  /// Write a 16-bit value to the mapped address
  /// </summary>
  /// <param name="data">data to write</param>
  public void WriteInt16(short data)
  {
   Marshal.WriteInt16(m_addressPointer, data);
  }

  /// <summary>
  /// Write an 8-bit value to the mapped address
  /// </summary>
  /// <param name="data">data to write</param>
  public void WriteByte(byte data)
  {
   Marshal.WriteByte(m_addressPointer, data);
  }

  /// <summary>
  /// Read a series of bytes from the mapped address
  /// </summary>
  /// <param name="length">number of bytes to read</param>
  /// <returns>read data</returns>
  public byte[] ReadBytes(int length)
  {
   byte[] bytes = new byte[length];
   Marshal.Copy(m_addressPointer, bytes, 0, length);
   return bytes;
  }

  /// <summary>
  /// Read a 32-bit value from the mapped address
  /// </summary>
  /// <returns>read value</returns>
  public int ReadInt32()
  {
   return Marshal.ReadInt32(m_addressPointer);
  }

  /// <summary>
  /// Read a 16-bit value from the mapped address
  /// </summary>
  /// <returns>read value</returns>
  public short ReadInt16()
  {
   return Marshal.ReadInt16(m_addressPointer);
  }

  /// <summary>
  /// Read an 8-bit value from the mapped address
  /// </summary>
  /// <returns>read value</returns>
  public byte ReadByte()
  {
   return Marshal.ReadByte(m_addressPointer);
  }

  IntPtr MapPhysicalAddress(uint physicalAddress, uint size)
  {
   uint alignedAddress = 0;
   uint offset   = 0;
   uint alignedSize  = 0;
   IntPtr returnAddress = IntPtr.Zero;
   
   
   // get a page aligned address
   alignedAddress = PageAlignAddress(physicalAddress);
   offset = physicalAddress - alignedAddress;

   // get a page aligned size
   alignedSize = RoundSizeToNextPage(size + offset);

   // reserve some virtual memory
   m_virtualAddress = VirtualAlloc(0, alignedSize, MEM_RESERVE, PAGE_NOACCESS);

   // sanity check
   if(m_virtualAddress == IntPtr.Zero)
   {
    // allocation failure!
    return IntPtr.Zero;
   }

   
   // Map physical memory to virtual memory
   if(VirtualCopy (m_virtualAddress, (IntPtr)(alignedAddress >> 8), alignedSize,
    PAGE_READWRITE | PAGE_NOCACHE | PAGE_PHYSICAL) == 0)
   {
    // copy failure!
    VirtualFree(m_virtualAddress, 0, MEM_RELEASE);

    return IntPtr.Zero;
   }

   // offset and return
   return new IntPtr(m_virtualAddress.ToInt32() + offset);
  }

  // simply aligns an address to a page boundary to prevent data aborts and fun stuff like that
  uint PageAlignAddress(uint addressToAlign)
  {
   return addressToAlign & ~(PAGE_SIZE -1);
  }

  // allocations must be made in page multiples. 
  // this method finds the next multiple given a desired size
  uint RoundSizeToNextPage(uint size)
  {
   return (size + PAGE_SIZE - 1) & ~(PAGE_SIZE - 1);
  }

  // p/invoke declarations
  [DllImport("coredll.dll", EntryPoint="VirtualAlloc", SetLastError=true)]
  private static extern IntPtr VirtualAlloc(uint lpAddress, uint dwSize, uint flAllocationType, uint flProtect);

  [DllImport("coredll.dll", EntryPoint="VirtualCopy", SetLastError=true)]
  private static extern int VirtualCopy(IntPtr lpvDest, IntPtr lpvSrc, uint cbSize, uint fdwProtect);

  [DllImport("coredll.dll", EntryPoint="VirtualFree", SetLastError=true)]
  private static extern int VirtualFree(IntPtr lpAddress, uint dwSize, uint dwFreeType);
 }
}

 

11/10/2004 3:28:34 PM (Eastern Standard Time, UTC-05:00)  #    Comments [5]  | 
 Monday, October 18, 2004

Yesterday I fought with getting the ListView to have CheckBoxes that didn't “automagically” toggle whenever you tapped an item, but only when the checkbox itself was clicked.  Well, not to be outdone, I've got it working (rather well actually).  It was a 3 hour pain in my ass, facilitated by my earlier work with the IMessageFilter and ApplicationEx classes, and it shouldn't have required this much effort, but hey, that's what makes our jobs interesting.

So here's the scoop.  First you need an IMessageFilter implementation.  This can be expanded to the other mouse events, I've only added the MouseDown cause that's all I care about right now.  Feel free to add your own (submit it back if you do, please):

using System;
using System.Windows.Forms;
using System.Collections;

namespace OpenNETCF.Windows.Forms
{
 public delegate void MouseEvent(MouseEventArgs e);

 /// <summary>
 /// Summary description for MouseEventFilter.
 /// </summary>
 public class MouseEventFilter : OpenNETCF.Windows.Forms.IMessageFilter
 {
  private const int WM_MOUSEFIRST                   = 0x0200;
  private const int WM_MOUSEMOVE                    = 0x0200;
  private const int WM_LBUTTONDOWN                  = 0x0201;
  private const int WM_LBUTTONUP                    = 0x0202;
  private const int WM_LBUTTONDBLCLK                = 0x0203;
  private const int WM_RBUTTONDOWN                  = 0x0204;
  private const int WM_RBUTTONUP                    = 0x0205;
  private const int WM_RBUTTONDBLCLK                = 0x0206;
  private const int WM_MBUTTONDOWN                  = 0x0207;
  private const int WM_MBUTTONUP                    = 0x0208;
  private const int WM_MBUTTONDBLCLK                = 0x0209;
  private const int WM_MOUSELAST                    = 0x0209;
  private const int MK_LBUTTON        = 0x0001;
  private const int MK_RBUTTON        = 0x0002;
  private const int MK_SHIFT         = 0x0004;
  private const int MK_CONTROL        = 0x0008;
  private const int MK_MBUTTON        = 0x0010;

  public event MouseEvent MouseDown;
  public event MouseEvent MouseUp;
  public event MouseEvent MouseMove;

  private MouseButtons btns = 0;
  private short x = 0;
  private short y = 0;
  private byte[] pos;

  private bool m_enabled = true;
  private ArrayList m_hwnds;

  public MouseEventFilter()
  {
   m_hwnds = new ArrayList(1);
  }

  public bool Enabled
  {
   get { return m_enabled; }
   set { m_enabled = value; }
  }

  public ArrayList FilterHandles
  {
   get { return m_hwnds; }
   set { m_hwnds = value; }
  }

  #region IMessageFilter Members

  public bool PreFilterMessage(ref Microsoft.WindowsCE.Forms.Message m)
  {
   if(m_enabled)
   {
    // filter if the filter list is empty (filter all hWnds) or if it's marked for filtering
    if((m_hwnds.Count == 0) || (m_hwnds.Contains(m.HWnd)))
    {
     switch(m.Msg)
     {
      case WM_LBUTTONDOWN:
       if(MouseDown != null)
       {
        btns = MouseButtons.None;

        btns |= ((m.WParam.ToInt32() & MK_LBUTTON) > 0) ? MouseButtons.Left : 0;
        btns |= ((m.WParam.ToInt32() & MK_RBUTTON) > 0) ? MouseButtons.Right : 0;
  
        pos = BitConverter.GetBytes(m.LParam.ToInt32());

        x = BitConverter.ToInt16(pos, 0); // LOWORD
        y = BitConverter.ToInt16(pos, 2); // HIWORD

        foreach(MouseEvent me in MouseDown.GetInvocationList())
        {

         me(new MouseEventArgs(btns, 0, x, y, 0));
        }
       }
       break;
     }
    }
   }
   return false;
  }

  #endregion
 }
}

Next you use ApplicationEx to add it to your app, something like this:

static void Main()
{
 eventFilter = new MouseEventFilter();
 myForm = new MyForm();
 ApplicationEx.AddMessageFilter(eventFilter);
 ApplicationEx.Run(myForm);
}

Then in your Form with the ListView, you add your hWnd to the Filter (for better perf):

lvwMyList.Focus();
hwndList = GetFocus();  // use P/Invoke
eventFilter.FilterHandles.Add(hwndList);

And then add a mousedown handler:

eventFilter.MouseDown += new MouseEvent(eventFilter_MouseDown);

Add implementation:

private void eventFilter_MouseDown(MouseEventArgs e)
{
 // determine which item index was clicked
 int topindex = SendMessageInt(hwndList, LVM_GETTOPINDEX, 0, 0);
 int index = topindex + (e.Y / itemheight);

 if(e.X <= 17)
 {
  // on a check
  lstReports.Items[index].ImageIndex = (lstReports.Items[index].ImageIndex == AppGlobal.ICON_CHECKED) ? AppGlobal.ICON_UNCHECKED : AppGlobal.ICON_CHECKED;
 }
 else
 {
  // navigate
  MessageBox.Show("Navigate");
 }
}

And voila - fucking magic.  IMHO, this is a good example of why you pay consultants consultant rates.  How long would it have taken someone with less experience to come up with that?  Today it's a freebie.

10/18/2004 11:44:10 PM (Eastern Daylight Time, UTC-04:00)  #    Comments [3]  | 

First, let me point out that I'm not a UI person.  I don't like doing them becasue they don't interest me, and I usually find it to be mostly grunt work, nothing interesting.  Because I don't do UI, I'm only vaguely aware of UI component shortcomings in the CF.  Sure I know several from newsgroup reports, but I've not been really bitten by one - until this weekend that is.

I'm working on a project that has a seeminly simple UI requirement - a two column list of items.  In column 1 is a checkbox, and column 2 is text, preferrably a LinkLabel.  The user can “check” multiple items to remove them from the list with the click of another button, or a click on the text should send them to the detail view of that item.  Simple use of a ListView in detail mode with Checkboxes enabled, right?  I can already see the seasoned CF UI developer smiling....

Take 1: Drop on a ListView, turn on CheckBoxes and FullRowSelect , and run
Interesting.  Every time I tap on a row, the checkbox state toggles.  Who's bright idea was that?  I recall the behavior from the newsgroups, so I Google for a workaround.  Nothing proposed to actually fix it, just ideas for different presentation.  I'm not giving up that easily, I want it to work the way it should.

Take 2: Screw around with P/Invoking ListView Messages
My first thought is that maybe I can find if the user clicked (keep it to yourself, the problem with this idea isn't discovered until Take 4) on the checkbox itself, so I need to determine where its bounds are.  Tried LVM_GETCOLUMNWIDTH and LVM_GETITEMRECT.  Seems that the checkbox isn't a column itself, and there are no APIs I can come up with to get it.  Ugh - I've now burned an hour on something that should be simple, and of course now I can't just let it be.

Take 3: Use my own icon
While playing with the APIs I saw that LVM_GETITEMRECT can get the position of the icon, so in theory I could get the tap position, then use that to find out if it's on the icon, and swap the icon.  I created my icons, added them to a ImageList, removed the Checkboxes and loaded the list.  All is looking good.  I also noticed that LVM_HITTEST can tell me if the icon was tapped without a check for bounds, so I'll implement that once I get thetap location.

Take 4: Added MouseDown and MouseUp handlers
Of course Studio says they're available, but I get nothing.  Maybe it's becasue I'm using the RTM CF.  SP2 fixed a lot, so I'll add that.  While I'm att it I'll add handling in the app to ensure that SP2 is applied in order to run the app.

Take 5: Added the SP2 handler first, then found out that SP2 still didn't add the events.  Crap.  Who made the decisions on what would be supported with this control and did he or she have and clue about how this f'ing control was used in the real world?

So at midnight on Sunday I packed it in.  Next step: unless someone recommends or provides a much simpler solution, I'm going to add an IMessageFilter implementation (fortunately I added that for OpenNETCF or I'd really be screwed), use that to get my mouse location, then use that location with the LVM_HITTEST to see if the icon was tapped, and if it was toggle its state, otherwise navigate.  With all this work I should have just done an implementation of the OwnerDrawnList.  To think I didn't do that because I wanted something simple and easy to implement.

10/18/2004 10:03:22 AM (Eastern Daylight Time, UTC-04:00)  #    Comments [0]  |