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.traits : Unqual;
21 import std.string;
22 import std.traits;
23 import std.utf;
24 
25 private void pinInternalPointers(T)(ref T value) @trusted nothrow
26     if(is(T == struct))
27 {
28     import std.traits : Fields, isPointer, isDynamicArray, isStaticArray;
29 
30     static foreach(i, Field; Fields!T)
31     {
32         static assert(!isPointer!T, "If we're now supporting pointers, this code needs to be updated");
33         static assert(!isStaticArray!T, "If we're now supporting static arrays, this code needs to be updated");
34 
35         static if(is(Field == struct))
36             pinInternalPointers(value.tupleof[i]);
37         else static if(isDynamicArray!Field)
38         {
39             if(value.tupleof[i] !is null)
40                 pinPointer(cast(void*)value.tupleof[i].ptr);
41         }
42         else static if(is(Field == class) || is(Field == interface))
43         {
44             if(value.tupleof[i] !is null)
45                 pinPointer(cast(void*)value.tupleof[i]);
46         }
47     }
48 }
49 
50 extern(C) export struct returnValue(T) {
51     T value;
52     wstring error;
53 
54     this(T value) nothrow {
55         this.value = value;
56         static if (isDynamicArray!T)
57         {
58             if (this.value !is null)
59                 pinPointer(cast(void*)this.value.ptr);
60         }
61         else static if (is(T == class) || is(T == interface))
62         {
63             if (this.value !is null)
64                 pinPointer(cast(void*)this.value);
65         }
66         else static if (is(T == struct))
67             pinInternalPointers(value);
68     }
69 
70     this(Exception error) nothrow {
71         this.value = T.init;
72         try {
73             this.error = to!wstring(error.toString());
74         } catch(Exception ex) {
75             this.error = "Unhandled Exception while marshalling exception data. You should never see this error.";
76         }
77         pinPointer(cast(void*)this.error.ptr);
78     }
79 }
80 
81 extern(C) export struct returnVoid {
82     wstring error = null;
83 
84     this(Exception error) nothrow {
85         try {
86             this.error = to!wstring(error.toString());
87         } catch(Exception ex) {
88             this.error = "Unhandled Exception while marshalling exception data. You should never see this error.";
89         }
90         pinPointer(cast(void*)this.error.ptr);
91     }
92 }
93 
94 // Used for core.time.Duration
95 extern(C) export struct Marshalled_Duration {
96 
97     import core.time : dur, Duration;
98 
99     long hnsecs;
100 
101     this(Duration d)
102     {
103         hnsecs = d.total!"hnsecs";
104     }
105 
106     Duration opCast() { return dur!"hnsecs"(hnsecs); }
107 }
108 
109 // Used for Date, DateTime, and TimeOfDay from std.datetime.date.
110 // Unfortunately, C# doesn't really have a clean way of representing any of
111 // them. So, barring the use of a 3rd party library like Noda, the two options
112 // are to either use C#'s DateTime or create new types. Laeeth wanted the D
113 // date/time types to marshall to the standard C# date/time types. So, the
114 // current solution is to convert std.datetime.DateTime to a C# DateTime with
115 // UTC as its DateTimeKind so that the value doesn't risk changing based on
116 // time zone issues. std.datetime.Date will be converted to a C# DateTime with
117 // DateTimeKind.UTC and midnight for the time. std.datetime.TimeOfDay will be
118 // converted to a C# Datetime with DateTimeKind.Utc and January 1st, 1 A.D. as
119 // its date. When converting from C# to D, C#'s DateTime's properties will be
120 // used to get the values out so that the values will match whatever
121 // DateTimeKind the Datetime is using. std.datetime.Date will then ignore the
122 // hour, minute, and second values, and std.datetime.TimeOfDay will then ignore
123 // the year, month, and day values.
124 // The marshalled type does not need to keep track of whether a DateTime, Date,
125 // or TimeOfDay is intended, because the generated D code using it knows which
126 // D date/time type it's working with and will generate the correct conversion
127 // code.
128 extern(C) export struct Marshalled_std_datetime_date {
129 
130     import std.datetime.date : Date, DateTime, TimeOfDay;
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     GC.setAttr(ptr, GC.BlkAttr.NO_MOVE);
222     GC.addRoot(ptr);
223 }
224 
225 extern(C) void rt_moduleTlsCtor();
226 extern(C) void rt_moduleTlsDtor();
227 
228 struct AttachThread
229 {
230     static create() nothrow
231     {
232         import core.thread : Thread, thread_attachThis;
233 
234         typeof(this) retval;
235 
236         retval.notAttached = Thread.getThis() is null;
237 
238         if(retval.notAttached)
239         {
240             try
241             {
242                 thread_attachThis();
243                 rt_moduleTlsCtor();
244             }
245             catch(Exception)
246                 assert(0, "An Exception shouldn't be possible here, but it happened anyway.");
247         }
248 
249         return retval;
250     }
251 
252     ~this() nothrow
253     {
254         import core.thread : thread_detachThis;
255 
256         if(notAttached)
257         {
258             try
259             {
260                 thread_detachThis();
261                 rt_moduleTlsDtor();
262             }
263             catch(Exception)
264                 assert(0, "An Exception shouldn't be possible here, but it happened anyway.");
265         }
266     }
267 
268     private bool notAttached = false;
269 }
270 
271 public string wrapCSharp(in Modules modules, OutputFileName outputFile, LibraryName libraryName, RootNamespace rootNamespace) @safe pure {
272     import std.format : format;
273     import std.algorithm: map;
274     import std.array: join;
275     import autowrap.common;
276     import autowrap.csharp.boilerplate;
277 
278     if(!__ctfe) return null;
279 
280     const modulesList = modules.value.map!(a => a.toString).join(", ");
281 
282     return q{
283         extern(C) export void autowrap_csharp_release(void* ptr) nothrow {
284             import core.memory : GC;
285             GC.clrAttr(ptr, GC.BlkAttr.NO_MOVE);
286             GC.removeRoot(ptr);
287         }
288 
289         extern(C) export string autowrap_csharp_createString(wchar* str) nothrow {
290             import std.string : fromStringz;
291             import std.utf : toUTF8;
292             import autowrap.csharp.boilerplate : AttachThread, pinPointer;
293 
294             auto attachThread = AttachThread.create();
295             string temp = toUTF8(str.fromStringz());
296             pinPointer(cast(void*)temp.ptr);
297             return temp;
298         }
299 
300         extern(C) export wstring autowrap_csharp_createWString(wchar* str) nothrow {
301             import std.string : fromStringz;
302             import std.utf : toUTF16;
303             import autowrap.csharp.boilerplate : pinPointer;
304 
305             auto attachThread = AttachThread.create();
306             wstring temp = toUTF16(str.fromStringz());
307             pinPointer(cast(void*)temp.ptr);
308             return temp;
309         }
310 
311         extern(C) export dstring autowrap_csharp_createDString(wchar* str) nothrow {
312             import std.string : fromStringz;
313             import std.utf : toUTF32;
314             import autowrap.csharp.boilerplate : pinPointer;
315 
316             auto attachThread = AttachThread.create();
317             dstring temp = toUTF32(str.fromStringz());
318             pinPointer(cast(void*)temp.ptr);
319             return temp;
320         }
321 
322         static import autowrap.csharp.dlang;
323         mixin(autowrap.csharp.dlang.wrapDLang!(%1$s));
324 
325         //Insert DllMain for Windows only.
326         version(Windows) {
327             %2$s
328         }
329 
330         void main() {
331             import std.stdio : File;
332             import autowrap.csharp.common : LibraryName, RootNamespace;
333             import autowrap.csharp.csharp : generateCSharp;
334             string generated = generateCSharp!(%1$s)(LibraryName("%3$s"), RootNamespace("%4$s"));
335             auto f = File("%5$s", "w");
336             f.writeln(generated);
337         }
338     }.format(modulesList, dllMainMixinStr(), libraryName.value, rootNamespace.value, outputFile.value);
339 }