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 }