1 /**
2    Necessary boilerplate for C#/.NET.
3 
4    To wrap all functions/return/parameter types and struct/class definitions from
5    a list of modules, write this in a "main" module and generate mylib.{so,dll}:
6 
7    ------
8    mixin wrapCSharp("mylib", Modules("module1", "module2", ...));
9    ------
10  */
11 module autowrap.csharp.boilerplate;
12 
13 import autowrap.common;
14 import autowrap.reflection;
15 import autowrap.csharp;
16 
17 import core.time;
18 import core.memory;
19 import std.conv;
20 import std.datetime : DateTime, SysTime, Date, TimeOfDay, SimpleTimeZone, LocalTime;
21 import std.traits : Unqual;
22 import std.string;
23 import std.traits;
24 import std.utf;
25 
26 private void pinInternalPointers(T)(ref T value) @trusted nothrow
27     if(is(T == struct))
28 {
29     import std.traits : Fields, isPointer, isDynamicArray, isStaticArray;
30 
31     static foreach(i, Field; Fields!T)
32     {
33         static assert(!isPointer!T, "If we're now supporting pointers, this code needs to be updated");
34         static assert(!isStaticArray!T, "If we're now supporting static arrays, this code needs to be updated");
35 
36         static if(is(Field == struct))
37             pinInternalPointers(value.tupleof[i]);
38         else static if(isDynamicArray!Field)
39         {
40             if(value.tupleof[i] !is null)
41                 pinPointer(cast(void*)value.tupleof[i].ptr);
42         }
43         else static if(is(Field == class) || is(Field == interface))
44         {
45             if(value.tupleof[i] !is null)
46                 pinPointer(cast(void*)value.tupleof[i]);
47         }
48     }
49 }
50 
51 extern(C) export struct returnValue(T) {
52     T value;
53     wstring error;
54 
55     this(T value) nothrow {
56         this.value = value;
57         static if (isDynamicArray!T)
58         {
59             if (this.value !is null)
60                 pinPointer(cast(void*)this.value.ptr);
61         }
62         else static if (is(T == class) || is(T == interface))
63         {
64             if (this.value !is null)
65                 pinPointer(cast(void*)this.value);
66         }
67         else static if (is(T == struct))
68             pinInternalPointers(value);
69     }
70 
71     this(Exception error) nothrow {
72         this.value = T.init;
73         try {
74             this.error = to!wstring(error.toString());
75         } catch(Exception ex) {
76             this.error = "Unhandled Exception while marshalling exception data. You should never see this error.";
77         }
78         pinPointer(cast(void*)this.error.ptr);
79     }
80 }
81 
82 extern(C) export struct returnVoid {
83     wstring error = null;
84 
85     this(Exception error) nothrow {
86         try {
87             this.error = to!wstring(error.toString());
88         } catch(Exception ex) {
89             this.error = "Unhandled Exception while marshalling exception data. You should never see this error.";
90         }
91         pinPointer(cast(void*)this.error.ptr);
92     }
93 }
94 
95 extern(C) export struct datetime {
96     long ticks;
97     long offset;
98 
99     public this(Duration value) {
100         this.ticks = value.total!"hnsecs";
101         this.offset = 0;
102     }
103 
104     public this(DateTime value) {
105         this.ticks = SysTime(value).stdTime;
106         this.offset = 0;
107     }
108 
109     public this(Date value) {
110         this.ticks = SysTime(value).stdTime;
111         this.offset = 0;
112     }
113 
114     public this(TimeOfDay value) {
115         import core.time : Duration, hours, minutes, seconds;
116         Duration t = hours(value.hour) + minutes(value.minute) + seconds(value.second);
117         this.ticks = t.total!"hnsecs";
118         this.offset = 0;
119     }
120 
121     public this(SysTime value) {
122         this.ticks = value.stdTime;
123         this.offset = value.utcOffset.total!"hnsecs";
124     }
125 }
126 
127 public void pinPointer(void* ptr) nothrow {
128     GC.setAttr(ptr, GC.BlkAttr.NO_MOVE);
129     GC.addRoot(ptr);
130 }
131 
132 public datetime toDatetime(T)(T value)
133 if (isDateTimeType!T) {
134     return datetime(value);
135 }
136 
137 public datetime[] toDatetime1DArray(T)(T[] value)
138 if (isDateTimeType!T) {
139     import std.algorithm : map;
140     import std.array : array;
141     return value.map!datetime.array;
142 }
143 
144 public T fromDatetime(T)(datetime value) if (is(Unqual!T == SysTime)) {
145     return SysTime(value.ticks, cast(immutable)new SimpleTimeZone(hnsecs(value.offset)));
146 }
147 
148 public T fromDatetime(T)(datetime value) if (is(Unqual!T == DateTime)) {
149     return cast(T)SysTime(value.ticks, LocalTime());
150 }
151 
152 public T fromDatetime(T)(datetime value) if (is(Unqual!T == Date) || is(Unqual!T == TimeOfDay)) {
153     return cast(T)SysTime(value.ticks, cast(immutable)new SimpleTimeZone(hnsecs(0)));
154 }
155 
156 public T fromDatetime(T)(datetime value) if (is(Unqual!T == Duration)) {
157     return hnsecs(value.ticks);
158 }
159 
160 public T[] fromDatetime1DArray(T)(datetime[] value)
161 if (isDateTimeType!T) {
162     import std.algorithm : map;
163     import std.array : array;
164     return value.map!(fromDatetime!T).array;
165 }
166 
167 extern(C) void rt_moduleTlsCtor();
168 extern(C) void rt_moduleTlsDtor();
169 
170 struct AttachThread
171 {
172     static create() nothrow
173     {
174         import core.thread : Thread, thread_attachThis;
175 
176         typeof(this) retval;
177 
178         retval.notAttached = Thread.getThis() is null;
179 
180         if(retval.notAttached)
181         {
182             try
183             {
184                 thread_attachThis();
185                 rt_moduleTlsCtor();
186             }
187             catch(Exception)
188                 assert(0, "An Exception shouldn't be possible here, but it happened anyway.");
189         }
190 
191         return retval;
192     }
193 
194     ~this() nothrow
195     {
196         import core.thread : thread_detachThis;
197 
198         if(notAttached)
199         {
200             try
201             {
202                 thread_detachThis();
203                 rt_moduleTlsDtor();
204             }
205             catch(Exception)
206                 assert(0, "An Exception shouldn't be possible here, but it happened anyway.");
207         }
208     }
209 
210     private bool notAttached = false;
211 }
212 
213 public string wrapCSharp(in Modules modules, OutputFileName outputFile, LibraryName libraryName, RootNamespace rootNamespace) @safe pure {
214     import std.format : format;
215     import std.algorithm: map;
216     import std.array: join;
217     import autowrap.common;
218     import autowrap.csharp.boilerplate;
219 
220     if(!__ctfe) return null;
221 
222     const modulesList = modules.value.map!(a => a.toString).join(", ");
223 
224     return q{
225         extern(C) export void autowrap_csharp_release(void* ptr) nothrow {
226             import core.memory : GC;
227             GC.clrAttr(ptr, GC.BlkAttr.NO_MOVE);
228             GC.removeRoot(ptr);
229         }
230 
231         extern(C) export string autowrap_csharp_createString(wchar* str) nothrow {
232             import std.string : fromStringz;
233             import std.utf : toUTF8;
234             import autowrap.csharp.boilerplate : AttachThread, pinPointer;
235 
236             auto attachThread = AttachThread.create();
237             string temp = toUTF8(str.fromStringz());
238             pinPointer(cast(void*)temp.ptr);
239             return temp;
240         }
241 
242         extern(C) export wstring autowrap_csharp_createWString(wchar* str) nothrow {
243             import std.string : fromStringz;
244             import std.utf : toUTF16;
245             import autowrap.csharp.boilerplate : pinPointer;
246 
247             auto attachThread = AttachThread.create();
248             wstring temp = toUTF16(str.fromStringz());
249             pinPointer(cast(void*)temp.ptr);
250             return temp;
251         }
252 
253         extern(C) export dstring autowrap_csharp_createDString(wchar* str) nothrow {
254             import std.string : fromStringz;
255             import std.utf : toUTF32;
256             import autowrap.csharp.boilerplate : pinPointer;
257 
258             auto attachThread = AttachThread.create();
259             dstring temp = toUTF32(str.fromStringz());
260             pinPointer(cast(void*)temp.ptr);
261             return temp;
262         }
263 
264         static import autowrap.csharp.dlang;
265         mixin(autowrap.csharp.dlang.wrapDLang!(%1$s));
266 
267         //Insert DllMain for Windows only.
268         version(Windows) {
269             %2$s
270         }
271 
272         void main() {
273             import std.stdio : File;
274             import autowrap.csharp.common : LibraryName, RootNamespace;
275             import autowrap.csharp.csharp : generateCSharp;
276             string generated = generateCSharp!(%1$s)(LibraryName("%3$s"), RootNamespace("%4$s"));
277             auto f = File("%5$s", "w");
278             f.writeln(generated);
279         }
280     }.format(modulesList, dllMainMixinStr(), libraryName.value, rootNamespace.value, outputFile.value);
281 }