Structure definition class
Current version: 1.0.001
What?
The structure definition class allows developers to easily define, create, access and modify memory structures to use in many parts of the Win32 API. Instead of calculating the memory positions of the individual members and doing all the needed conversion by hand, this class does the hard work for you and lets you access members by name.
Why?
The power of this class really shows itself with an example. Say you needed a POINT structure to retrieve the current mouse position with
GetCursorPos.
According to MSDN, a POINT is defined as followed:
cpp code:
typedef struct tagPOINT {
LONG x;
LONG y;
} POINT, *PPOINT;
Using just the Plus! scripting engine, we could retrieve the cursor position with:
js code:
var pt = Interop.Allocate(8);
Interop.Call( 'user32', 'GetCursorPos', pt );
var x = pt.ReadDWORD(0);
var y = pt.ReadDWORD(4);
For small structures like this one, this is still pretty manageable. However when you need a structure with 10 or 20 members with different types and sizes, it quickly becomes frustrating to figure out the right positions.
Now have a look at how this same task can be achieved with this class:
js code:
var POINT = new StructType({
x: DataType.LONG,
y: DataType.LONG
};
var pt = new POINT;
Interop.Call( 'user32', 'GetCursorPos', pt.dataptr() );
var x = pt.x();
var y = pt.y();
As you can see, this is much easier to read and to work with. If that doesn't convince you, try a more complex example:
js code:
var LVITEM = new StructType({
mask: DataType.UINT,
iItem: DataType.INT,
iSubItem: DataType.INT,
state: DataType.UINT,
stateMask: DataType.UINT,
pszText: DataType.LPTSTR,
cchTextMax: DataType.INT,
iImage: DataType.INT,
lParam: DataType.LONG,
iIndent: DataType.INT,
iGroupId: DataType.INT,
cColumns: DataType.UINT,
puColumns: DataType.UINT
});
var firstItem = new LVITEM;
firstItem.mask( /* LVIF_TEXT */ 0x1 );
firstItem.pszText( 'First item' );
PlusWnd.SendControlMessage( 'MyListView', /* LVM_INSERTITEMW */ (0x1000 + 77), 0, firstItem.dataptr() );
Not only the memory positions are handled by the class, it also does the needed type conversions for you. In this example, the pszText member takes a string, creates a new DataBloc, writes the string to it and then writes the pointer of that DataBloc to the structure.
Features
- Define once, use forever. By defining a structure type, you create a new class which creates structures. You can create as many class instances as you want, so you don't have to redefine that type ever again.
- Defining structures is almost like copy pasting from MSDN. You can use the same names for the members and in almost all cases, you can use the same names for the data types as well. If MSDN says that 'mask' is a LONG, then just define it as a DataType.LONG, no need to figure out the corresponding DataBloc method any more.
- Accessing members couldn't be easier: just use their names. No more fiddling with memory positions, the class always knows where the members are located.
- Combined getter/setter methods for each member. To write a value to a member, pass it as the first parameter. To read the value of a member, just pass no arguments. Can it be any easier?
js code:
// Get
var x = struct.myMember();
// Set
struct.myMember(10);
- Convert values automatically to the right type. Some DataTypes have a custom setter methods which allow you to pass a value in different types, such a string for an LPSZ, an [x,y] array for a POINT or red/green/blue values for a COLORREF.
- Use structures in structures. Occasionally, you'll find that a structure contains another structure. For example, NMLISTVIEW has a NMHDR structure for its 'hdr' member. Instead of copying the members of NMHDR to NMLISTVIEW, just create an NMHDR type and use it within NMLISTVIEW:
js code:
var NMHDR = new StructType({
hwndFrom: DataType.HWND,
idFrom: DataType.UINT,
code: DataType.UINT
});
var NMLISTVIEW = new StructType({
hdr: NMHDR,
iItem: DataType.INT,
iSubItem: DataType.INT,
uNewState: DataType.UINT,
uOldState: DataType.UINT,
uChanged: DataType.UINT,
ptAction: DataType.POINT,
lParam: DataType.LONG
});
You can then access a sub structure member with:
js code:
var nmListView = new NMLISTVIEW;
var hwndFrom = nmListView.hdr().hwndFrom();
This is possible since StructTypes double as DataTypes, so you can use them just like you'd use a DataType. (prototypical inheritance ftw!)
- Easily work with datatype arrays. Some structures take an array of values for a single member, for example LOGFONT takes an array of 32 TCHARS for its 'lfFaceName' member. Instead of defining 32 different TCHAR members in a structure type, you can use DataType.Array to create an array-like data type with a fixed length.
js code:
var LF_FACESIZE = 32;
var LOGFONT = new StructType({
lfHeight: DataType.LONG,
// ...
lfFaceName: DataType.Array(DataType.TCHAR, LF_FACESIZE)
});
var font = new LOGFONT;
// Write a single item to a specific index
font.lfFaceName().item(0, 'a');
// Read a single item from a specific index
var char = font.lfFaceName().item(0);
// Write an array
font.lfFaceName().fromArray(['a', 'b', 'c']);
// Read as an array
var chars = font.lfFaceName().toArray();
// (W)CHAR arrays even have extra methods for easier handling
font.lfFaceName().fromString('abcdef');
var fontFace = font.lfFaceName().toString();
Download
»
Structure definition class (v1.0.001 - 7 November 2010)
Change-log
- 1.0.001 (7 November 2010)
To-do
- More DataTypes? Post your suggestions below!
Note
This thread was split from SmokingCookie's thread about
list-view groups. I threw in the structure class while trying to get grouping support in my ListView class. Because this was the first time the community heard about this class, there was a lot of excitement about it so the thread got split. That's why some of the first replies here may refer to this other thread.