-
Notifications
You must be signed in to change notification settings - Fork 5
/
HaxeCBridge.hx
1942 lines (1656 loc) · 64.8 KB
/
HaxeCBridge.hx
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
/**
HaxeCBridge
HaxeCBridge is a @:build macro that enables calling haxe code from C by exposing classes via an automatically generated C header.
Works with the hxcpp target and requires haxe 4.0 or newer
@author George Corney (haxiomic)
@license MIT
**Usage**
Haxe-side:
- Add `@:build(HaxeCBridge.expose())` to classes you want to expose to C (you can add this to as many classes as you like – all functions are combined into a single header file)
- The first argument of expose() sets generated C function name prefix: `expose('Example')` or `expose('')` for no prefix
- Add `-D dll_link` or `-D static_link` to compile your haxe program into a native library binary
- HaxeCBridge will then generate a header file in your build output directory named after your `--main` class (however a `--main` class is not required to use HaxeCBridge)
- Change the generated library name by adding `-D HaxeCBridge.name=YourLibName` to your hxml
C-side:
- Include the generated header and link with the hxcpp generated library binary
- Before calling any haxe functions you must start the haxe thread: call `YourLibName_initializeHaxeThread(onHaxeException)`
- Now interact with your haxe library thread by calling the exposed functions
- When your program exits call `YourLibName_stopHaxeThread(true)`
**/
#if (haxe_ver < 4.0) #error "Haxe 4.0 required" #end
#if macro
// fast path for when code gen isn't required
// disable this to get auto-complete when editing this file
#if (display || display_details || target.name != cpp || cppia)
class HaxeCBridge {
public static function expose(?namespace: String)
return haxe.macro.Context.getBuildFields();
@:noCompletion
static macro function runUserMain()
return macro null;
}
#else
import HaxeCBridge.CodeTools.*;
import haxe.ds.ReadOnlyArray;
import haxe.io.Path;
import haxe.macro.Compiler;
import haxe.macro.ComplexTypeTools;
import haxe.macro.Context;
import haxe.macro.Expr;
import haxe.macro.ExprTools;
import haxe.macro.PositionTools;
import haxe.macro.Printer;
import haxe.macro.Type;
import haxe.macro.TypeTools;
import haxe.macro.TypedExprTools;
import sys.FileSystem;
import sys.io.File;
using Lambda;
using StringTools;
class HaxeCBridge {
static final noOutput = Sys.args().has('--no-output');
static final printer = new Printer();
static var firstRun = true;
static var libName: Null<String> = getLibNameFromHaxeArgs(); // null if no libName determined from args
static final compilerOutputDir = Compiler.getOutput();
// paths relative to the compiler output directory
static final implementationPath = Path.join(['src', '__HaxeCBridgeBindings__.cpp']);
static final queuedClasses = new Array<{
cls: Ref<ClassType>,
namespace: String,
}>();
// conversion state
static final functionInfo = new Map<String, {
kind: FunctionInfoKind,
hxcppClass: String,
hxcppFunctionName: String,
field: ClassField,
tfunc: TFunc,
rootCTypes: {
args: Array<CType>,
ret: CType
},
pos: Position,
}>();
static public function expose(?namespace: String) {
var clsRef = Context.getLocalClass();
var cls = clsRef.get();
var fields = Context.getBuildFields();
if (libName == null) {
// if we cannot determine a libName from --main or -D, we use the first exposed class
libName = if (namespace != null) {
namespace;
} else {
cls.name;
}
}
queuedClasses.push({
cls: clsRef,
namespace: namespace
});
// add @:keep
cls.meta.add(':keep', [], Context.currentPos());
if (firstRun) {
final headerPath = Path.join(['$libName.h']);
// resolve runtime HaxeCBridge class to make sure it's generated
// add @:buildXml to include generated code
var HaxeCBridgeType = Context.resolveType(macro :HaxeCBridge, Context.currentPos());
switch HaxeCBridgeType {
case TInst(_.get().meta => meta, params):
if (!meta.has(':buildXml')) {
meta.add(':buildXml', [
macro $v{code('
<!-- HaxeCBridge -->
<files id="haxe">
<file name="$implementationPath">
<depend name="$headerPath"/>
</file>
</files>
')}
], Context.currentPos());
}
default: throw 'Internal error';
}
Context.onAfterTyping(_ -> {
final cConversionContext = new CConverterContext({
declarationPrefix: libName,
generateTypedef: true,
generateTypedefWithTypeParameters: false,
});
for (item in queuedClasses) {
convertQueuedClass(libName, cConversionContext, item.cls, item.namespace);
}
var header = generateHeader(cConversionContext, libName);
var implementation = generateImplementation(cConversionContext, libName);
function saveFile(path: String, content: String) {
var directory = Path.directory(path);
if (!FileSystem.exists(directory)) {
FileSystem.createDirectory(directory);
}
// only save if there's a difference (save C++ compilation by not changing the file if not needed)
if (FileSystem.exists(path)) {
if (content == sys.io.File.getContent(path)) {
return;
}
}
sys.io.File.saveContent(path, content);
}
if (!noOutput) {
saveFile(Path.join([compilerOutputDir, headerPath]), header);
saveFile(Path.join([compilerOutputDir, implementationPath]), implementation);
}
});
firstRun = false;
}
return fields;
}
static function getHxcppNativeName(t: BaseType) {
var nativeMeta = t.meta.extract(':native')[0];
var nativeMetaValue = nativeMeta != null ? ExprTools.getValue(nativeMeta.params[0]) : null;
var nativeName = (nativeMetaValue != null ? nativeMetaValue : t.pack.concat([t.name]).join('.'));
return nativeName;
}
static function convertQueuedClass(libName: String, cConversionContext: CConverterContext, clsRef: Ref<ClassType>, namespace: String) {
var cls = clsRef.get();
// validate
if (cls.isInterface) Context.error('Cannot expose interface to C', cls.pos);
if (cls.isExtern) Context.error('Cannot expose extern directly to C', cls.pos);
// determine the name of the class as generated by hxcpp
var nativeName = getHxcppNativeName(cls);
var isNativeGen = cls.meta.has(':nativeGen');
var nativeHxcppName = nativeName + (isNativeGen ? '' : '_obj');
// determine the hxcpp generated header path for this class
var typeHeaderPath = nativeName.split('.');
cConversionContext.requireImplementationHeader(Path.join(typeHeaderPath) + '.h', false);
// prefix all functions with lib name and class path
var classPrefix = cls.pack.concat([namespace == null ? cls.name : namespace]);
var cNameMeta = getCNameMeta(cls.meta);
var functionPrefix =
if (cNameMeta != null)
[cNameMeta];
else
[libName]
.concat(safeIdent(classPrefix.join('.')) != libName ? classPrefix : [])
.filter(s -> s != '');
function convertFunction(f: ClassField, kind: FunctionInfoKind) {
var isConvertibleMethod = f.isPublic && !f.isExtern && switch f.kind {
case FVar(_), FMethod(MethMacro): false; // skip macro methods
case FMethod(_): true;
}
if (!isConvertibleMethod) return;
// f is public static function
var fieldExpr = f.expr();
switch fieldExpr.expr {
case TFunction(tfunc):
// we have to tweak the descriptor for instance constructors and members
var functionDescriptor: TFunc = switch kind {
case Constructor: {
args: tfunc.args,
expr: tfunc.expr,
t: TInst(clsRef, []), // return a instance of this class
}
case Member:
var instanceTArg: TVar = {id: -1, name: 'instance', t: TInst(clsRef, []), meta: null, capture: false, extra: null};
{
args: [{v: instanceTArg, value: null}].concat(tfunc.args),
expr: tfunc.expr,
t: tfunc.t,
}
case Static: tfunc;
}
// add C function declaration
var cNameMeta = getCNameMeta(f.meta);
var cFuncName: String =
if (cNameMeta != null)
cNameMeta;
else
functionPrefix.concat([f.name]).join('_');
var cleanDoc = f.doc != null ? StringTools.trim(removeIndentation(f.doc)) : null;
cConversionContext.addTypedFunctionDeclaration(cFuncName, functionDescriptor, cleanDoc, f.pos);
inline function getRootCType(t: Type) {
var tmpCtx = new CConverterContext({generateTypedef: false, generateTypedefForFunctions: false, generateEnums: true});
return tmpCtx.convertType(t, true, true, f.pos);
}
var hxcppClass = nativeHxcppName.split('.').join('::');
// store useful information about this function that we can use when generating the implementation
functionInfo.set(cFuncName, {
kind: kind,
hxcppClass: nativeName.split('.').join('::'),
hxcppFunctionName: hxcppClass + '::' + switch kind {
case Constructor: '__new';
case Static | Member: f.name;
},
field: f,
tfunc: tfunc,
rootCTypes: {
args: functionDescriptor.args.map(a -> getRootCType(a.v.t)),
ret: getRootCType(functionDescriptor.t)
},
pos: f.pos
});
default: Context.fatalError('Internal error: Expected function expression', f.pos);
}
}
if (cls.constructor != null) {
convertFunction(cls.constructor.get(), Constructor);
}
for (f in cls.fields.get()) {
convertFunction(f, Member);
}
for (f in cls.statics.get()) {
convertFunction(f, Static);
}
}
static macro function runUserMain() {
var mainClassPath = getMainFromHaxeArgs(Sys.args());
if (mainClassPath == null) {
return macro null;
} else {
return Context.parse('$mainClassPath.main()', Context.currentPos());
}
}
static function isLibraryBuild() {
return Context.defined('dll_link') || Context.defined('static_link');
}
static function isDynamicLink() {
return Context.defined('dll_link');
}
static function getCNameMeta(meta: MetaAccess): Null<String> {
var cNameMeta = meta.extract('HaxeCBridge.name')[0];
return if (cNameMeta != null) {
switch cNameMeta.params {
case [{expr: EConst(CString(name))}]:
safeIdent(name);
default:
Context.error('Incorrect usage, syntax is @${cNameMeta.name}(name: String)', cNameMeta.pos);
}
} else null;
}
static function generateHeader(ctx: CConverterContext, namespace: String) {
ctx.requireHeader('stdbool.h', false); // we use bool for _stopHaxeThread()
var includes = ctx.includes.copy();
// sort includes, by <, " and alphabetically
includes.sort((a, b) -> {
var i = (a.quoted ? 1 : -1);
var j = (b.quoted ? 1 : -1);
return if (i == j) {
a.path > b.path ? 1 : -1;
} else i - j;
});
var prefix = isDynamicLink() ? 'API_PREFIX' : '';
return code('
/**
* $namespace.h
* ${isLibraryBuild() ?
'Automatically generated by HaxeCBridge' :
'! Warning, binary not generated as a library, make sure to add `-D dll_link` or `-D static_link` when compiling the haxe project !'
}
*/
#ifndef HaxeCBridge_${namespace}_h
#define HaxeCBridge_${namespace}_h
')
+ (if (includes.length > 0) includes.map(CPrinter.printInclude).join('\n') + '\n\n'; else '')
+ (if (ctx.macros.length > 0) ctx.macros.join('\n') + '\n' else '')
+ (if (isDynamicLink()) {
code('
#ifndef API_PREFIX
#ifdef _WIN32
#define API_PREFIX __declspec(dllimport)
#else
#define API_PREFIX
#endif
#endif
');
} else '')
+ 'typedef void (* HaxeExceptionCallback) (const char* exceptionInfo);\n'
+ (if (ctx.supportTypeDeclarations.length > 0) ctx.supportTypeDeclarations.map(d -> CPrinter.printDeclaration(d, true)).join(';\n') + ';\n\n'; else '')
+ (if (ctx.typeDeclarations.length > 0) ctx.typeDeclarations.map(d -> CPrinter.printDeclaration(d, true)).join(';\n') + ';\n'; else '')
+ code('
#ifdef __cplusplus
extern "C" {
#endif
/**
* Initializes a haxe thread that executes the haxe main() function remains alive indefinitely until told to stop.
*
* This must be first before calling haxe functions (otherwise those calls will hang waiting for a response from the haxe thread).
*
* @param unhandledExceptionCallback a callback to execute if an unhandled exception occurs on the haxe thread. The haxe thread will continue processing events after an unhandled exception and you may want to stop it after receiving this callback. Use `NULL` for no callback
* @returns `NULL` if the thread initializes successfully or a null-terminated C string if an error occurs during initialization
*/
$prefix const char* ${namespace}_initializeHaxeThread(HaxeExceptionCallback unhandledExceptionCallback);
/**
* Stops the haxe thread, blocking until the thread has completed. Once ended, it cannot be restarted (this is because static variable state will be retained from the last run).
*
* Other threads spawned from the haxe thread may still be running (you must arrange to stop these yourself for safe app shutdown).
*
* It can be safely called any number of times – if the haxe thread is not running this function will just return.
*
* After executing no more calls to main-thread haxe functions can be made (as these will hang waiting for a response from the main thread).
*
* Thread-safety: Can be called safely called on any thread. If called on the haxe thread it will trigger the thread to stop but it cannot then block until stopped.
*
* @param waitOnScheduledEvents If `true`, this function will wait for all events scheduled to execute in the future on the haxe thread to complete – this is the same behavior as running a normal hxcpp program. If `false`, immediate pending events will be finished and the thread stopped without executing events scheduled in the future
*/
$prefix void ${namespace}_stopHaxeThreadIfRunning(bool waitOnScheduledEvents);
')
+ indent(1, ctx.supportFunctionDeclarations.map(fn -> CPrinter.printDeclaration(fn, true, prefix)).join(';\n\n') + ';\n\n')
+ indent(1, ctx.functionDeclarations.map(fn -> CPrinter.printDeclaration(fn, true, prefix)).join(';\n\n') + ';\n\n')
+ code('
#ifdef __cplusplus
}
#endif
#undef API_PREFIX
#endif /* HaxeCBridge_${namespace}_h */
');
}
static function generateImplementation(ctx: CConverterContext, namespace: String) {
return code('
/**
* HaxeCBridge Function Binding Implementation
* Automatically generated by HaxeCBridge
*/
#include <hxcpp.h>
#include <hx/Native.h>
#include <hx/Thread.h>
#include <hx/StdLibs.h>
#include <hx/GC.h>
#include <HaxeCBridge.h>
#include <assert.h>
#include <queue>
#include <utility>
#include <atomic>
// include generated bindings header
')
+ (if (isDynamicLink()) code('
// set prefix when exporting dll symbols on windows
#ifdef _WIN32
#define API_PREFIX __declspec(dllexport)
#endif
')
else
''
)
+ code('
#include "../${namespace}.h"
#define HAXE_C_BRIDGE_LINKAGE HXCPP_EXTERN_CLASS_ATTRIBUTES
')
+ ctx.implementationIncludes.map(CPrinter.printInclude).join('\n') + '\n'
+ code('
namespace HaxeCBridgeInternal {
// we cannot use hxcpps HxCreateDetachedThread() because we cannot wait on these threads to end on unix because they are detached threads
#if defined(HX_WINDOWS)
HANDLE haxeThreadNativeHandle = nullptr;
DWORD haxeThreadNativeId = 0; // 0 is not valid thread id
bool createHaxeThread(DWORD (WINAPI *func)(void *), void *param) {
haxeThreadNativeHandle = CreateThread(NULL, 0, func, param, 0, &haxeThreadNativeId);
return haxeThreadNativeHandle != 0;
}
bool waitForThreadExit(HANDLE handle) {
DWORD result = WaitForSingleObject(handle, INFINITE);
return result != WAIT_FAILED;
}
#else
pthread_t haxeThreadNativeHandle;
bool createHaxeThread(void *(*func)(void *), void *param) {
// same as HxCreateDetachedThread(func, param) but without detaching the thread
pthread_attr_t attr;
if (pthread_attr_init(&attr) != 0)
return false;
if (pthread_create(&haxeThreadNativeHandle, &attr, func, param) != 0 )
return false;
if (pthread_attr_destroy(&attr) != 0)
return false;
return true;
}
bool waitForThreadExit(pthread_t handle) {
int result = pthread_join(handle, NULL);
return result == 0;
}
#endif
std::atomic<bool> threadStarted = { false };
std::atomic<bool> threadRunning = { false };
// once haxe statics are initialized we cannot clear them for a clean restart
std::atomic<bool> staticsInitialized = { false };
struct HaxeThreadData {
HaxeExceptionCallback haxeExceptionCallback;
const char* initExceptionInfo;
};
HxSemaphore threadInitSemaphore;
HxMutex threadManageMutex;
void defaultExceptionHandler(const char* info) {
printf("Unhandled haxe exception: %s\\n", info);
}
typedef void (* MainThreadCallback)(void* data);
HxMutex queueMutex;
std::queue<std::pair<MainThreadCallback, void*>> queue;
void runInMainThread(MainThreadCallback callback, void* data) {
queueMutex.Lock();
queue.push(std::make_pair(callback, data));
queueMutex.Unlock();
HaxeCBridge::wakeMainThread();
}
// called on the haxe main thread
void processNativeCalls() {
AutoLock lock(queueMutex);
while(!queue.empty()) {
std::pair<MainThreadCallback, void*> pair = queue.front();
queue.pop();
pair.first(pair.second);
}
}
#if defined(HX_WINDOWS)
bool isHaxeMainThread() {
return threadRunning &&
(GetCurrentThreadId() == haxeThreadNativeId) &&
(haxeThreadNativeId != 0);
}
#else
bool isHaxeMainThread() {
return threadRunning && pthread_equal(haxeThreadNativeHandle, pthread_self());
}
#endif
}
THREAD_FUNC_TYPE haxeMainThreadFunc(void *data) {
HX_TOP_OF_STACK
HaxeCBridgeInternal::HaxeThreadData* threadData = (HaxeCBridgeInternal::HaxeThreadData*) data;
HaxeCBridgeInternal::threadRunning = true;
threadData->initExceptionInfo = nullptr;
// copy out callback
HaxeExceptionCallback haxeExceptionCallback = threadData->haxeExceptionCallback;
bool firstRun = !HaxeCBridgeInternal::staticsInitialized;
// See hx::Init in StdLibs.cpp for reference
if (!HaxeCBridgeInternal::staticsInitialized) try {
::hx::Boot();
__boot_all();
HaxeCBridgeInternal::staticsInitialized = true;
} catch(Dynamic initException) {
// hxcpp init failure or uncaught haxe runtime exception
threadData->initExceptionInfo = initException->toString().utf8_str();
}
if (HaxeCBridgeInternal::staticsInitialized) { // initialized without error
// blocks running the event loop
// keeps alive until manual stop is called
HaxeCBridge::mainThreadInit(HaxeCBridgeInternal::isHaxeMainThread);
HaxeCBridgeInternal::threadInitSemaphore.Set();
HaxeCBridge::mainThreadRun(HaxeCBridgeInternal::processNativeCalls, haxeExceptionCallback);
} else {
// failed to initialize statics; unlock init semaphore so _initializeHaxeThread can continue and report the exception
HaxeCBridgeInternal::threadInitSemaphore.Set();
}
HaxeCBridgeInternal::threadRunning = false;
THREAD_FUNC_RET
}
HAXE_C_BRIDGE_LINKAGE
const char* ${namespace}_initializeHaxeThread(HaxeExceptionCallback unhandledExceptionCallback) {
HaxeCBridgeInternal::HaxeThreadData threadData;
threadData.haxeExceptionCallback = unhandledExceptionCallback == nullptr ? HaxeCBridgeInternal::defaultExceptionHandler : unhandledExceptionCallback;
threadData.initExceptionInfo = nullptr;
{
// mutex prevents two threads calling this function from being able to start two haxe threads
AutoLock lock(HaxeCBridgeInternal::threadManageMutex);
if (!HaxeCBridgeInternal::threadStarted) {
// startup the haxe main thread
HaxeCBridgeInternal::createHaxeThread(haxeMainThreadFunc, &threadData);
HaxeCBridgeInternal::threadStarted = true;
// wait until the thread is initialized and ready
HaxeCBridgeInternal::threadInitSemaphore.Wait();
} else {
threadData.initExceptionInfo = "haxe thread cannot be started twice";
}
}
if (threadData.initExceptionInfo != nullptr) {
${namespace}_stopHaxeThreadIfRunning(false);
const int returnInfoMax = 1024;
static char returnInfo[returnInfoMax] = ""; // statically allocated for return safety
strncpy(returnInfo, threadData.initExceptionInfo, returnInfoMax);
return returnInfo;
} else {
return nullptr;
}
}
HAXE_C_BRIDGE_LINKAGE
void ${namespace}_stopHaxeThreadIfRunning(bool waitOnScheduledEvents) {
if (HaxeCBridgeInternal::isHaxeMainThread()) {
// it is possible for stopHaxeThread to be called from within the haxe thread, while another thread is waiting on for the thread to end
// so it is important the haxe thread does not wait on certain locks
HaxeCBridge::endMainThread(waitOnScheduledEvents);
} else {
AutoLock lock(HaxeCBridgeInternal::threadManageMutex);
if (HaxeCBridgeInternal::threadRunning) {
struct Callback {
static void run(void* data) {
bool* b = (bool*) data;
HaxeCBridge::endMainThread(*b);
}
};
HaxeCBridgeInternal::runInMainThread(Callback::run, &waitOnScheduledEvents);
HaxeCBridgeInternal::waitForThreadExit(HaxeCBridgeInternal::haxeThreadNativeHandle);
}
}
}
HAXE_C_BRIDGE_LINKAGE
void ${namespace}_releaseHaxeObject(void* objPtr) {
struct Callback {
static void run(void* data) {
HaxeCBridge::releaseHaxePtr(data);
}
};
HaxeCBridgeInternal::runInMainThread(Callback::run, objPtr);
}
HAXE_C_BRIDGE_LINKAGE
void ${namespace}_releaseHaxeString(const char* strPtr) {
// we use the same release call for all haxe pointers
${namespace}_releaseHaxeObject((void*) strPtr);
}
')
+ ctx.functionDeclarations.map(d -> generateFunctionImplementation(namespace, d)).join('\n') + '\n'
;
}
static function generateFunctionImplementation(namespace: String, d: CDeclaration) {
var signature = switch d.kind {case Function(sig): sig; default: null;};
var haxeFunction = functionInfo.get(signature.name);
var hasReturnValue = !haxeFunction.rootCTypes.ret.match(Ident('void'));
var externalThread = haxeFunction.field.meta.has('externalThread');
// rename signature args to a1, a2, a3 etc, this is to avoid possible conflict with local function variables
var signature: CFunctionSignature = {
name: signature.name,
args: signature.args.mapi((i, arg) -> {name: 'a$i', type: arg.type}),
ret: signature.ret,
}
var d: CDeclaration = { kind: Function(signature) }
// cast a C type to one which works with hxcpp
inline function castC2Cpp(expr: String, rootCType: CType) {
// type cast argument before passing to hxcpp
return switch rootCType {
case Enum(_): expr; // enum to int works with implicit cast
case Ident('HaxeObject'): 'Dynamic((hx::Object *)$expr)'; // Dynamic cast requires including the hxcpp header of the type
case Ident(_), FunctionPointer(_), InlineStruct(_), Pointer(_): expr; // hxcpp auto casting works
}
}
inline function castCpp2C(expr: String, cType: CType, rootCType: CType) {
// cast hxcpp type to c
return switch rootCType {
case Enum(_): 'static_cast<${CPrinter.printType(cType)}>($expr)'; // need explicit cast for int -> enum
case Ident('HaxeObject'): 'HaxeCBridge::retainHaxeObject($expr)'; // Dynamic cast requires including the hxcpp header of the type
case Ident('HaxeString'): 'HaxeCBridge::retainHaxeString($expr)'; // ensure string is held by the GC (until manual release)
case Ident(_), FunctionPointer(_), InlineStruct(_), Pointer(_): expr; // hxcpp auto casting works
}
}
inline function callWithArgs(argNames: Array<String>) {
var callExpr = switch haxeFunction.kind {
case Constructor | Static:
'${haxeFunction.hxcppFunctionName}(${argNames.mapi((i, arg) -> castC2Cpp(arg, haxeFunction.rootCTypes.args[i])).join(', ')})';
case Member:
var a0Name = argNames[0];
var argNames = argNames.slice(1);
var argCTypes = haxeFunction.rootCTypes.args.slice(1);
'(${haxeFunction.hxcppClass}((hx::Object *)$a0Name, true))->${haxeFunction.field.name}(${argNames.mapi((i, arg) -> castC2Cpp(arg, argCTypes[i])).join(', ')})';
}
return if (hasReturnValue) {
castCpp2C(callExpr, signature.ret, haxeFunction.rootCTypes.ret);
} else {
callExpr;
}
}
if (externalThread) {
// straight call through
return (
code('
HAXE_C_BRIDGE_LINKAGE
${CPrinter.printDeclaration(d, false)} {
hx::NativeAttach autoAttach;
return ${callWithArgs(signature.args.map(a->a.name))};
}
')
);
} else {
// main thread synchronization implementation
var fnDataTypeName = 'Data';
var fnDataName = 'data';
var fnDataStruct: CStruct = {
fields: [
{
name: 'args',
type: InlineStruct({fields: signature.args})
},
{
name: 'lock',
type: Ident('HxSemaphore')
}
].concat(
hasReturnValue ? [{
name: 'ret',
type: signature.ret
}] : []
)
};
var fnDataDeclaration: CDeclaration = { kind: Struct(fnDataTypeName, fnDataStruct) }
return (
code('
HAXE_C_BRIDGE_LINKAGE
')
+ CPrinter.printDeclaration(d, false) + ' {\n'
+ indent(1,
code('
if (HaxeCBridgeInternal::isHaxeMainThread()) {
return ${callWithArgs(signature.args.map(a->a.name))};
}
')
+ CPrinter.printDeclaration(fnDataDeclaration) + ';\n'
+ code('
struct Callback {
static void run(void* p) {
// executed within the haxe main thread
$fnDataTypeName* $fnDataName = ($fnDataTypeName*) p;
try {
${hasReturnValue ?
'$fnDataName->ret = ${callWithArgs(signature.args.map(a->'$fnDataName->args.${a.name}'))};' :
'${callWithArgs(signature.args.map(a->'$fnDataName->args.${a.name}'))};'
}
$fnDataName->lock.Set();
} catch(Dynamic runtimeException) {
$fnDataName->lock.Set();
throw runtimeException;
}
}
};
#ifdef HXCPP_DEBUG
assert(HaxeCBridgeInternal::threadRunning && "haxe thread not running, use ${namespace}_initializeHaxeThread() to activate the haxe thread");
#endif
$fnDataTypeName $fnDataName = { {${signature.args.map(a->a.name).join(', ')}} };
// queue a callback to execute ${haxeFunction.field.name}() on the main thread and wait until execution completes
HaxeCBridgeInternal::runInMainThread(Callback::run, &$fnDataName);
$fnDataName.lock.Wait();
')
+ if (hasReturnValue) code('
return $fnDataName.ret;
') else ''
)
+ code('
}
')
);
}
}
/**
We determine a project name to be the `--main` startup class
The user can override this with `-D HaxeCBridge.name=ExampleName`
This isn't rigorously defined but hopefully will produced nicely namespaced and unsurprising function names
**/
static function getLibNameFromHaxeArgs(): Null<String> {
var overrideName = Context.definedValue('HaxeCBridge.name');
if (overrideName != null && overrideName != '') {
return safeIdent(overrideName);
}
var args = Sys.args();
var mainClassPath = getMainFromHaxeArgs(args);
if (mainClassPath != null) {
return safeIdent(mainClassPath);
}
// no lib name indicator found in args
return null;
}
static function getMainFromHaxeArgs(args: Array<String>): Null<String> {
for (i in 0...args.length) {
var arg = args[i];
switch arg {
case '-m', '-main', '--main':
var classPath = args[i + 1];
return classPath;
default:
}
}
return null;
}
static function safeIdent(str: String) {
// replace non a-z0-9_ with _
str = ~/[^\w]/gi.replace(str, '_');
// replace leading number with _
str = ~/^[^a-z_]/i.replace(str, '_');
// replace empty string with _
str = str == '' ? '_' : str;
return str;
}
}
enum FunctionInfoKind {
Constructor;
Member;
Static;
}
enum CModifier {
Const;
}
enum CType {
Ident(name: String, ?modifiers: Array<CModifier>);
Pointer(t: CType, ?modifiers: Array<CModifier>);
FunctionPointer(name: String, argTypes: Array<CType>, ret: CType, ?modifiers: Array<CModifier>);
InlineStruct(struct: CStruct);
Enum(name: String);
}
// not exactly specification C but good enough for this purpose
enum CDeclarationKind {
Typedef(type: CType, declarators: Array<String>);
Enum(name: String, fields: Array<{name: String, ?value: Int}>);
Function(fun: CFunctionSignature);
Struct(name: String, struct: CStruct);
Variable(name: String, type: CType);
}
enum CCustomMeta {
CppFunction(str: String);
}
typedef CDeclaration = {
kind: CDeclarationKind,
?doc: String,
}
typedef CStruct = {
fields: Array<{name: String, type: CType}>
}
typedef CFunctionSignature = {
name: String,
args: Array<{name: String, type: CType}>,
ret: CType
}
typedef CInclude = {
path: String,
quoted: Bool,
}
typedef CMacro = {
directive: String,
name: String,
content: String,
}
class CPrinter {
public static function printInclude(inc: CInclude) {
return '#include ${inc.quoted ? '"' : '<'}${inc.path}${inc.quoted ? '"' : '>'}';
}
public static function printMacro(cMacro: CMacro) {
var escapedContent = cMacro.content.replace('\n', '\n\\');
return '#${cMacro.directive} ${cMacro.name} ${escapedContent}';
}
public static function printType(cType: CType): String {
return switch cType {
case Ident(name, modifiers):
(hasModifiers(modifiers) ? (printModifiers(modifiers) + ' ') : '') + name;
case Pointer(t, modifiers):
printType(t) + '*' + (hasModifiers(modifiers) ? (' ' + printModifiers(modifiers)) : '');
case FunctionPointer(name, argTypes, ret):
'${printType(ret)} (* $name) (${argTypes.length > 0 ? argTypes.map(printType).join(', ') : 'void'})';
case InlineStruct(struct):
'struct {${printFields(struct.fields, false)}}';
case Enum(name):
'enum $name';
}
}
public static function printDeclaration(cDeclaration: CDeclaration, docComment: Bool = true, qualifier: String = '') {
return
(cDeclaration.doc != null && docComment ? (printDoc(cDeclaration.doc) + '\n') : '')
+ (qualifier != '' ? (qualifier + ' ') : '')
+ switch cDeclaration.kind {
case Typedef(type, declarators):
'typedef ${printType(type)}' + (declarators.length > 0 ? ' ${declarators.join(', ')}' :'');
case Enum(name, fields):
'enum $name {\n'
+ fields.map(f -> '\t' + f.name + (f.value != null ? ' = ${f.value}' : '')).join(',\n') + '\n'
+ '}';
case Struct(name, {fields: fields}):
'struct $name {\n'
+ printFields(fields, true)
+ '}';
case Function(sig):
printFunctionSignature(sig);
case Variable(name, type):
'${printType} $name';
}
}
public static function printFields(fields: Array<{name: String, type: CType}>, newlines: Bool) {
var sep = (newlines?'\n':' ');
return fields.map(f -> '${newlines?'\t':''}${printField(f)}').join(sep) + (newlines?'\n':'');
}
public static function printField(f: {name: String, type: CType}) {
return '${printType(f.type)} ${f.name};';
}
public static function printFunctionSignature(signature: CFunctionSignature) {
var name = signature.name;
var args = signature.args;
var ret = signature.ret;
return '${printType(ret)} $name(${args.map(arg -> '${printType(arg.type)} ${arg.name}').join(', ')})';
}
public static function printDoc(doc: String) {
return '/**\n${doc.split('\n').map(l -> ' * ' + l).join('\n')}\n */';
}
static function hasModifiers(modifiers: Null<Array<CModifier>>)
return modifiers != null && modifiers.length > 0;
public static function printModifiers(modifiers: Null<Array<CModifier>>) {
return if (hasModifiers(modifiers)) modifiers.map(printModifier).join('\n');
else '';
}
public static function printModifier(modifier: CModifier) {
return switch modifier {
case Const: 'const';
}
}
}
class CConverterContext {
public final includes = new Array<CInclude>();
public final implementationIncludes = new Array<CInclude>();
public final macros = new Array<String>();
public final supportTypeDeclarations = new Array<CDeclaration>();
final supportDeclaredTypeIdentifiers = new Map<String, Bool>();
public final supportFunctionDeclarations = new Array<CDeclaration>();
final supportDeclaredFunctionIdentifiers = new Map<String, Position>();
public final typeDeclarations = new Array<CDeclaration>();
final declaredTypeIdentifiers = new Map<String, Bool>();
public final functionDeclarations = new Array<CDeclaration>();
final declaredFunctionIdentifiers = new Map<String, Position>();