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 }