conceptual inertia

mediocrity at its finest

Archive for the ‘code’ Category

hosting flash

without comments

I was working on a project a while back that required a fairly rich UI. I was brought a ”sketch” of the UI in Flash. Without even thinking, I started up Visual Studio and began laying out a few controls to port the sketch. Hmm…should I skin the controls or derive new ones? Should I draw everything manually with the Graphics object? Should I load some vector art from a metafile? How was I going to perform those animations and transitions? Ugh…it was going to take a long time to implement the UI before I could even get around to real processing functionality. Then it hit me, why not use Flash for the UI? The client had most of the work already done, he simply needed a Windows application to do some behind the scenes processing that would be hard given the constraints of Flash sandboxing.

Today’s post covers how to host Flash in a C# application with two-way communcation between environments. Here’s a screenshot of the demo:

Clicking the green button in Flash pops up a message box in C#. Entering text in C# and clicking the Send button echos it in Flash.
The first step is to add the Shockwave Flash ActiveX control to the Toolbox (it doesn’t matter which group you add the control to):

After selecting the component from the COM tab, you should see a new icon in your Toolbox:

Now, drag ‘n drop this control onto Form1 and you should get the following:

Something broke with the release of VS2005. You should have no problem doing the above in VS2003. However, I don’t have VS2003 so what’s the solution? As it turns out, the answer is in the warning messages. There was a failure to create a wrapper assembly due to a COM exception. Well, I know of another .NET language that should have no problem interacting with COM…

Now create a new FlashPanel user control:

The FlashPanel is a derivative of UserControl. In the designer, repeat the above steps – drag ‘n drop the Flash object onto the panel and set its Dock property to ”Fill”. The C++/CLI control library will successfully create a wrapper assembly for the ActiveX control. Add the following property so C# can access the Flash object:


public: property AxShockwaveFlashObjects::AxShockwaveFlash^ Flash
{
    AxShockwaveFlashObjects::AxShockwaveFlash^ get()
    {
        return this->axShockwaveFlash1;
    }
}
 

Once you build the control library, you should see your new UserControl in the Toolbox:

Now create a C# Windows application. Under the References folder, add the following:

Next, we begin using our FlashPanel. The ActiveX Flash control has a LoadMovie function which takes an absolute directory path to the .swf file. It also has an FsCommand delegate:


public Form1()
{
    InitializeComponent();
    string moviePath = Application.StartupPath + "test.swf";
    this.flashPanel1.Flash.LoadMovie(0, moviePath);
    this.flashPanel1.Flash.FSCommand += new
        AxShockwaveFlashObjects._IShockwaveFlashEvents_FSCommandEventHandler(
                                                            OnFsCommand);
}
 

FsCommand is used by Flash to communicate with the host environment. In the test.swf file, the green arrow button has the following ActionScript attached to it:


on(release)
{
    fscommand("ButtonClick", "You clicked a button in Flash");
}
 

OnFsCommand will respond to messages from Flash. In this example, it only checks for the “ButtonClick” message:


void OnFsCommand(object sender,
                 AxShockwaveFlashObjects._IShockwaveFlashEvents_FSCommandEvent e)
{
    if( e.command == "ButtonClick")
        MessageBox.Show(e.args);
}
 

When sending a message to Flash, we use SetVariable passing both the message name and value:


private void OnSendButtonClick(object sender, EventArgs e)
{
    this.flashPanel1.Flash.SetVariable("DotNetMessage", this.textBox1.Text);
}
 

If you wanted to send more information in the value param you could use a delimiter. For example, “foo?bar” would need to be parsed and split based on ”?” characters to extract multiple values.
The last bit of ActionScript code is used to setup event listening. The root watches for a “DotNetMessage” and then passes it along to the listener object. The code in this example, simple echos any value to the textbox control inside Flash.


var listenerObj:Object = new Object();
listenerObj.onMessage = function (prop, oldVal, newVal)
{
    _root.textBox = newVal;
}
_root.watch("DotNetMessage", listenerObj.onMessage);
 

The .fla file is available in the Debug directory. You can download the source code here.

Written by admin

June 2nd, 2006 at 9:46 pm

Posted in code

custom serialization

without comments

For a derivative class to be serializable, the base class must be marked as serializable as well. For example, if I do the following:


[Serializable]
public partial class Form1 : Form
 

And attempt to serialize the object, I will get an exception.

Adding a serializable attribute to the base Form is not possible and many people at this point move on looking for another solution. However, it is possible to make Form1 serializable through custom serialization. To do this, first add ISerializable to the inheritance chain:


[Serializable]
public partial class Form1 : Form, ISerializable
 

And then implement it:


void ISerializable.GetObjectData(SerializationInfo info, StreamingContext context)
{
    info.AddValue("Location", this.Location);
    info.AddValue("ClientSize", this.ClientSize);
}
 

An exception is no longer produced when running the code now. After execution, you should find a ”Form1.bin” file in the Debug directory. Despite this, a binary file is not very user-friendly. Let’s modify the code so we can actually open the file and read it. Rather than using a BinaryFormatter, why not a SoapFormatter? To use soap formatting you’ll need to add a reference to the assembly:


using System.Runtime.Serialization.Formatters.Soap;
//...

FileStream fileStream = File.Create("Form1.xml");
IFormatter iFormatter = new SoapFormatter();
iFormatter.Serialize(fileStream, form);
 

In the above code, I’ve changed the file name from Form1.bin to Form1.xml. However, the file extension doesn’t impact the content. Here’s a sample of the output:

So, serialization works but what about deserialization? To accomplish that, you need to implement a deserialization constructor:


private Form1(SerializationInfo info, StreamingContext context)
{
    DefaultInitialization();
    this.Location = (Point)info.GetValue("Location", this.Location.GetType());
    this.ClientSize = (Size)info.GetValue("ClientSize", this.ClientSize.GetType());
}
 

The DefaultInitialization function calls InitializeComponent and then assigns some default values. Calling InitializeComponent is important if you want the Form and any of its controls to be initialized appropriately.


private void DefaultInitialization()
{
    InitializeComponent();
    this.StartPosition = FormStartPosition.Manual;
    this.Location = new Point(0, 0);
    this.ClientSize = new Size(640, 480);
}
 

The DefaultInitialization function is also called from the default constructor:


public Form1()
{
    DefaultInitialization();
}
 

Deserialization can’t occur after Application.Run returns because the Form has already been destroyed. Here, we plug-in to the FormClosing event to handle deserialization:


private void OnFormClosing(object sender, FormClosingEventArgs e)
{
    FileStream fileStream = new FileStream("Form1.xml",
                                           FileMode.Create,
                                           FileAccess.Write);

    IFormatter iFormatter = new SoapFormatter();
    iFormatter.Serialize(fileStream, this);

    fileStream.Close();
}
 

Finally, we can bring it all together in Main:


[STAThread]
static void Main()
{
    Form1 form = null;

    if (File.Exists("Form1.xml"))
    {
        FileStream fileStream = File.Open("Form1.xml", FileMode.Open);
        IFormatter iFormatter = new SoapFormatter();
        form = iFormatter.Deserialize(fileStream) as Form1;

        fileStream.Close();
    }

    if (form == null)
        form = new Form1();

    Application.Run(form);
}
 

With this simple example, you can position and resize the Form; restart the application and it will remember your previous settings. Now I’m not saying this is a way to save Form settings. There are numerous methods including:

  • app.config
  • .resources
  • .ini
  • manual parsing of various file formats

The real demonstration here is how to make an object serializable when the base class isn’t.

Written by admin

June 2nd, 2006 at 12:52 am

Posted in code

misc directx

without comments

ProgressiveMeshDemo.zip – A simple demo of the D3D progressive mesh interface using C++/CLI.

PickingDemo.zip – A simple ray picking demo using DirectX and C++/CLI .Drag and click the arrow to slide it back and forth.

OrthoPickingDemo.zip – A simple demo of picking in an orthographic viewport using DirectX and C++/CLI:

LerpDemo.zip – C++ DirectX demo of linear interpolation between keyframes using an assembly shader.

Written by admin

May 22nd, 2006 at 10:14 pm

Posted in code

wireframe

without comments

What’s the “minimum” amount of C# code you need to create a spinning wireframe quad in 3D?


using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using System.Diagnostics;

namespace Wireframe1
{
    public partial class Form1 : Form
    {
        List<Vertex> vertices = new List<Vertex>();

        Point prev  = new Point();
        Point curr  = new Point();
        Point first = new Point();

        float angle = 0.0f;

        public Form1()
        {
            InitializeComponent();

            vertices.Add(new Vertex(-1.0f, -1.0f, 0.0f));
            vertices.Add(new Vertex(-1.0f,  1.0f, 0.0f));
            vertices.Add(new Vertex( 1.0f,  1.0f, 0.0f));
            vertices.Add(new Vertex( 1.0f, -1.0f, 0.0f));
        }

        private void OnTick(object sender, EventArgs e)
        {
            this.Invalidate();
        }

        private void OnPaint(object sender, PaintEventArgs e)
        {
            // rotate quad a little
            int rotationTime = 8000;
            int time = System.Environment.TickCount % rotationTime;
            angle = time * (2.0f * (float)Math.PI) / rotationTime;

            for (int i = 0; i < 4; ++i)
            {
                // we don't want to alter the original vert
                Vertex v = new Vertex(vertices[i].x,
                                      vertices[i].y,
                                      vertices[i].z);

                float oldX = vertices[i].x;
                float oldY = vertices[i].y;
                float oldZ = vertices[i].z;

                // y-axis rotation
                v.x = oldX * (float) Math.Cos(angle) +
                      oldZ * (float)Math.Sin(angle);
                v.z = oldX * (float)-Math.Sin(angle) +
                      oldZ * (float)Math.Cos(angle);

                // view transform - translate inverse cam
                v.z -= -2.4f; // move camera back slightly

                // - simple perspective divide (not the best looking)
                // - note: building a perspective matrix with an
                //         arbitrary field of view is more involved
                v.x /= v.z;
                v.y /= v.z;

                // screen transform
                v.x =  v.x * 640 / 2 + 640 / 2;
                v.y = -v.y * 480 / 2 + 480 / 2;

                curr.X = (int)v.x;
                curr.Y = (int)v.y;

                if (i == 0)
                {
                    first = curr;
                    prev = curr;
                    continue;
                }

                e.Graphics.DrawLine(Pens.Black, prev, curr);
                prev = curr;

            } // end for

            e.Graphics.DrawLine(Pens.Black, curr, first);

        } // end OnPaint

    } // end Form

    public struct Vertex
    {
        public float x;
        public float y;
        public float z;

        public Vertex(float x, float y, float z)
        {
            this.x = x;
            this.y = y;
            this.z = z;
        }

    } // end Vertex

} // end namespace
 

Written by admin

May 21st, 2006 at 2:08 am

Posted in code

signing C++/CLI assemblies

with one comment

Stubbornness is a form of persistence and I’m continually amazed at my inability to give something up and move on – at least when it comes to programming. Getting a signed C++/CLI DLL to work with a C# project had me stumped for a while. Here’s the process I went through.

First, create a new solution:

Now add, a C++/CLI class library project:

I’ve called the project ClassLibrary1 and added some trivial code:


namespace ClassLibrary1
{
    public ref class Class1
    {
        public: void Foo()
        {
            Console::WriteLine("Bar");
        }
   };
}
 

The first step to making this a strong-named DLL is to generate a cryptographic key pair using sn.exe. This command line utility is located under the “Microsoft Visual Studio 8SDKv2.0Bin” directory path. To execute sn.exe you’ll need a Run prompt. To bring up the Run prompt hit the Windows+R key combo. Now type CMD and hit Enter:

You have to navigate to the Bin directory mentioned earlier. The DOS command to do this is called CD (change directory). For example:

Call sn.exe with the -k option to generate a new key pair to the specified file. In this example, a new key pair is generated and written to the file ClassLibrary1.snk:

Grab the newly generated file from the Bin folder and cut ‘n paste it into your project folder:

Inside of AssemblyInfo.cpp, add the following line of code:


[assembly:AssemblyKeyFile("ClassLibrary1.snk")];
 

Compile the project and mt.exe (manifest tool) returns the following:


general warning 810100b3: ..debugClassLibrary1.dll is a strong-name
signed assembly and embedding a manifest invalidates the signature.
You will need to re-sign this file to make it a valid assembly.
 

Okay…So let’s get rid of the embedded manifest:

A rebuild proves successful. Now add a CLR console app:

Right-click the project and select References to bring up the following dialog:

Under the Projects tab select ClassLibrary1 and click OK:

You should see the newly added ClassLibrary1 in the References section. None of the Build Properties need to be altered.

I’ve added the following code to test things out:


using namespace System;
using namespace System::Reflection;

int main(array<System::String ^>^ args)
{
    ClassLibrary1::Class1 c1;
    c1.Foo();

    for each(Assembly^ a in AppDomain::CurrentDomain->GetAssemblies())
    {
        Console::WriteLine(a->FullName);
    }

   Console::ReadLine();
   return 0;
}
 

Set ConsoleApplication1 as the startup project and test it app. Output example:

The output shows the full name of ClassLibrary1 with PublicKeyToken and hash value. Now, you may think everything is working fine but let’s see what happens when a C# project tries to use ClassLibrary1.dll. Add a new C# console project and a reference to ClassLibrary1:

As soon as you do so, the following warning pops up:

And sure enough, if you compile it, the build fails with a ResolveNativeReference task failure. So, delete the reference to ClassLibrary1. Add the reference again but this time, do so from the Browse tab:

Recompile and it builds successfully. Now, just because it compiles doesn’t mean it works, so let’s test it out.


using System;
using System.Collections.Generic;
using System.Text;
using System.Reflection;

namespace ConsoleApplication2
{
    class Program
    {
        static void Main(string[] args)
        {
            ClassLibrary1.Class1 c1 = new ClassLibrary1.Class1();
            c1.Foo();

            foreach (Assembly a in AppDomain.CurrentDomain.GetAssemblies())
            {
                Console.WriteLine(a.FullName);
            }

            Console.ReadLine();
        }
    }
}
 

Hit F5 to launch a debug session and:

The specified module could not be found. Great…but which module? After messing with this failure for a long time it turns out I should have signed the assembly in a different manner (not using the AssemblyKeyFile attribute). Have you ever signed a C# DLL? If you open the AssemblyInfo.cs file (it’s inside the Properties folder) and add the following:


[assembly: AssemblyKeyFile("SomeCSharpLibrary.snk")]
 

It returns with a warning not to use it.


Use command line option '/keyfile' or appropriate project settings instead of
'AssemblyKeyFile'
 

Visual Studio prefers you go under project properties to set the stong name.

Okay. Perhaps we can do something similar with the C++/CLI DLL? In the ClassLibrary1 project, remove the AssemblyKeyFile attribute from the AssemblyInfo.cpp file. Under the project properties, restore Embed Manifest to Yes. Now go to the Advanced section and set:

A rebuild proves successful. Run it and it works! Finally…

In the future, don’t forget to add the library via the Browse tab or you’ll get a ResolveNativeReference error.

Written by admin

May 20th, 2006 at 6:23 pm

Posted in code

Hello Kondo!

without comments

I’m sure the marketing department will tell you that the Kondo KHR-1 is a programmable humanoid robot capable of amazing feats.

It looks humanoid. It’s programmable – via the RCB-1 board mounted on its back. It’s certainly a robot. However, in its current state it’s nothing more than a bunch of servos held together by aluminum brackets. Only through programmability can we bring it to life.

Set aside your dreams of artificial intelligence for the moment. The first thing we need to accomplish is basic movement. The equivalent of a “Hello World!” app for the KHR-1 is the following:

Move the slider, move the head. Once you learn to move one servo, you can move many.

Communication between the PC and the RCB-1 happens through the serial port. If your computer (ie. laptop) doesn’t have a serial port, you can purchase a USB to serial port adapter.

Thankfully, the release of .NET 2.0 saw the introduction of a SerialPort class (found under the System.IO.Ports namespace). You can drag ‘n drop an instance of the SerialPort control from the Toolbox onto your Form. Here”s the Form constructor:


public Form1()
{
    InitializeComponent();
    this.serialPort1 = new System.IO.Ports.SerialPort();
    this.serialPort1.BaudRate = 115200;

    // change to whatever port # you're using
    this.serialPort1.PortName = "COM4";
    this.serialPort1.ReadTimeout = 30; // ms timeout

    try
    {
        serialPort1.Open();
    }
    catch(Exception e)
    {
        MessageBox.Show(e.Message + "nnBe sure to change the port # in code");
    }
}
 

If the serial port is successfully opened, trackbar events will be routed to the RotateHead method:


private void OnTrackbarScroll(object sender, EventArgs e)
{
    if (serialPort1.IsOpen)
        this.RotateHead((byte)this.trackBar1.Value);
}
 

To communicate with the RCB-1, fill an array of bytes and send them out the port. How do you know what bytes to send? A command reference PDF is available for download, however it’s not written in English. Fortunately, it has been translated. Here is page six from the RCB1stdraft.doc included with the download:

This page details the command used to send positional data to one or more servos. TX stands for transmission. An array of 16 bytes needs to be filled with the following information:

  • Byte 1 – The command. 0xFD to flag a servo position change.
  • Byte 2 – The board ID. The KHR-1 has two RCB-1 boards. One board controls servos 1-12, the other board controls servos 13-24.
  • Byte 3 – The servo speed. Eight speeds are supported with 0 being fastest and 7 the slowest.
  • Bytes 4-15 – The servo degree. There are 12 channels (corresponding to servos on either board) specifying the degree of rotation.
  • Byte 16 – A checksum. This is a value computed from the contents of the packet. The RCB-1 will calculate the checksum (given the contents) to determine if the data was sent correctly.

public void RotateHead(byte degree)
{
    byte[] by = new byte[16];
    byte command = 0xFD;
    byte boardId = 0;
    byte speed   = 0; // 0 = fastest, 7 = slowest

    by[0] = command;
    by[1] = boardId;
    by[2] = speed;

    // you can rotate the servos 0 to 180
    // so set unused channels to a value beyond this range
    by[3] = 255; // ch 1
    by[4] = 255; // ch 2
    by[5] = 255; // ch 3
    by[6] = 255; // ch 4
    by[7] = 255; // ch 5
    by[8] = degree; // ch 6 - the head on my kondo
    by[9]  = 255; // ch 7
    by[10] = 255; // ch 8
    by[11] = 255; // ch 9
    by[12] = 255; // ch 10
    by[13] = 255; // ch 11
    by[14] = 255; // ch 12
    by[15] = (byte)(command + boardId + speed + (255 * 11 + degree) & 0x7F);

    Trace.WriteLine("sending...");
    this.serialPort1.Write(by, 0, by.Length);
}
 

You can download the source here.

Written by admin

April 11th, 2006 at 3:43 am

Posted in code

powermate

without comments

The Griffin PowerMate is a USB input device. In addition to the hardware, it comes with a driver and a control panel. The PowerMate control panel allows you to map a “User Action” to a “Computer Action”. For example, when the user rotates the knob right, the page in Internet Explorer will scroll down:

For the majority of end users, this is all the customization they need. However, wouldn’t it be nice if we could program directly against the PowerMate? That’s the focus of today’s post and here’s a screenshot of the resulting app:

The first requirement to programming the PowerMate is a copy of the Windows Driver Development Kit (DDK). The Windows Server 2003 SP1 DDK is free. However, you will have to pay for shipping and handling of the CD to your door. The option to download is available only to MSDN subscribers. After installation, the directory paths will need to be set. Add the DDK directory path to the start of the search path (bottom of the list) to avoid compilation errors.

The Solution distributed with this post has two projects:

  • C++/CLI class library for the PowerMate code
  • C# application to test the PowerMate.dll

Let’s begin by listing the types and data members of the PowerMate class. First, simple enums to represent button state and rotational direction:


public: enum class ButtonState
{
    Up,
    Down
};

public: enum class RotationalDirection
{
    Left,
    Right
};
 

Next, we have a private enum representing human interface device (HID) attributes for the PowerMate hardware. These values will be compared against HID attributes for each device returned during the enumeration process.


private: enum class HidAttributes
{
    VendorID      = 0x077d,
    ProductID     = 0x0410,
    VersionNumber = 0x0311
};
 

A kernel object handle to the HID. We’ll be using this handle for read/write operations later on.


private: HANDLE handleToDevice;
 

A managed thread handle. The thread created will be used for event notification and only terminates when the exit event is triggered.


private: Thread^ inputThread;
private: ManualResetEvent^ exitEvent
 

Next, we have storage for a rotational value that is clamped between the upper and lower bounds. By default, the bounds are between -100 and 100. If a user continues rotating the knob beyond the specified range the event will be ignored and the delegate will not fire. Of course, you’re free to alter this feature.


privateint rotationValue;
privateint rotationLowerBound;
privateint rotationUpperBound;
 

Finally, delegates for button and rotation events:


publicdelegate void RotationHandler(RotationalDirection direction, int value);
publicdelegate void ButtonHandler(ButtonState bs);
private: RotationHandler^ rotationDelegate;
private: ButtonHandler^ buttonDelegate;
 

Clients can subscribe to events using the following event accessors:


publicevent RotationHandler^ RotateEvent
{
    void add(RotationHandler^ value)
    {
        rotationDelegate += value;
    }

    void remove(RotationHandler^ value)
    {
        rotationDelegate -= value;
    }
}

publicevent ButtonHandler^ ButtonEvent
{
    void add(ButtonHandler^ value)
    {
        buttonDelegate += value;
    }

    void remove(ButtonHandler^ value)
    {
        buttonDelegate -= value;
    }
}
 

Before we examine more properties of the PowerMate class, let’s first have a look at its two-stage construction. The constructor is trivial so we can safely skip it. After construction, the Initialize method needs to be called. Initialize will enumerate available HID devices and open the first PowerMate device it encounters. Once you’ve successfully programmed one PowerMate device, you can easily modify the code to suport many – perhaps by creating a PowerMateManager class to maintain a list of every PowerMate connected to the system.

The Initialize method begins by finding and shutting down the PowerMate Control Panel. Why? The PowerMate Control Panel intercepts input and executes the appropriate action stored in its settings. An example of one such setting is ”Increase volume” under Global settings. As our app responds to the user performing a right rotation, the PowerMate Control Panel also responds to this action and increases the system volume. To obtain exclusive access to the hardware, we need to close the PowerMate Control Panel (PowerMate.exe).

Please note that all of the code related to the Initialize method has been stripped clean of of error handling and conditionals for presentation purposes.


array^ powerMateProcess = Process::GetProcessesByName("PowerMate");

HANDLE hp = OpenProcess(SYNCHRONIZE | PROCESS_TERMINATE,
                        FALSE,
                        powerMateProcess[0]->Id);

HWND hWnd = FindWindow(NULL, L"PowerMateWnd");
PostMessage(hWnd, WM_CLOSE, 0, 0);
WaitForSingleObject(hp, 5000);
 

Now it’s time to put that DDK to work. This section of the Initialize method involves the following steps:

  1. Get the HID GUID.
  2. Obtain a handle to a device information set.
  3. Enumerate device interfaces.
  4. Open a device.
  5. Check for matching HID attributes.

Step 1: Get the HID GUID.
This GUID represents a device interface that the driver exposes. Device interfaces with the same functionality are grouped together in interface classes. For example, all joysticks on the system could be members of the joystick device interface class.


GUID hidClass;
HidD_GetHidGuid(&hidClass);
 

Step 2: Obtain a handle to a device information set.
A device information set consists of device information elements. A device information element contains a list of all device interfaces associated with a particular device. The SetupDiGetClassDevs function retrieves a set of devices for the specified class. In other words, a list of HID-compliant devices.


HDEVINFO hDevInfoSet = SetupDiGetClassDevs(&hidClass,
                                           NULL,
                                           NULL,
                                           DIGCF_PRESENT | DIGCF_INTERFACEDEVICE);
 

Step 3: Enumerate device interfaces.
To communicate with the device, we need to open the device. To open the device, we need a device path. The device path is retrieved during the enumeration process and involves the following steps:

  • For each device class in the device information set, call SetupDiEnumDeviceInterfaces.
  • Each call to the above function returns an SP_DEVICE_INTERFACE_DATA structure.
  • We pass the above structure to SetupDiGetDeviceInterfaceDetail.
  • The above method returns an SP_DEVICE_INTERFACE_DETAIL_DATA structure.
  • The SP_DEVICE_INTERFACE_DETAIL_DATA structure contains the device path.

SP_INTERFACE_DEVICE_DATA interfaceData;
interfaceData.cbSize = sizeof(interfaceData);

for (int i = 0;
     SetupDiEnumDeviceInterfaces(hDevInfoSet,
                                 NULL,
                                 &hidClass,
                                 i,
                                 &interfaceData);
     ++i)
{
    // determine buffer size first
    DWORD bufferLength;
    SetupDiGetDeviceInterfaceDetail(hDevInfoSet,
                                    &interfaceData,
                                    NULL,
                                    0,
                                    &bufferLength,
                                    NULL);

    // then allocate
    PSP_INTERFACE_DEVICE_DETAIL_DATA interfaceDetail =
        (PSP_INTERFACE_DEVICE_DETAIL_DATA)new char[bufferLength];

    interfaceDetail->cbSize = sizeof(SP_INTERFACE_DEVICE_DETAIL_DATA);

    SetupDiGetDeviceInterfaceDetail(hDevInfoSet,
                                    &interfaceData,
                                    interfaceDetail,
                                    bufferLength,
                                    NULL,
                                    NULL);
 

Step 4: Open a device.
With a successful call to SetupDiGetDeviceInterfaceDetail above we now have a device path to pass into CreateFile. Upon success, the CreateFile function will open the device and return a handle to it.


HANDLE hDevice = CreateFile(interfaceDetail->DevicePath,
                            GENERIC_READ | GENERIC_WRITE,
                            FILE_SHARE_READ | FILE_SHARE_WRITE,
                            NULL,
                            OPEN_EXISTING,
                            FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED,
                            NULL);
 

Step 5: Check for matching HID attributes.
We check the attributes of the newly opened device to verify it’s a PowerMate input device.


HIDD_ATTRIBUTES hidAttr;
BOOLEAN result = HidD_GetAttributes(hDevice, &hidAttr);

if ((int)HidAttributes::ProductID == hidAttr.ProductID &&
    (int)HidAttributes::VendorID  == hidAttr.VendorID)
{
    // ...

    inputThread = gcnew Thread(gcnew ThreadStart(this, &PowerMate::InputThread));

    inputThread->Priority = ThreadPriority::Normal;
    inputThread->Start();

    return true;
}
 

When a PowerMate input device is found, an input thread is started for event notification. We”ll have a look at the input thread shortly, but first two-stage destruction:


publicvoid Shutdown()
{
    if (nullptr != exitEvent)
    {
        exitEvent->Set();
        inputThread->Join();
    }

    if (NULL != handleToDevice)
        CloseHandle(handleToDevice);

    Process^ p = Process::Start(
        "C:Program FilesGriffin TechnologyPowerMatePowerMate.exe");

    HWND hWndTaskBar   = FindWindowEx(NULL, NULL, L"Shell_TrayWnd", NULL);
    HWND hWndTrayNotify= FindWindowEx(hWndTaskBar, NULL, L"TrayNotifyWnd", NULL);
    HWND hWndSysPager  = FindWindowEx(hWndTrayNotify, NULL, L"SysPager", NULL);
    HWND hWndToolBar   = FindWindowEx(hWndSysPager, NULL, L"ToolbarWindow32", NULL);

    RECT rect;
    ::GetClientRect(hWndToolBar, &rect);

    for (WORD x = 0; x < rect.right; x += 8)
    {
        ::SendMessage(hWndToolBar,
                      WM_MOUSEMOVE,
                      0,
                      MAKELPARAM(x,0));
    }
}
 

Shutdown begins by signaling the exit event. Join blocks the calling thread until the input thread terminates.
Next, we start a new instance of the PowerMate Control Panel (after shutting it down during Initialization). It”s hardwired to the default installation directory. However, it’s advisable that you pull this path out of the registry. The InstallDir key is located under: HKEY_LOCAL_MACHINESOFTWAREGriffin TechnologyPowerMate1.5 and you can use regedit to verify this.

The last part is silly and if you have a better way to fix it please let me know. The icon in the task notification area does not update automatically. Here, we simulate a mouse traveling across the notification area to force an update.

The input thread drops into a while loop that only breaks when it receives an exit event. The exit event is checked during wait timeouts which occur every 2000 milliseconds. With each iteration a call to ReadFile is made. The OVERLAPPED structure is used for asynchronous I/O. The event member of the OVERLAPPED structure is signaled when the read operation is complete. Upon a successful read, a report buffer consisting of eight characters is returned. Indices in the report buffer are checked for a button or rotational event and the corresponding delegate is fired.


privatevoid InputThread()
{
    char reportBuffer[8] = {0};
    DWORD dwBytesRead = 0;
    BOOL result = FALSE;

    HANDLE hEvent = CreateEvent(NULL, FALSE, FALSE, NULL);

    OVERLAPPED overLap;
    ZeroMemory( &overLap, sizeof(overLap));
    overLap.hEvent = hEvent;

    while(true)

    {
        result = ReadFile(handleToDevice,
                          reportBuffer,
                          sizeof(reportBuffer),
                          &dwBytesRead,
                          &overLap);

        DWORD dw = WaitForSingleObject(hEvent, 2000);

        switch(dw)
        {
            case WAIT_OBJECT_0:
            {
                // test for pushbutton
                if (reportBuffer[1] == 1)
                {
                    ButtonState bs = ButtonState::Down;
                    EventsHelper::Fire(buttonDelegate, bs);
                }
                else if (reportBuffer[2] == 0 && reportBuffer[1] == 0)
                {
                    ButtonState bs = ButtonState::Up;
                    EventsHelper::Fire(buttonDelegate, bs);
                }

                // test for knob rotation
                if (reportBuffer[2] != 0)
                {
                    rotationValue += reportBuffer[2];
                    if (rotationValue < rotationLowerBound)
                    {
                        rotationValue = rotationLowerBound;
                        break;
                    }

                    if (rotationValue > rotationUpperBound)
                    {
                        rotationValue = rotationUpperBound;
                        break;
                    }

                    EventsHelper::Fire(rotationDelegate,
                                       (reportBuffer[2] < 0) ?
                                           RotationalDirection::Left :
                                           RotationalDirection::Right,
                                       rotationValue);

                } // end ifbreak;

            case WAIT_TIMEOUT:
            {
                if (exitEvent->WaitOne(20,false))
                {
                    CloseHandle(hEvent);
                    return;
                }
            } break;

        } // end switch

// end while

// end method
 

The EventsHelper class continues to publish events even if a subscriber throws an exception:


public ref class EventsHelper
{
    publicstatic void Fire(Delegate^ del, ... array^ args)
    {
        if (nullptr == del)

            return;

        array^ delegates = del->GetInvocationList();
        for each (Delegate^ sink in delegates)
        {
            try
            {
                sink->DynamicInvoke(args);
            }
            catch(Exception^ e)
            {

            }
        }
    }
};
 

Finally, we can start manipulating properties of the PowerMate input device. Setting various properties involve the same basic steps:

  • Filling out report buffers and transmitting them.
  • Receiving report buffers and acting on them.

For example, getting or setting the LED brightness:


publicproperty Byte LedBrightness
{
    void set(Byte value)
    {
        if (NULL != handleToDevice)
        {
            HANDLE hEvent = CreateEvent(NULL, FALSE, FALSE, NULL);

            OVERLAPPED overLap;
            ZeroMemory(&overLap, sizeof(overLap));
            overLap.hEvent = hEvent;

            UCHAR reportBuffer[2];
            reportBuffer[0] = 0;
            reportBuffer[1] = value;
            DWORD dwBytesTransmitted = 0;

            BOOL result = WriteFile(handleToDevice,
                                    reportBuffer,
                                    sizeof(reportBuffer),
                                    &dwBytesTransmitted,
                                    &overLap);

            DWORD dw = WaitForSingleObject(hEvent, 42);

            switch(dw)
            {
                case WAIT_OBJECT_0:
                    break;

                case WAIT_TIMEOUT:
                    break;
            }

            CloseHandle(hEvent);
        }
    }

    Byte get()
    {
        UCHAR reportBuffer[7] = { 0 };

        if (NULL != handleToDevice)
        {
            BOOLEAN error = HidD_GetInputReport(handleToDevice,
                                                reportBuffer,
                                                sizeof(reportBuffer));

            if( FALSE == error )
                Trace::WriteLine("Failure to get input report - brightness");
        }

        return reportBuffer[4];
    }
}
 

The two other properties are ”pulse on” and ”pulse speed”. Please reference the code for implementation details.

Those are the basics of PowerMate programming. Special thanks to Dustin at Griffin for making this possible. The code still needs refactoring and more support for error handling. However, it’s surely enough to get you started. You can download the source here.

Written by admin

April 3rd, 2006 at 5:09 am

Posted in code