Permalink
Switch branches/tags
Find file Copy path
Fetching contributors…
Cannot retrieve contributors at this time
283 lines (252 sloc) 7.65 KB
--binding/struct: struct ctype wrapper
--Written by Cosmin Apreutesei. Public Domain.
setfenv(1, require'winapi.namespace')
require'winapi.util'
local Struct = {}
local Struct_meta = {__index = Struct}
--struct virtual field setter and getter -------------------------------------
local setbit = setbit --cache
local pins = setmetatable({}, {__mode = 'k'}) --{cdata = {field = pinned_val}}
function Struct:set(cdata, field, value) --hot code
if type(field) ~= 'string' then
error(string.format('struct "%s" has no field of type "%s"', self.ctype, type(field)), 5)
end
local def = self.fields[field]
if def then
local name, mask, setter = unpack(def, 1, 3)
if mask then
cdata[self.mask] = setbit(cdata[self.mask] or 0, mask, value ~= nil)
end
if name then
if setter then
value = setter(value, cdata)
end
--cdata values are pinned to their respective struct field automatically.
--only the current value is pinned. the old value is released when a new value is set.
if type(value) == 'cdata' then
local t = pins[cdata]
if not t then
t = {}
pins[cdata] = t
end
t[field] = value
end
cdata[name] = value
else
setter(value, cdata) --custom setter
end
return
end
--masked bitfield support.
def = self.bitfields and self.bitfields[field]
if def then
--support the syntax `struct.field = {BITNAME = true|false, ...}`
for bitname, enabled in pairs(value) do
cdata[field..'_'..bitname] = enabled
end
return
else
--support the syntax `struct.field_BITNAME = true|false`.
local fieldname, bitname = field:match'^([^_]+)_(.*)'
if fieldname then
def = self.bitfields[fieldname]
if def then
local datafield, maskfield, prefix = unpack(def, 1, 3)
local mask = _M[prefix..'_'..bitname:upper()]
if mask then
cdata[maskfield] = setbit(cdata[maskfield] or 0, mask, value ~= nil)
cdata[datafield] = setbit(cdata[datafield] or 0, mask, value)
return
end
end
end
end
--TODO: find a way to raise this error on assignment but not on initialization.
--error(string.format('struct "%s" has no field "%s"', self.ctype, field), 5)
end
local getbit = getbit
function Struct:get(cdata, field) --hot code
if type(field) ~= 'string' then
error(string.format('struct "%s" has no field of type "%s"', self.ctype, type(field)), 5)
end
local def = self.fields[field]
if def then
local name, mask, _, getter = unpack(def, 1, 4)
if not mask or getbit(cdata[self.mask], mask) then
if not name then
if getter then
return getter(cdata)
else
return true
end
elseif not getter then
return cdata[name]
else
return getter(cdata[name], cdata)
end
else
return nil
end
end
--masked bitfield support
local fieldname, bitname = field:match'^([^_]+)_(.*)'
if fieldname then
def = self.bitfields[fieldname]
if def then
local datafield, maskfield, prefix = unpack(def, 1, 3)
local mask = _M[prefix..'_'..bitname]
if mask then
if getbit(cdata[maskfield] or 0, mask) then
return getbit(cdata[datafield] or 0, mask)
else
return nil --masked off
end
end
end
end
error(string.format('struct "%s" has no field "%s"', self.ctype, field), 5)
end
--struct instance constructor ------------------------------------------------
--set all fields with a table.
function Struct:setall(cdata, t)
if not t then return end
for field, value in pairs(t) do
cdata[field] = value
end
end
--set all fields which have default values to their default values.
function Struct:setdefaults(cdata)
if not self.defaults then return end
for field, value in pairs(self.defaults) do
cdata[field] = value
end
end
function Struct:init(cdata) end --stub
function Struct:reset(cdata)
local size = ffi.sizeof(cdata)
ffi.fill(cdata, size)
if self.size then
cdata[self.size] = size
end
self:setdefaults(cdata)
end
--create a struct with a clear mask and default values.
--cdata passes through untouched.
function Struct:new(t)
if type(t) == 'cdata' then return t end
local cdata = self.ctype_cons()
self:reset(cdata)
self:setall(cdata, t)
--TODO: provide a way to make in/out buffer allocations declarative
--instead of manually via init constructor (see winapi.filedialogs).
self.init(cdata)
return cdata
end
--clear all mask bits (prepare the struct for setting data).
function Struct:clearmask(cdata)
if self.mask then cdata[self.mask] = 0 end
end
--create or use existing struct and set all mask bits (prepare for receiving data).
function Struct:setmask(cdata)
if not cdata then
cdata = self.ctype_cons()
if self.size then cdata[self.size] = ffi.sizeof(cdata) end
end
if self.mask then cdata[self.mask] = self.full_mask end
return cdata
end
Struct_meta.__call = Struct.new
--collect the values of all virtual fields in a table.
function Struct:collect(cdata)
local t = {}
for field in pairs(self.fields) do
t[field] = cdata[field]
end
return t
end
--compute the struct's full mask (i.e. with all mask bits set).
function Struct:compute_mask()
local mask = 0
for field, def in pairs(self.fields) do
local maskbit = def[2]
if maskbit then mask = bit.bor(mask, maskbit) end
end
return mask
end
--struct definition constructor ----------------------------------------------
local valid_struct_keys =
index{'ctype', 'size', 'mask', 'fields', 'defaults', 'bitfields', 'init'}
local function checkdefs(s) --typecheck a struct definition
assert(s.ctype ~= nil, 'ctype missing')
for k,v in pairs(s) do --check for typos in struct definition
assert(valid_struct_keys[k], 'invalid struct key "%s"', k)
end
if s.fields then --check for accidentaly hidden fields
for vname,def in pairs(s.fields) do
local sname, mask, setter, getter = unpack(def, 1, 4)
assert(vname ~= sname, 'virtual field "%s" not visible', vname)
end
end
end
function struct(s)
checkdefs(s)
setmetatable(s, Struct_meta)
s.ctype_cons = ffi.typeof(s.ctype)
if s.mask then s.full_mask = s:compute_mask() end
ffi.metatype(s.ctype_cons, { --setup ctype for virtual fields
__index = function(cdata,k)
return s:get(cdata,k)
end,
__newindex = function(cdata,k,v)
s:set(cdata,k,v)
end,
})
return s
end
--struct field definitions constructors --------------------------------------
--field definitions constructor for defining non-masked struct fields.
--t is a table of form {virtfield1, cfield1, setter, getter, virtfield2, ...}.
function sfields(t)
local dt = {}
for i=1,#t,4 do
assert(type(t[i]) == 'string', 'invalid sfields spec')
assert(type(t[i+1]) == 'string', 'invalid sfields spec')
dt[t[i]] = {
t[i+1] ~= '' and t[i+1] or nil,
nil,
(type(t[i+2]) ~= 'function' or t[i+2] ~= pass) and t[i+2] or nil,
(type(t[i+3]) ~= 'function' or t[i+3] ~= pass) and t[i+3] or nil,
}
end
return dt
end
--field definitions constructor for defining masked struct fields.
--t is a table of form {virtfield1, cfield1, mask, setter, getter, virtfield2, ...}.
function mfields(t)
local dt = {}
for i=1,#t,5 do
assert(type(t[i]) == 'string', 'invalid mfields spec')
assert(type(t[i+1]) == 'string', 'invalid mfields spec')
assert(type(t[i+2]) == 'number', 'invalid mfields spec')
dt[t[i]] = {
t[i+1] ~= '' and t[i+1] or nil,
t[i+2] ~= 0 and t[i+2] or nil,
t[i+3] ~= pass and t[i+3] or nil,
t[i+4] ~= pass and t[i+4] or nil,
}
end
return dt
end
--struct field setters and getters -------------------------------------------
--create a struct setter for setting a fixed-size WCHAR[n] field with a Lua string.
function wc_set(field)
return function(s, cdata)
wcs_to(s, cdata[field])
end
end
--create a struct getter for getting a fixed-size WCHAR[n] field as a Lua string.
function wc_get(field)
return function(cdata)
return mbs(ffi.cast('WCHAR*', cdata[field]))
end
end