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 
14 import autowrap.types: Modules, LibraryName, OutputFileName, RootNamespace;
15 
16 
17 private void pinInternalPointers(T)(ref T value) @trusted nothrow
18     if(is(T == struct))
19 {
20     import std.traits : Fields, isPointer, isDynamicArray, isStaticArray;
21 
22     static foreach(i, Field; Fields!T)
23     {
24         static assert(!isPointer!T, "If we're now supporting pointers, this code needs to be updated");
25         static assert(!isStaticArray!T, "If we're now supporting static arrays, this code needs to be updated");
26 
27         static if(is(Field == struct))
28             pinInternalPointers(value.tupleof[i]);
29         else static if(isDynamicArray!Field)
30         {
31             if(value.tupleof[i] !is null)
32                 pinPointer(cast(void*)value.tupleof[i].ptr);
33         }
34         else static if(is(Field == class) || is(Field == interface))
35         {
36             if(value.tupleof[i] !is null)
37                 pinPointer(cast(void*)value.tupleof[i]);
38         }
39     }
40 }
41 
42 extern(C) export struct returnValue(T) {
43 
44     import std.traits: isDynamicArray;
45 
46     T value;
47     wstring error;
48 
49     this(T value) nothrow {
50         this.value = value;
51         static if (isDynamicArray!T)
52         {
53             if (this.value !is null)
54                 pinPointer(cast(void*)this.value.ptr);
55         }
56         else static if (is(T == class) || is(T == interface))
57         {
58             if (this.value !is null)
59                 pinPointer(cast(void*)this.value);
60         }
61         else static if (is(T == struct))
62             pinInternalPointers(value);
63     }
64 
65     this(Exception error) nothrow {
66         import std.conv: to;
67 
68         this.value = T.init;
69         try {
70             this.error = to!wstring(error.toString());
71         } catch(Exception ex) {
72             this.error = "Unhandled Exception while marshalling exception data. You should never see this error.";
73         }
74         pinPointer(cast(void*)this.error.ptr);
75     }
76 }
77 
78 extern(C) export struct returnVoid {
79     import std.conv: to;
80 
81     wstring error = null;
82 
83     this(Exception error) nothrow {
84         try {
85             this.error = to!wstring(error.toString());
86         } catch(Exception ex) {
87             this.error = "Unhandled Exception while marshalling exception data. You should never see this error.";
88         }
89         pinPointer(cast(void*)this.error.ptr);
90     }
91 }
92 
93 // Used for core.time.Duration
94 extern(C) export struct Marshalled_Duration {
95 
96     import core.time : dur, Duration;
97 
98     long hnsecs;
99 
100     this(Duration d)
101     {
102         hnsecs = d.total!"hnsecs";
103     }
104 
105     Duration opCast() { return dur!"hnsecs"(hnsecs); }
106 }
107 
108 // Used for Date, DateTime, and TimeOfDay from std.datetime.date.
109 // Unfortunately, C# doesn't really have a clean way of representing any of
110 // them. So, barring the use of a 3rd party library like Noda, the two options
111 // are to either use C#'s DateTime or create new types. Laeeth wanted the D
112 // date/time types to marshall to the standard C# date/time types. So, the
113 // current solution is to convert std.datetime.DateTime to a C# DateTime with
114 // UTC as its DateTimeKind so that the value doesn't risk changing based on
115 // time zone issues. std.datetime.Date will be converted to a C# DateTime with
116 // DateTimeKind.UTC and midnight for the time. std.datetime.TimeOfDay will be
117 // converted to a C# Datetime with DateTimeKind.Utc and January 1st, 1 A.D. as
118 // its date. When converting from C# to D, C#'s DateTime's properties will be
119 // used to get the values out so that the values will match whatever
120 // DateTimeKind the Datetime is using. std.datetime.Date will then ignore the
121 // hour, minute, and second values, and std.datetime.TimeOfDay will then ignore
122 // the year, month, and day values.
123 // The marshalled type does not need to keep track of whether a DateTime, Date,
124 // or TimeOfDay is intended, because the generated D code using it knows which
125 // D date/time type it's working with and will generate the correct conversion
126 // code.
127 extern(C) export struct Marshalled_std_datetime_date {
128 
129     import std.datetime.date : Date, DateTime, TimeOfDay;
130     import std.traits: Unqual;
131 
132     // = 1 so that the result is a valid C# DateTime when the D type is Date
133     short year = 1;
134     ubyte month = 1;
135     ubyte day = 1;
136     ubyte hour;
137     ubyte minute;
138     ubyte second;
139 
140     this(DateTime dt)
141     {
142         year = dt.year;
143         month = dt.month;
144         day = dt.day;
145         hour = dt.hour;
146         minute = dt.minute;
147         second = dt.second;
148     }
149 
150     this(Date d)
151     {
152         year = d.year;
153         month = d.month;
154         day = d.day;
155     }
156 
157     this(TimeOfDay tod)
158     {
159         hour = tod.hour;
160         minute = tod.minute;
161         second = tod.second;
162     }
163 
164     DateTime opCast(T)()
165         if(is(Unqual!T == DateTime))
166     {
167         return DateTime(year, month, day, hour, minute, second);
168     }
169 
170     Date opCast(T)()
171         if(is(Unqual!T == Date))
172     {
173         return Date(year, month, day);
174     }
175 
176     TimeOfDay opCast(T)()
177         if(is(Unqual!T == TimeOfDay))
178     {
179         return TimeOfDay(hour, minute, second);
180     }
181 }
182 
183 // Used for std.datetime.systime.SysTime. It will be converted to C#'s
184 // DateTimeOffset. Unfortunately, DateTimeOffset does not have a way to
185 // uniquely distinguish UTC or local time in the way that SysTime does.
186 // Essentially, it's the equivalent of a SysTime which always uses a
187 // SimpleTimeZone for its timezone property. The current solution is to treat
188 // an offset of 0 as always being UTC (since odds are that that's what it is,
189 // and it requires less memory allocation that way), but since assuming that
190 // a UTC offset that matches the local time is intended to be treated as local
191 // time could cause problems, it unfortunately is treated as just another
192 // SimpleTimeZone rather than LocalTime.
193 extern(C) export struct Marshalled_std_datetime_systime {
194 
195     import core.time : hnsecs;
196     import std.datetime.systime : SysTime;
197 
198     long ticks; // SysTime's stdTime is equivalent to C#'s Ticks
199     long utcOffset; // in hnsecs
200 
201     this(SysTime st)
202     {
203         ticks = st.stdTime;
204         utcOffset = st.utcOffset.total!"hnsecs";
205     }
206 
207     SysTime opCast() {
208         import std.datetime.timezone : SimpleTimeZone, UTC;
209         return SysTime(ticks, utcOffset == 0 ? UTC() : new immutable SimpleTimeZone(hnsecs(utcOffset)));
210     }
211 }
212 
213 public auto mapArray(alias pred, T)(T[] arr)
214 {
215     import std.algorithm.iteration : map;
216     import std.array : array;
217     return arr.map!pred.array;
218 }
219 
220 public void pinPointer(void* ptr) nothrow {
221     import core.memory: GC;
222     GC.setAttr(ptr, GC.BlkAttr.NO_MOVE);
223     GC.addRoot(ptr);
224 }
225 
226 extern(C) void rt_moduleTlsCtor();
227 extern(C) void rt_moduleTlsDtor();
228 
229 struct AttachThread
230 {
231     static create() nothrow
232     {
233         import core.thread : Thread, thread_attachThis;
234 
235         typeof(this) retval;
236 
237         retval.notAttached = Thread.getThis() is null;
238 
239         if(retval.notAttached)
240         {
241             try
242             {
243                 thread_attachThis();
244                 rt_moduleTlsCtor();
245             }
246             catch(Exception)
247                 assert(0, "An Exception shouldn't be possible here, but it happened anyway.");
248         }
249 
250         return retval;
251     }
252 
253     ~this() nothrow
254     {
255         import core.thread : thread_detachThis;
256 
257         if(notAttached)
258         {
259             try
260             {
261                 thread_detachThis();
262                 rt_moduleTlsDtor();
263             }
264             catch(Exception)
265                 assert(0, "An Exception shouldn't be possible here, but it happened anyway.");
266         }
267     }
268 
269     private bool notAttached = false;
270 }
271 
272 public string wrapCSharp(
273     in Modules modules,
274     in OutputFileName outputFile,
275     in LibraryName libraryName,
276     in RootNamespace rootNamespace)
277     @safe pure
278 {
279     import std.format : format;
280     import std.algorithm: map;
281     import std.array: join;
282     import autowrap.common: dllMainMixinStr;
283     import autowrap.csharp.boilerplate;
284 
285     if(!__ctfe) return null;
286 
287     const modulesList = modules.value.map!(a => a.toString).join(", ");
288 
289     return q{
290         extern(C) export void autowrap_csharp_release(void* ptr) nothrow {
291             import core.memory : GC;
292             GC.clrAttr(ptr, GC.BlkAttr.NO_MOVE);
293             GC.removeRoot(ptr);
294         }
295 
296         extern(C) export string autowrap_csharp_createString(wchar* str) nothrow {
297             import std..string : fromStringz;
298             import std.utf : toUTF8;
299             import autowrap.csharp.boilerplate : AttachThread, pinPointer;
300 
301             auto attachThread = AttachThread.create();
302             string temp = toUTF8(str.fromStringz());
303             pinPointer(cast(void*)temp.ptr);
304             return temp;
305         }
306 
307         extern(C) export wstring autowrap_csharp_createWString(wchar* str) nothrow {
308             import std..string : fromStringz;
309             import std.utf : toUTF16;
310             import autowrap.csharp.boilerplate : pinPointer;
311 
312             auto attachThread = AttachThread.create();
313             wstring temp = toUTF16(str.fromStringz());
314             pinPointer(cast(void*)temp.ptr);
315             return temp;
316         }
317 
318         extern(C) export dstring autowrap_csharp_createDString(wchar* str) nothrow {
319             import std..string : fromStringz;
320             import std.utf : toUTF32;
321             import autowrap.csharp.boilerplate : pinPointer;
322 
323             auto attachThread = AttachThread.create();
324             dstring temp = toUTF32(str.fromStringz());
325             pinPointer(cast(void*)temp.ptr);
326             return temp;
327         }
328 
329         static import autowrap.csharp.dlang;
330         mixin(autowrap.csharp.dlang.wrapDLang!(%1$s));
331 
332         //Insert DllMain for Windows only.
333         version(Windows) {
334             %2$s
335         }
336 
337         void main() {
338             import std.stdio : File;
339             import autowrap.types: LibraryName, RootNamespace;
340             import autowrap.csharp.csharp : generateCSharp;
341             string generated = generateCSharp!(%1$s)(LibraryName("%3$s"), RootNamespace("%4$s"));
342             auto f = File("%5$s", "w");
343             f.writeln(generated);
344         }
345     }.format(modulesList, dllMainMixinStr(), libraryName.value, rootNamespace.value, outputFile.value);
346 }