├── .gitignore ├── README.md ├── bin └── ffi-generate.js ├── lib ├── ffi-type-map.js └── generateffi.js ├── package.json └── templates └── ffi ├── array.mustache ├── ffi.mustache ├── funcptr.mustache ├── opaque.mustache ├── struct.mustache └── union.mustache /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | `npm install -g ffi-generate` 2 | 3 | Generate FFI Bindings 4 | --------------------- 5 | `ffi-generate -f /path/to/myLibrary/header.h -l libmyLibrary` 6 | 7 | Will parse the given filename and print to standard out the resulting javascript 8 | suitable for use as a module. 9 | 10 | * f -- required -- The header file you wish to parse 11 | * l -- required -- The library FFI will use to dlopen 12 | * m -- optional -- The module name underwhich functions will be stored (uses library name otherwise) 13 | * p -- optional -- Only include functions whose name starts with the provided prefix 14 | - you can specify multiple `-p` on the command line to get multiple prefixes 15 | * x -- optional -- Restrict to only functions declared in the given header file 16 | * s -- optional -- Use StrictType type wrapper (experimental) 17 | * L -- optional -- If libclang.{so,dylib} is in a non-standard path use this 18 | which will rerun the process with `[DY]LD_LIBRARY_PATH` set 19 | 20 | It may be necessary to pass additional flags to libclang so it can better parse 21 | the header (i.e. include paths). To pass options directly to libclang use `--` 22 | so ffi-generate-node knows to stop parsing arguments, the rest will be passed 23 | to libclang without modification. 24 | 25 | `ffi-generate -f /usr/include/ImageMagick/wand/MagickWand.h -l libMagickWand -m wand -p Magick -- $(Magick-config --cflags)` 26 | 27 | Generate FFI Bindings Programatically 28 | ------------------------------------- 29 | ```javascript 30 | var exec = require('child_process').exec; 31 | var path = require('path'); 32 | var fs = require('fs'); 33 | var jsb = require('beautifyjs'); 34 | var generate = require('lib/generateffi').generate; 35 | 36 | exec('llvm-config --includedir', function (fail, out, err) { 37 | var includedir = out.replace(/\s+$/, ''); 38 | var result = exports.generate({ 39 | filename: path.join(includedir, 'clang-c', 'Index.h'), 40 | library: 'libclang', 41 | prefix: 'clang_', 42 | includes: [includedir], 43 | }); 44 | 45 | if (result.unmapped.length > 0) { 46 | console.log('----- UNMAPPED FUNCTIONS -----'); 47 | console.log(result.unmapped); 48 | console.log('----- UNMAPPED FUNCTIONS -----'); 49 | } 50 | 51 | fs.writeFileSync(path.join(__dirname, 'dynamic_clang.js'), jsb.js_beautify(result.serialized)); 52 | var dynamic_clang = require(path.join(__dirname, 'dynamic_clang')); 53 | var ver = dynamic_clang.libclang.clang_getClangVersion(); 54 | console.log(dynamic_clang.libclang.clang_getCString(ver)); 55 | dynamic_clang.libclang.clang_disposeString(ver) 56 | }); 57 | ```` 58 | Input to the generate method 59 | 60 | * opts.filename -- required -- the full path to the header source file to parse 61 | * opts.library -- required -- the library ffi should use to dlopen 62 | * opts.module -- optional -- the name of the module that will be exported (otherwise uses library name) 63 | * opts.prefix -- optional -- restrict imported functions to a given prefix 64 | * opts.includes -- optional -- a set of directory paths to aid type expansion 65 | * opts.compiler_args -- optional -- a set of clang command line options passed to the parser 66 | * opts.single_file -- optional -- restricts functions to only those defined in the header file 67 | - this does not restrict dependent types 68 | 69 | The result from generate is an object that has two properties 70 | 71 | * serialized - a string representation of the bindings suitable for writing to file 72 | * unmapped - a set of functions that failed to map -- most likely from failure to 73 | map a type to ffi type. 74 | - each element is an object with following properties 75 | * position - -1 means the return type, otherwise the argument 76 | * arg - name of the type that failed to map 77 | * name - the name of the function that failed 78 | * decl - the signature of the function that failed 79 | 80 | -------------------------------------------------------------------------------- /bin/ffi-generate.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | var jsb = require('js-beautify'); 4 | var argv = require('optimist') 5 | .usage('Generate node-ffi bindings for a given header file\nUsage: $0') 6 | .demand('f').alias('f', 'file').describe('f', 'The header file to parse') 7 | .demand('l').alias('l', 'library').describe('l', 'The name of the library to dlopen') 8 | .alias('m', 'module').describe('m', 'The name of module the bindings will be exported as') 9 | .boolean('x').alias('x', 'file_only').describe('x', 'Only export functions found in this file') 10 | .alias('p', 'prefix').describe('p', 'Only import functions whose name start with prefix') 11 | .boolean('s').alias('s', 'strict').describe('s', 'Use StrictType (experimental)') 12 | .alias('L', 'libclang').describe('L', 'Path to directory where libclang.{so,dylib} is located') 13 | .argv 14 | 15 | function tryClang(cb) { 16 | var libclang; 17 | 18 | try { 19 | libclang = require('libclang'); 20 | } catch (e) { 21 | libclang = false; 22 | } 23 | 24 | if (libclang) return cb(true); 25 | 26 | if (process.env.FFI_GENERATE_CHILD) return cb(false); 27 | 28 | require('child_process').exec('llvm-config --libdir', function (err, stdout, stderr) { 29 | if (stdout.trim()) { 30 | cb(stdout.trim()); 31 | } else { 32 | cb(err.code); 33 | } 34 | }); 35 | } 36 | 37 | function generate() { 38 | var generate = require('../lib/generateffi').generate; 39 | 40 | var ret = generate({ 41 | filename: argv.f, 42 | library: argv.l, 43 | module: argv.m, 44 | prefix: argv.p, 45 | compiler_args: argv._, 46 | strict_type: argv.s, 47 | single_file: argv.x, 48 | }); 49 | 50 | //console.log(jsb.js_beautify(ret.serialized)); 51 | console.log(ret.serialized); 52 | 53 | if (generate.unmapped) { 54 | process.stderr.write("-------Unmapped-------\r\n"); 55 | process.stderr.write(generate.unmapped + '\r\n'); 56 | } 57 | } 58 | 59 | tryClang(function (ret) { 60 | var library; 61 | 62 | if (isNaN(ret)) library = ret; 63 | if (argv.L) library = argv.L; 64 | 65 | if (ret === true) { 66 | generate(); 67 | } else if (library && ret !== false) { 68 | var env = process.env; 69 | env.FFI_GENERATE_CHILD = '1'; 70 | switch (process.platform) { 71 | case 'darwin': 72 | env.DYLD_LIBRARY_PATH = library + ':' + (env.DYLD_LIBRARY_PATH || ''); 73 | break; 74 | default: 75 | env.LD_LIBRARY_PATH = library + ':' + (env.LD_LIBRARY_PATH || ''); 76 | break; 77 | } 78 | var c = require('child_process').spawn(process.execPath, process.argv.slice(1), {env:env}); 79 | c.stdout.pipe(process.stdout); 80 | c.stderr.pipe(process.stderr); 81 | c.on('exit', function (code) { 82 | process.exit(code); 83 | }); 84 | } else { 85 | console.error('Unable to load libclang, make sure you have 3.2 installed, either specify -L or have llvm-config in your path'); 86 | } 87 | }); 88 | -------------------------------------------------------------------------------- /lib/ffi-type-map.js: -------------------------------------------------------------------------------- 1 | var libclang = require('libclang'); 2 | var Type = libclang.Type; 3 | 4 | module.exports = function (type) { 5 | var ret; 6 | switch (type) { 7 | case Type.Void: 8 | ret = 'ref.types.void'; 9 | break; 10 | case Type.Bool: 11 | ret = 'ref.types.byte'; 12 | break; 13 | case Type.Char_U: 14 | case Type.UChar: 15 | ret = 'ref.types.uchar'; 16 | break; 17 | case Type.UShort: 18 | ret = 'ref.types.ushort'; 19 | break; 20 | case Type.UInt: 21 | ret = 'ref.types.uint32'; 22 | break; 23 | case Type.ULong: 24 | ret = 'ref.types.ulong'; 25 | break; 26 | case Type.ULongLong: 27 | ret = 'ref.types.ulonglong'; 28 | break; 29 | case Type.Char_S: 30 | case Type.SChar: 31 | ret = 'ref.types.char'; 32 | break; 33 | case Type.Short: 34 | ret = 'ref.types.short'; 35 | break; 36 | case Type.Int: 37 | ret = 'ref.types.int32'; 38 | break; 39 | case Type.Long: 40 | ret = 'ref.types.long'; 41 | break; 42 | case Type.LongLong: 43 | ret = 'ref.types.longlong'; 44 | break; 45 | case Type.Float: 46 | ret = 'ref.types.float'; 47 | break; 48 | case Type.Double: 49 | ret = 'ref.types.double'; 50 | break; 51 | } 52 | return ret; 53 | }; 54 | module.exports.CString = 'ref.types.CString'; 55 | -------------------------------------------------------------------------------- /lib/generateffi.js: -------------------------------------------------------------------------------- 1 | var assert = require('assert'), 2 | fs = require('fs'), 3 | hogan = require('hogan.js'), 4 | path = require('path'), 5 | util = require('util'); 6 | 7 | var libclang = require('libclang'); 8 | 9 | var Cursor = libclang.Cursor, 10 | Index = libclang.Index, 11 | TranslationUnit = libclang.TranslationUnit, 12 | Type = libclang.Type; 13 | 14 | /* 15 | * Accepts an object as defined below 16 | * opts.filename -- required -- the full path to the header source file to parse 17 | * opts.library -- required -- the library ffi should use to dlopen 18 | * opts.module -- optional -- the name of the module that will be exported (otherwise uses library name) 19 | * opts.prefix -- optional -- restrict imported functions to a given prefix 20 | * opts.includes -- optional -- a set of directory paths to aid type expansion 21 | * opts.compiler_args -- optional -- a set of clang command line options passed to the parser 22 | * 23 | * returns an object with the following fields 24 | * unmapped -- an array where each element object describes a function that failed to map 25 | * position -- indicates which field failed to map, -1 is return value, otherwise argument index 26 | * arg -- the kind of type in question 27 | * name -- the spelling of the function 28 | * decl -- the method signature (excluding the result type) 29 | * serialized -- string representation of the types and module [eval or save] 30 | */ 31 | exports.generate = function (opts) { 32 | var fname = path.normalize(opts.filename); 33 | var library = opts.library; 34 | var module = opts.module || opts.library; 35 | var prefix = opts.prefix || []; 36 | var includes = opts.includes || []; 37 | var compiler_args = []; 38 | var single_file = opts.single_file || false; 39 | var strict_type = opts.strict_type || false; 40 | 41 | var generateType = opts.generateType || 'ffi'; 42 | 43 | var ffiMapType = require('./' + generateType + '-type-map'); 44 | 45 | var templateDir = path.join(__dirname, '..', 'templates', generateType); 46 | var ArrayTmpl = hogan.compile(fs.readFileSync(path.join(templateDir, 'array.mustache'), 'utf8')); 47 | var FuncPtrTmpl = hogan.compile(fs.readFileSync(path.join(templateDir, 'funcptr.mustache'), 'utf8')); 48 | var OpaqueTmpl = hogan.compile(fs.readFileSync(path.join(templateDir, 'opaque.mustache'), 'utf8')); 49 | var StructTmpl = hogan.compile(fs.readFileSync(path.join(templateDir, 'struct.mustache'), 'utf8')); 50 | var UnionTmpl = hogan.compile(fs.readFileSync(path.join(templateDir, 'union.mustache'), 'utf8')); 51 | var TypeTmpl = hogan.compile(fs.readFileSync(path.join(templateDir, generateType + '.mustache'), 'utf8')); 52 | 53 | if (opts.compiler_args) { 54 | compiler_args = opts.compiler_args.slice(0); 55 | } 56 | 57 | if (!(prefix instanceof Array)) { 58 | prefix = [prefix]; 59 | } 60 | 61 | var idx = new Index(true, true); 62 | var tu; 63 | 64 | includes.forEach(function (include) { compiler_args.push('-I'+include); }); 65 | tu = TranslationUnit.fromSource(idx, fname, compiler_args); 66 | 67 | var curs = tu.cursor; 68 | 69 | var unmapped = []; 70 | var structs = {}; 71 | var enums = {}; 72 | 73 | var toRender = { 74 | enums: [], 75 | types: [], 76 | functions: [], 77 | module: module, 78 | library: library, 79 | }; 80 | 81 | var WrapType = function (name) { 82 | this.name = name; 83 | this.type = name; 84 | this.abort = false; 85 | this.opaque = false; 86 | this.arrSize = false; 87 | this.return_type = undefined; 88 | this.args = []; 89 | this.elements = []; 90 | this.unionCount = 0; 91 | } 92 | 93 | /* For a given Typedef try and iterate fields and define an FFI struct */ 94 | var defineType = function (type, unionName) { 95 | /* We've previously defined this type 96 | * TODO XXX FIXME? wanted to use type.usr since it's relevant per translation unit 97 | * but using just the regular spelling makes things nicer for accessibility 98 | */ 99 | var name = type.spelling, wrap; 100 | 101 | if (!name && unionName) 102 | name = unionName; 103 | 104 | if (!name && type.usr) { 105 | // TODO XXX FIXME we probably have a record here and are hacking our way 106 | // to find out its name 107 | name = type.usr.split('@')[2]; 108 | } 109 | 110 | if (!name) { 111 | //console.error(name, !name, type.spelling, type.usr); 112 | return undefined; 113 | } 114 | 115 | 116 | wrap = structs[name]; 117 | 118 | if (wrap) { 119 | return wrap; 120 | } 121 | 122 | wrap = new WrapType(name); 123 | 124 | var first = true; 125 | type.visitChildren(function(parent) { 126 | var ret, t, tname; 127 | /* First item in the cursor is the one we started with? 128 | * immediately move beyond it 129 | */ 130 | //console.error('visiting', this.kind, this.spelling, parent.kind, parent.spelling); 131 | 132 | if (first && this.kind == Cursor.StructDecl) { 133 | first = false; 134 | return Cursor.Recurse; 135 | } 136 | 137 | if (this.kind == Cursor.UnionDecl) { 138 | var un = this.type.declaration.spelling || 'union' + wrap.unionCount++; 139 | //console.error('UnionDecl', un, this.kind); 140 | var union = defineType(this.type.declaration, un); 141 | if (union) { 142 | union.kind = 'union'; 143 | wrap.elements.push(union); 144 | } else 145 | //console.error('failed to wrap union'); 146 | return Cursor.Continue; 147 | } 148 | /* TODO XXX FIXME? 149 | * If the current position of the cursor has an empty string, we're 150 | * probably still at the start of the struct/typedef 151 | */ 152 | if (this.spelling) { 153 | /* Potentially some recursion here -- if this type depends on a type 154 | * a type we have yet to define this call should recurse until we're 155 | * in a sane state -- undefined if we can't map the type. 156 | */ 157 | if (this.kind == Cursor.TypeRef) { 158 | t = mapType(this.referenced.type, this.referenced.spelling); 159 | tname = this.referenced.spelling; 160 | } else if (this.kind == Cursor.UnexposedDecl) { 161 | t = mapType(this.type, this.type.spelling); 162 | tname = this.type.spelling; 163 | } else if (this.kind == Cursor.FieldDecl && this.type.kind == Type.Unexposed) { 164 | //console.error('FieldDecl', this.type.kind, this.type.spelling); 165 | t = wrap.elements[wrap.elements.length - 1]; 166 | /* This may not be entirely correct, we're assuming that because we 167 | * already have elements defined that we're at the name of it, 168 | * consider `union { int foo; } myUnion;` but it's also possible 169 | * we are at an actual definition and we should be defining this type 170 | * 171 | * TODO -- man I need tests. 172 | */ 173 | if (t) { 174 | t.name = this.spelling; 175 | return Cursor.Continue; 176 | } else { 177 | t = defineType(this.type.declaration); 178 | tname = this.spelling; 179 | } 180 | } else { 181 | t = mapType(this.type, this.spelling); 182 | tname = this.spelling; 183 | } 184 | if (!t) { 185 | /* This type has an element we don't know how to map yet, abort */ 186 | wrap.abort = { 187 | name: tname, 188 | }; 189 | //console.error('breaking because of unmapped type', tname, this.kind); 190 | ret = Cursor.Break; 191 | } else { 192 | /* Add the field for the struct */ 193 | wrap.elements.push({ 194 | name: tname, 195 | type: t.kind === 'struct' ? t.name : t.type || t.name, 196 | }); 197 | ret = Cursor.Continue; 198 | } 199 | } else if (this.kind === Cursor.UnexposedDecl || this.kind === Cursor.UnexposedAttr) { 200 | //console.error('unexposed decl', this.type.spelling); 201 | ret = Cursor.Continue; 202 | } else if (this.type.kind == Cursor.BlockExpr) { 203 | //console.error('block expr'); 204 | ret = Cursor.Continue; 205 | } else { 206 | //console.error('breaking because of unknown kind', this.type.kind, this.kind); 207 | wrap.abort = { 208 | kind: this.type.kind, 209 | }; 210 | ret = Cursor.Break; 211 | } 212 | first = false; 213 | return ret; 214 | }); 215 | 216 | /* types should probably contain at least one type, and don't claim to support partially defined types */ 217 | if (!wrap.abort && wrap.elements.length > 0) { 218 | if (!unionName) { 219 | wrap.type = StructTmpl.render(wrap).trim(); 220 | wrap.kind = 'struct'; 221 | toRender.types.push(wrap); 222 | structs[wrap.name] = wrap; 223 | } else { 224 | wrap.type = UnionTmpl.render(wrap).trim(); 225 | wrap.kind = 'union'; 226 | } 227 | return wrap; 228 | } else { 229 | //console.error(wrap); 230 | return undefined; 231 | } 232 | }; 233 | 234 | var mapEnum = function (type, ret) { 235 | var def; 236 | type.visitChildren(function (parent) { 237 | var val; 238 | 239 | if (ret.name[0] === 'u') { 240 | val = this.enumUValue; 241 | } else { 242 | val = this.enumValue; 243 | } 244 | 245 | def = enums[parent.spelling]; 246 | if (!def) { 247 | def = enums[parent.spelling] = {}; 248 | } 249 | 250 | def[this.spelling] = val; 251 | 252 | return Cursor.Continue; 253 | }); 254 | //console.error(def); 255 | }; 256 | 257 | var defineOpaque = function (canonical) { 258 | ret = new WrapType(canonical); 259 | ret.opaque = true; 260 | if (!structs[ret.name]) { 261 | toRender.types.push({ 262 | name: ret.name, 263 | type: OpaqueTmpl.render(ret).trim(), 264 | }); 265 | structs[ret.name] = ret; 266 | } 267 | return ret; 268 | }; 269 | 270 | var defineFunction = function (name, type) { 271 | //console.error('defining function', name); 272 | 273 | var ret = new WrapType(name); 274 | 275 | var result = mapType(type.result, name); 276 | 277 | if (!result) { 278 | //console.error('could not map return type', type.result.spelling); 279 | ret.abort = { 280 | name: name, 281 | arg: type.result.spelling, 282 | position: -1, 283 | kind: type.declaration.spelling, 284 | }; 285 | return ret; 286 | } 287 | 288 | ret.result = result.name; 289 | 290 | var i, arg, a, b; 291 | for (i = 0; i < type.argTypes; i++) { 292 | a = type.getArg(i); 293 | 294 | //console.error('mapping argument', i); 295 | arg = mapType(a, name + '-arg' + i); 296 | if (!arg) { 297 | ret.abort = { 298 | name: name, 299 | displayname: a.declaration.displayname, 300 | arg: a.spelling, 301 | position: i, 302 | }; 303 | return ret; 304 | } 305 | ret.args.push(arg); 306 | } 307 | 308 | return ret; 309 | }; 310 | 311 | /* 312 | Turn the libclang type into ffi type 313 | TODO XXX FIXME -- Still missing array support (but node-ffi is too) 314 | */ 315 | var mapType = function (type, fieldname) { 316 | var ret; 317 | //console.error('mapType', type.spelling, type.kind); 318 | 319 | if (type.kind === Type.Pointer && type.pointeeType.kind === Type.Char_S) { 320 | ret = new WrapType(ffiMapType.CString); 321 | } else { 322 | switch (type.kind) 323 | { 324 | case Type.Typedef: 325 | /* Handle the case where someone has simply redefined an existing type */ 326 | var canonical = type.canonical; 327 | 328 | if (canonical.kind == Type.Pointer && canonical.declaration.kind == Cursor.NoDeclFound && 329 | type.declaration.spelling) { 330 | if (canonical.pointeeType.kind == Type.FunctionProto) { 331 | ret = structs[type.declaration.spelling]; 332 | if (!ret) { 333 | ret = defineFunction(type.declaration.spelling, canonical.pointeeType); 334 | if (!ret.abort) { 335 | toRender.types.push({ 336 | name: ret.name, 337 | type: FuncPtrTmpl.render(ret).trim(), 338 | }); 339 | structs[type.declaration.spelling] = ret; 340 | } else { 341 | unmapped.push(ret.abort); 342 | return undefined; 343 | } 344 | } 345 | return ret; 346 | } else { 347 | ret = defineType(canonical.pointeeType.declaration); 348 | if (ret) 349 | return new WrapType(ret.name + 'Ptr'); 350 | else 351 | return defineOpaque(type.declaration.spelling); 352 | } 353 | } 354 | canonical = mapType(canonical, fieldname); 355 | 356 | if (canonical) 357 | ret = canonical; 358 | else /* If this is a struct try and create */ 359 | ret = defineType(type.declaration); 360 | break; 361 | case Type.Unexposed: 362 | /* Special case enums so we can pass them around as integer type */ 363 | if (type.declaration.kind === Cursor.EnumDecl) { 364 | ret = mapType(type.declaration.enumType, fieldname); 365 | mapEnum(type.declaration, ret); 366 | } else if (type.declaration.kind === Cursor.StructDecl) { 367 | ret = defineType(type.declaration); 368 | } 369 | break; 370 | case Type.Enum: 371 | ret = mapType(type.declaration.enumType, fieldname); 372 | mapEnum(type.declaration, ret); 373 | break; 374 | case Type.Pointer: 375 | if (type.pointeeType.declaration.kind == Cursor.TypedefDecl) 376 | ret = defineType(type.pointeeType.declaration); 377 | else { 378 | ret = ffiMapType(type.pointeeType.canonical.kind); 379 | if (ret) { 380 | // TODO XXX FIXME template work 381 | if (ret != 'ref.types.void') 382 | ret = 'ref.refType('+ ret + ')'; 383 | else 384 | ret = 'voidPtr'; 385 | return new WrapType(ret); 386 | } 387 | } 388 | 389 | if (!ret) { 390 | if (type.pointeeType.declaration.kind == Cursor.TypedefDecl && type.pointeeType.declaration.spelling) { 391 | ret = defineOpaque(type.pointeeType.declaration.spelling); 392 | } else { 393 | ret = { 394 | type: OpaqueTmpl.render({}).trim(), 395 | name: OpaqueTmpl.render({}).trim(), 396 | }; 397 | } 398 | } else { 399 | ret = new WrapType(ret.name + 'Ptr'); 400 | } 401 | break; 402 | case Type.ConstantArray: 403 | ret = mapType(type.elementType, fieldname); 404 | 405 | if (!ret) 406 | ret = defineType(type.elementType.declaration); 407 | 408 | ret.arrSize = type.arraySize; 409 | ret.type = ArrayTmpl.render(ret).trim(); 410 | //console.error(fieldname, type.spelling, type.elementType.spelling, ret); 411 | break; 412 | case Type.Record: 413 | //console.error('mapType Record'); 414 | ret = defineType(type.declaration); 415 | break; 416 | default: 417 | //console.error(type.spelling); 418 | ret = ffiMapType(type.kind); 419 | if (ret) 420 | ret = new WrapType(ret); 421 | assert(type.kind != 0); 422 | break; 423 | } 424 | } 425 | 426 | return ret; 427 | }; 428 | 429 | 430 | function matchPrefix(name) { 431 | var i; 432 | 433 | if (!prefix.length) return true; 434 | 435 | for (i = 0; i < prefix.length; i++) 436 | if (name.indexOf(prefix[i]) == 0) 437 | return true; 438 | 439 | return false; 440 | } 441 | 442 | /* 443 | * Main source traversal -- We're mostly/only? concerned with wrapping functions 444 | * we could theoretically handle types here, but handling it by dependency means 445 | * we don't necessarily work through types we eventually won't care about 446 | */ 447 | curs.visitChildren(function(parent) { 448 | switch (this.kind) 449 | { 450 | case Cursor.FunctionDecl: 451 | if (matchPrefix(this.spelling)) { 452 | if (single_file && path.normalize(this.location.presumedLocation.filename) !== fname) { 453 | return Cursor.Continue; 454 | } 455 | 456 | //var result = mapType(this.resultType, this.spelling); 457 | var result = defineFunction(this.spelling, this.type); 458 | if (result.abort) { 459 | unmapped.push(result.abort); 460 | return Cursor.Continue; 461 | } else { 462 | toRender.functions.push(result); 463 | } 464 | } 465 | break; 466 | } 467 | return Cursor.Continue; 468 | }); 469 | 470 | //tu.dispose(); 471 | idx.dispose(); 472 | 473 | Object.keys(enums).forEach(function (e) { 474 | var ee = { name: e, values: [] }; 475 | Object.keys(enums[e]).forEach(function (vname) { 476 | ee.values.push({ 477 | name: vname, 478 | value: enums[e][vname], 479 | }); 480 | }); 481 | toRender.enums.push(ee); 482 | }); 483 | 484 | return { 485 | unmapped: unmapped, 486 | serialized: TypeTmpl.render(toRender), 487 | }; 488 | }; 489 | 490 | var generateLibClang = function () { 491 | var exec = require('child_process').exec; 492 | 493 | exec('llvm-config --includedir', function (fail, out, err) { 494 | var includedir = out.trim(); 495 | var result = exports.generate({ 496 | filename: path.join(includedir, 'clang-c', 'Index.h'), 497 | includes: [includedir], 498 | library: 'libclang', 499 | prefix: 'clang_', 500 | }); 501 | 502 | if (result.unmapped.length > 0) { 503 | console.log('----- UNMAPPED FUNCTIONS -----'); 504 | console.log(result.unmapped); 505 | console.log('----- UNMAPPED FUNCTIONS -----'); 506 | } 507 | 508 | var jsb = require('js-beautify'); 509 | require('fs').writeFileSync(path.join(__dirname, 'newclang.js'), jsb.js_beautify(result.serialized)); 510 | var dynamic_clang = require(path.join(__dirname, 'newclang')); 511 | var ver = dynamic_clang.libclang.clang_getClangVersion(); 512 | console.log(dynamic_clang.libclang.clang_getCString(ver)); 513 | dynamic_clang.libclang.clang_disposeString(ver) 514 | }); 515 | } 516 | 517 | if (require.main === module) { 518 | generateLibClang(); 519 | } 520 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ffi-generate", 3 | "version": "0.0.7", 4 | "description": "Generate FFI Bindings from header file", 5 | "keywords": ["libclang", "ffi", "bindings"], 6 | "author": "Timothy J Fontaine ", 7 | "main": "lib/generateffi.js", 8 | "bin": { 9 | "ffi-generate": "bin/ffi-generate.js" 10 | }, 11 | "dependencies": { 12 | "hogan.js": ">= 2.0.0", 13 | "js-beautify": "", 14 | "libclang": ">= 0.0.10", 15 | "optimist": "" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /templates/ffi/array.mustache: -------------------------------------------------------------------------------- 1 | ArrayType({{name}}, {{arrSize}}) 2 | -------------------------------------------------------------------------------- /templates/ffi/ffi.mustache: -------------------------------------------------------------------------------- 1 | var FFI = require('ffi'), 2 | ArrayType = require('ref-array'), 3 | Struct = require('ref-struct'), 4 | Union = require('ref-union'), 5 | ref = require('ref'); 6 | 7 | var voidPtr = ref.refType(ref.types.void); 8 | 9 | exports.CONSTANTS = { 10 | {{#enums}} 11 | '{{name}}': { 12 | {{#values}} 13 | {{name}}: {{value}}, 14 | {{/values}} 15 | {{#values}} 16 | '{{value}}': '{{name}}', 17 | {{/values}} 18 | }, 19 | {{/enums}} 20 | }; 21 | 22 | {{#types}} 23 | var {{name}} = exports.{{name}} = {{type}}; 24 | var {{name}}Ptr = exports.{{name}}Ptr = ref.refType({{name}}); 25 | {{/types}} 26 | 27 | exports.{{module}} = new FFI.Library('{{library}}', { 28 | {{#functions}} 29 | {{name}}: [{{result}}, [ 30 | {{#args}} 31 | {{name}}, 32 | {{/args}} 33 | ]], 34 | {{/functions}} 35 | }); 36 | -------------------------------------------------------------------------------- /templates/ffi/funcptr.mustache: -------------------------------------------------------------------------------- 1 | FFI.Function({{result}}, [ 2 | {{#args}} 3 | {{name}}, 4 | {{/args}} 5 | ]) 6 | -------------------------------------------------------------------------------- /templates/ffi/opaque.mustache: -------------------------------------------------------------------------------- 1 | voidPtr 2 | -------------------------------------------------------------------------------- /templates/ffi/struct.mustache: -------------------------------------------------------------------------------- 1 | Struct({ 2 | {{#elements}} 3 | {{name}}: {{type}}, 4 | {{/elements}} 5 | }) 6 | -------------------------------------------------------------------------------- /templates/ffi/union.mustache: -------------------------------------------------------------------------------- 1 | Union({ 2 | {{#elements}} 3 | {{name}}: {{type}}, 4 | {{/elements}} 5 | }) 6 | --------------------------------------------------------------------------------