The OpenD Programming Language

1 /++
2 	Code for COM interop on Windows. You can use it to consume
3 	COM objects (including several objects from .net assemblies)
4 	and to create COM servers with a natural D interface.
5 
6 	This code is not well tested, don't rely on it yet. But even
7 	in its incomplete state it might help in some cases. Strings
8 	and integers work pretty ok.
9 
10 	You can use it to interoperate with Word and Excel:
11 
12 	---
13 	void wordmain() {
14 		// gets the name of the open Word instance, if there is one
15 		// getComObject gets the currently registered open one, and the
16 		// "false" here means do not create a new one if none exists
17 		// (try changing it to true to open a hidden Word)
18 		auto wrd = getComObject("Word.Application", false);
19 		writeln(wrd.ActiveDocument.Name.getD!string);
20 	}
21 
22 	void excelmain() {
23 		// create anew Excel instance and put some stuff in it
24 		auto xlApp = createComObject("Excel.Application");
25 		try {
26 			xlApp.Visible() = 1;
27 			xlApp.Workbooks.Add()();
28 
29 			xlApp.ActiveSheet.Cells()(1, 1).Value() = "D can do it";
30 			xlApp.ActiveWorkbook.ActiveSheet.Cells()(1,2).Value() = "but come on";
31 
32 			writeln("success");
33 			readln();
34 
35 			xlApp.ActiveWorkbook.Close()(0);
36 		} catch(Exception e) {
37 			writeln(e.toString);
38 			writeln("waiting"); // let the user see before it closes
39 			readln();
40 		}
41 		xlApp.Quit()();
42 	}
43 	---
44 
45 	The extra parenthesis there are to work around D's broken `@property` attribute, you need one at the end before a = or call operator.
46 
47 	Or you can work with your own custom code:
48 
49 	```c#
50 	namespace Cool {
51 		public class Test {
52 
53 			static void Main() {
54 				System.Console.WriteLine("hello!");
55 			}
56 
57 			public int test() { return 4; }
58 			public int test2(int a) { return 10 + a; }
59 			public string hi(string s) { return "hello, " + s; }
60 		}
61 	}
62 	```
63 
64 	Compile it into a library like normal, then `regasm` it to register the
65 	assembly... then the following D code will work:
66 
67 	---
68 	import arsd.com;
69 
70 	interface CsharpTest {
71 		int test();
72 		int test2(int a);
73 		string hi(string s);
74 	}
75 
76 	void main() {
77 		auto obj = createComObject!CsharpTest("Cool.Test"); // early-bind dynamic version
78 		//auto obj = createComObject("Cool.Test"); // late-bind dynamic version
79 
80 		import std.stdio;
81 		writeln(obj.test()); // early-bind already knows the signature
82 		writeln(obj.test2(12));
83 		writeln(obj.hi("D"));
84 		//writeln(obj.test!int()); // late-bind needs help
85 		//writeln(obj.opDispatch!("test", int)());
86 	}
87 	---
88 
89 	I'll show a COM server example later. It is cool to call D objects
90 	from JScript and such.
91 +/
92 module arsd.com;
93 
94 import arsd.core;
95 
96 version(Windows):
97 
98 // for arrays to/from IDispatch use SAFEARRAY
99 // see https://stackoverflow.com/questions/295067/passing-an-array-using-com
100 
101 // for exceptions
102 // see: https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-erref/705fb797-2175-4a90-b5a3-3918024b10b8
103 // see: https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-erref/0c0bcf55-277e-4120-b5dc-f6115fc8dc38
104 
105 /+
106 	see: program\cs\comtest.d on the laptop.
107 
108 	as administrator: from program\cs
109 	c:\Windows\Microsoft.NEt\Framework64\v4.0.30319\regasm.exe /regfile /codebase test.dll
110 
111 	note: use the 64 bit register for 64 bit programs (Framework64)
112 	use 32 for 32 bit program (\Framework\)
113 
114 	sn -k key.snk
115 	program\cs\makefile
116 
117 	test.js in there shows it form wsh too
118 
119 	i can make it work through IDispatch easily enough, though
120 	ideally you'd have a real interface, that requires cooperation
121 	that the idispatch doesn't thanks to .net doing it for us.
122 
123 	passing other objects should work too btw thanks to idispatch
124 	in the variants... not sure about arrays tho
125 
126 	and then fully dynamic can be done with opDispatch for teh lulz.
127 +/
128 
129 /+
130 	createComObject returns the wrapped one
131 		wrapping can go dynamic if it is wrapping IDispatch
132 		some other IUnknown gets minimal wrapping (Translate formats)
133 		all wrappers can return lower level stuff on demand. like LL!string maybe is actually an RAII BSTR.
134 
135 		i also want variant to jsvar and stuff like that.
136 	createRawComObject returns the IUnknown raw one
137 +/
138 
139 public import core.sys.windows.windows;
140 public import core.sys.windows.com;
141 public import core.sys.windows.wtypes;
142 public import core.sys.windows.oaidl;
143 
144 import core.stdc.string;
145 import core.atomic;
146 
147 pragma(lib, "advapi32");
148 pragma(lib, "uuid");
149 pragma(lib, "ole32");
150 pragma(lib, "oleaut32");
151 pragma(lib, "user32");
152 
153 /* Attributes that help with automation */
154 
155 ///
156 static immutable struct ComGuid {
157 	///
158 	this(GUID g) { this.guid = g; }
159 	///
160 	this(string g) { guid = stringToGuid(g); }
161 	GUID guid;
162 }
163 
164 GUID stringToGuid(string g) {
165 	return GUID.init; // FIXME
166 }
167 
168 bool hasGuidAttribute(T)() {
169 	bool has = false;
170 	foreach(attr; __traits(getAttributes, T))
171 		static if(is(typeof(attr) == ComGuid))
172 			has = true;
173 	return has;
174 }
175 
176 template getGuidAttribute(T) {
177 	static ComGuid helper() {
178 		foreach(attr; __traits(getAttributes, T))
179 			static if(is(typeof(attr) == ComGuid))
180 				return attr;
181 		assert(0);
182 	}
183 	__gshared static immutable getGuidAttribute = helper();
184 }
185 
186 
187 /* COM CLIENT CODE */
188 
189 __gshared int coInitializeCalled;
190 shared static ~this() {
191 	CoFreeUnusedLibraries();
192 	if(coInitializeCalled) {
193 		CoUninitialize();
194 		coInitializeCalled--;
195 	}
196 }
197 
198 ///
199 void initializeClassicCom(bool multiThreaded = false) {
200 	if(coInitializeCalled)
201 		return;
202 
203 	ComCheck(CoInitializeEx(null, multiThreaded ? COINIT_MULTITHREADED : COINIT_APARTMENTTHREADED),
204 		"COM initialization failed");
205 
206 	coInitializeCalled++;
207 }
208 
209 ///
210 bool ComCheck(HRESULT hr, string desc) {
211 	if(FAILED(hr))
212 		throw new ComException(hr, desc);
213 	return true;
214 }
215 
216 ///
217 class ComException : WindowsApiException {
218 	this(HRESULT hr, string desc, string file = __FILE__, size_t line = __LINE__) {
219 		this.hr = hr;
220 		super(desc, cast(DWORD) hr, null, file, line);
221 	}
222 
223 	HRESULT hr;
224 }
225 
226 template Dify(T) {
227 	static if(is(T : IUnknown)) {
228 		// FIXME
229 		static assert(0);
230 	} else {
231 		alias Dify = T;
232 	}
233 }
234 
235 struct ComResult {
236 	VARIANT result;
237 
238 	ComProperty opDispatch(string memberName)() {
239 		auto newComObject = (result.vt == 9) ? result.pdispVal : null;
240 
241 		DISPID dispid;
242 
243 		if(newComObject !is null) {
244 			import std.conv;
245 			wchar*[1] names = [(to!wstring(memberName) ~ "\0"w).dup.ptr];
246 			ComCheck(newComObject.GetIDsOfNames(&GUID_NULL, names.ptr, 1, LOCALE_SYSTEM_DEFAULT, &dispid), "Look up name " ~ memberName);
247 		} else throw new Exception("cannot get member of non-object");
248 
249 		return ComProperty(newComObject, dispid, memberName);
250 	}
251 
252 	T getD(T)() {
253 		return getFromVariant!T(result);
254 	}
255 
256 }
257 
258 struct ComProperty {
259 	IDispatch innerComObject_;
260 	DISPID dispid;
261 	string name;
262 
263 	this(IDispatch a, DISPID c, string name) {
264 		this.innerComObject_ = a;
265 		this.dispid = c;
266 		this.name = name;
267 	}
268 
269 	T getD(T)() {
270 		auto res = _fetchProperty();
271 		return res.getD!T;
272 	}
273 
274 	ComResult _fetchProperty() {
275 		DISPPARAMS disp_params;
276 
277 		VARIANT result;
278 		EXCEPINFO einfo;
279 		uint argError;
280 
281 		auto hr =innerComObject_.Invoke(
282 			dispid,
283 			&GUID_NULL, LOCALE_SYSTEM_DEFAULT, // whatever
284 			DISPATCH_PROPERTYGET,
285 			&disp_params,
286 			&result,
287 			&einfo, // exception info
288 			&argError // arg error
289 		);//, "Invoke");
290 
291 		if (Exception e = exceptionFromComResult(hr, einfo, argError, "Property get")) {
292 			throw e;
293 		}
294 
295 		return ComResult(result);
296 	}
297 
298 	ComProperty opDispatch(string memberName)() {
299 		return _fetchProperty().opDispatch!memberName;
300 	}
301 
302 	T opAssign(T)(T rhs) {
303 		DISPPARAMS disp_params;
304 
305 		VARIANT[1] vargs;
306 		vargs[0] = toComVariant(rhs);
307 		disp_params.rgvarg = vargs.ptr;
308 		disp_params.cNamedArgs = 1;
309 		disp_params.cArgs = 1;
310 		DISPID dispidNamed = DISPID_PROPERTYPUT;
311 		disp_params.rgdispidNamedArgs = &dispidNamed;
312 
313 		VARIANT result;
314 		EXCEPINFO einfo;
315 		uint argError;
316 
317 		auto hr =innerComObject_.Invoke(
318 			dispid,
319 			&GUID_NULL, LOCALE_SYSTEM_DEFAULT, // whatever
320 			DISPATCH_PROPERTYPUT,
321 			&disp_params,
322 			&result,
323 			&einfo, // exception info
324 			&argError // arg error
325 		);//, "Invoke");
326 
327 		VariantClear(&vargs[0]);
328 
329 		if (Exception e = exceptionFromComResult(hr, einfo, argError, "Property put")) {
330 			throw e;
331 		}
332 
333 		return rhs;
334 	}
335 
336 	ComResult opCall(Args...)(Args args) {
337 		return callWithNamedArgs!Args(null, args);
338 	}
339 
340 	/// Call with named arguments
341 	///
342 	/// Note that all positional arguments are always followed by all named arguments.
343 	///
344 	/// So to call: `Com.f(10, 20, A: 30, B: 40)`, invoke this function as follows:
345 	/// ---
346 	/// Com.f().callWithNamedArgs(["A", "B"], 10, 20, 30, 40);
347 	/// ---
348 	/// Argument names are case-insensitive
349 	ComResult callWithNamedArgs(Args...)(string[] argNames, Args args) {
350 		DISPPARAMS disp_params;
351 
352 		static if (args.length) {
353 			VARIANT[args.length] vargs;
354 			foreach(idx, arg; args) {
355 				// lol it is put in backwards way to explain MSFT
356 				vargs[$ - 1 - idx] = toComVariant(arg);
357 			}
358 
359 			disp_params.rgvarg = vargs.ptr;
360 			disp_params.cArgs = cast(int) args.length;
361 
362 			if (argNames.length > 0) {
363 				wchar*[Args.length + 1] namesW;
364 				// GetIDsOfNames wants Method name at index 0 followed by parameter names.
365 				// Order of passing named args is up to us, but it's standard to also put them backwards,
366 				// and we've already done so with values in `vargs`, so we continue this trend
367 				// with dispatch IDs of names
368 				import std.conv: to;
369 				namesW[0] = (to!wstring(this.name) ~ "\0"w).dup.ptr;
370 				foreach (i; 0 .. argNames.length) {
371 					namesW[i + 1] = (to!wstring(argNames[$ - 1 - i]) ~ "\0"w).dup.ptr;
372 				}
373 				DISPID[Args.length + 1] dispIds;
374 				innerComObject_.GetIDsOfNames(
375 					&GUID_NULL, namesW.ptr, cast(uint) (1 + argNames.length), LOCALE_SYSTEM_DEFAULT, dispIds.ptr
376 				).ComCheck("Unknown parameter name");
377 
378 				// Strip Member name at index 0
379 				disp_params.cNamedArgs = cast(uint) argNames.length;
380 				disp_params.rgdispidNamedArgs = &dispIds[1];
381 			}
382 		}
383 
384 		VARIANT result;
385 		EXCEPINFO einfo;
386 		uint argError;
387 
388 		//ComCheck(innerComObject_.Invoke(
389 		auto hr =innerComObject_.Invoke(
390 			dispid,
391 			&GUID_NULL, LOCALE_SYSTEM_DEFAULT, // whatever
392 			DISPATCH_METHOD,// PROPERTYPUT, //DISPATCH_METHOD,
393 			&disp_params,
394 			&result,
395 			&einfo, // exception info
396 			&argError // arg error
397 		);//, "Invoke");
398 
399 		if(hr == 0x80020003) { // method not found
400 			// FIXME idk how to tell the difference between a method and a property from the outside..
401 			hr =innerComObject_.Invoke(
402 				dispid,
403 				&GUID_NULL, LOCALE_SYSTEM_DEFAULT, // whatever
404 				DISPATCH_PROPERTYGET,// PROPERTYPUT, //DISPATCH_METHOD,
405 				&disp_params,
406 				&result,
407 				&einfo, // exception info
408 				&argError // arg error
409 			);//, "Invoke");
410 		}
411 
412 		static if(args.length) {
413 			foreach (ref v; vargs[]) {
414 				VariantClear(&v);
415 			}
416 		}
417 
418 		if (Exception e = exceptionFromComResult(hr, einfo, argError, "Call")) {
419 			throw e;
420 		}
421 
422 		return ComResult(result);
423 	}
424 }
425 
426 /// Returns: `null` on success, a D Exception created from `einfo` and `argError`
427 /// in case the COM return `hr` signals failure
428 private Exception exceptionFromComResult(HRESULT hr, ref EXCEPINFO einfo, uint argError, string action)
429 {
430 	import std.conv;
431 	if(FAILED(hr)) {
432 		if(hr == DISP_E_EXCEPTION) {
433 			auto code = einfo.scode ? einfo.scode : einfo.wCode;
434 			string source;
435 			string description;
436 			if(einfo.bstrSource) {
437 				// this is really a wchar[] but it needs to be freed so....
438 				source = einfo.bstrSource[0 .. SysStringLen(einfo.bstrSource)].to!string;
439 				SysFreeString(einfo.bstrSource);
440 			}
441 			if(einfo.bstrDescription) {
442 				description = einfo.bstrDescription[0 .. SysStringLen(einfo.bstrDescription)].to!string;
443 				SysFreeString(einfo.bstrDescription);
444 			}
445 			if(einfo.bstrHelpFile) {
446 				// FIXME: we could prolly use this too
447 				SysFreeString(einfo.bstrHelpFile);
448 				// and dwHelpContext
449 			}
450 
451 			throw new ComException(code, description ~ " (from com source " ~ source ~ ")");
452 
453 		} else {
454 			throw new ComException(hr, action ~ " failed " ~ to!string(argError));
455 		}
456 	}
457 	return null;
458 }
459 
460 ///
461 struct ComClient(DVersion, ComVersion = IDispatch) {
462 	ComVersion innerComObject_;
463 	this(ComVersion t) {
464 		this.innerComObject_ = t;
465 	}
466 	this(this) {
467 		if(innerComObject_)
468 			innerComObject_.AddRef();
469 	}
470 	~this() {
471 		if(innerComObject_)
472 			innerComObject_.Release();
473 	}
474 
475 	// note that COM doesn't really support overloading so this
476 	// don't even attempt it. C# will export as name_N where N
477 	// is the index of the overload (except for 1) but...
478 
479 	static if(is(DVersion == Dynamic))
480 	ComProperty opDispatch(string memberName)() {
481 		// FIXME: this can be cached and reused, even done ahead of time
482 		DISPID dispid;
483 
484 		import std.conv;
485 		wchar*[1] names = [(to!wstring(memberName) ~ "\0"w).dup.ptr];
486 		ComCheck(innerComObject_.GetIDsOfNames(&GUID_NULL, names.ptr, 1, LOCALE_SYSTEM_DEFAULT, &dispid), "Look up name");
487 
488 		return ComProperty(this.innerComObject_, dispid, memberName);
489 	}
490 
491 	/+
492 	static if(is(DVersion == Dynamic))
493 	template opDispatch(string name) {
494 		template opDispatch(Ret = void) {
495 			Ret opDispatch(Args...)(Args args) {
496 				return dispatchMethodImpl!(name, Ret)(args);
497 			}
498 		}
499 	}
500 	+/
501 
502 	static if(is(ComVersion == IDispatch))
503 	template dispatchMethodImpl(string memberName, Ret = void) {
504 		Ret dispatchMethodImpl(Args...)(Args args) {
505 			static if(is(ComVersion == IDispatch)) {
506 
507 				// FIXME: this can be cached and reused, even done ahead of time
508 				DISPID dispid;
509 
510 				import std.conv;
511 				wchar*[1] names = [(to!wstring(memberName) ~ "\0"w).dup.ptr];
512 				ComCheck(innerComObject_.GetIDsOfNames(&GUID_NULL, names.ptr, 1, LOCALE_SYSTEM_DEFAULT, &dispid), "Look up name");
513 
514 				DISPPARAMS disp_params;
515 
516 				static if(args.length) {
517 					VARIANT[args.length] vargs;
518 					foreach(idx, arg; args) {
519 						// lol it is put in backwards way to explain MSFT
520 						vargs[$ - 1 - idx] = toComVariant(arg);
521 					}
522 
523 					disp_params.rgvarg = vargs.ptr;
524 					disp_params.cArgs = cast(int) args.length;
525 				}
526 
527 				VARIANT result;
528 				EXCEPINFO einfo;
529 				uint argError;
530 
531 				//ComCheck(innerComObject_.Invoke(
532 				auto hr =innerComObject_.Invoke(
533 					dispid,
534 					&GUID_NULL, LOCALE_SYSTEM_DEFAULT, // whatever
535 					DISPATCH_METHOD,// PROPERTYPUT, //DISPATCH_METHOD,
536 					&disp_params,
537 					&result,
538 					&einfo, // exception info
539 					&argError // arg error
540 				);//, "Invoke");
541 
542 				static if (args.length) {
543 					foreach (ref v; vargs[]) {
544 						VariantClear(&v);
545 					}
546 				}
547 
548 				if (Exception e = exceptionFromComResult(hr, einfo, argError, "Call")) {
549 					throw e;
550 				}
551 
552 				return getFromVariant!(typeof(return))(result);
553 			} else {
554 				static assert(0); // FIXME
555 			}
556 
557 		}
558 	}
559 
560 	// so note that if I were to just make this a class, it'd inherit
561 	// attributes from the D interface... but I want the RAII struct...
562 	// could do a class with a wrapper and alias this though. but meh.
563 	import std.traits;
564 	static foreach(memberName; __traits(allMembers, DVersion)) {
565 	static foreach(idx, overload; __traits(getOverloads, DVersion, memberName)) {
566 		mixin(q{ReturnType!overload }~memberName~q{(Parameters!overload args) {
567 			return dispatchMethodImpl!(memberName, typeof(return))(args);
568 		}
569 		});
570 	}
571 	}
572 }
573 
574 VARIANT toComVariant(T)(T arg) {
575 	VARIANT ret;
576 	static if(is(T : VARIANT)) {
577 		ret = arg;
578 	} else static if(is(T : ComClient!(Dynamic, IDispatch))) {
579 		ret.vt = VARENUM.VT_DISPATCH;
580 		ret.pdispVal = arg.innerComObject_;
581 	} else static if(is(T : ComProperty)) {
582 		ret = arg._fetchProperty();
583 	} else static if (is(T : ComResult)) {
584 		ret = arg.result;
585 	} else static if(is(T : IDispatch)) {
586 		ret.vt = VARENUM.VT_DISPATCH;
587 		ret.pdispVal = arg;
588 	} else static if(is(T : int)) {
589 		ret.vt = VARENUM.VT_I4;
590 		ret.intVal = arg;
591 	} else static if(is(T : long)) {
592 		ret.vt = VARENUM.VT_I8;
593 		ret.llVal = arg;
594 	} else static if(is(T : double)) {
595 		ret.vt = VARENUM.VT_R8;
596 		ret.dblVal = arg;
597 	} else static if(is(T : const(char)[])) {
598 		ret.vt = VARENUM.VT_BSTR;
599 		import std.utf;
600 		ret.bstrVal = SysAllocString(toUTFz!(wchar*)(arg));
601 	} else static if (is(T : E[], E)) {
602 		auto sizes = ndArrayDimensions!uint(arg);
603 		SAFEARRAYBOUND[sizes.length] saBound;
604 		foreach (i; 0 .. sizes.length) {
605 			saBound[i].lLbound = 0;
606 			saBound[i].cElements = sizes[i];
607 		}
608 		enum vt = vtFromDType!E;
609 		SAFEARRAY* sa = SafeArrayCreate(vt, saBound.length, saBound.ptr);
610 		int[sizes.length] indices;
611 		void fill(int dim, T)(T val) {
612 			static if (dim >= indices.length) {
613 				static if (vt == VARENUM.VT_BSTR) {
614 					import std.utf;
615 					SafeArrayPutElement(sa, indices.ptr, SysAllocString(toUTFz!(wchar*)(val)));
616 				} else {
617 					SafeArrayPutElement(sa, indices.ptr, &val);
618 				}
619 				return;
620 			} else {
621 				foreach (i; 0 .. val.length) {
622 					indices[dim] = cast(int) i;
623 					fill!(dim + 1)(val[i]);
624 				}
625 			}
626 		}
627 		fill!(0)(arg);
628 		ret.vt = VARENUM.VT_ARRAY | vt;
629 		ret.parray = sa;
630 	} else static assert(0, "Unsupported type (yet) " ~ T.stringof);
631 
632 	return ret;
633 }
634 
635 /// Returns: for any multi-dimensional array, a static array of `length` values for each dimension.
636 /// Strings are not considered arrays because they have the VT_BSTR type instead of VT_ARRAY
637 private auto ndArrayDimensions(I, T)(T arg) {
638 	static if (!is(T : const(char)[]) && (is(T == E[], E) || is(T == E[n], E, int n))) {
639         alias A = typeof(ndArrayDimensions!I(arg[0]));
640         I[1 + A.length] res = 0;
641         if (arg.length != 0) {
642             auto s = ndArrayDimensions!I(arg[0]);
643             res[1 .. $] = s[];
644         }
645         res[0] = cast(I) arg.length;
646 		return res;
647 	} else {
648 		I[0] res;
649 		return res;
650 	}
651 }
652 
653 unittest {
654 	auto x = new float[][][](2, 3, 5);
655 	assert(ndArrayDimensions!uint(x) == [2, 3, 5]);
656     short[4][][5] y;
657     y[0].length = 3;
658     assert(ndArrayDimensions!uint(y) == [5, 3, 4]);
659 }
660 
661 /// Get VARENUM tag for basic type T
662 private template vtFromDType(T) {
663 	static if (is(T == short)) {
664 		enum vtFromDType = VARENUM.VT_I2;
665 	} else static if(is(T == int)) {
666 		enum vtFromDType = VARENUM.VT_I4;
667 	} else static if (is(T == float)) {
668 		enum vtFromDType = VARENUM.VT_R4;
669 	} else static if (is(T == double)) {
670 		enum vtFromDType = VARENUM.VT_R8;
671 	} else static if(is(T == bool)) {
672 		enum vtFromDType = VARENUM.VT_BOOL;
673 	} else static if (is(T : const(char)[])) {
674 		enum vtFromDType = VARENUM.VT_BSTR;
675 	} else static if (is(T == E[], E)) {
676 		enum vtFromDType = vtFromDType!E;
677 	} else {
678 		static assert(0, "don't know VARENUM for " ~ T.stringof);
679 	}
680 }
681 
682 /*
683 	If you want to do self-registration:
684 
685 	if(dll_regserver("filename.dll", 1) == 0) {
686 		scope(exit)
687 			dll_regserver("filename.dll", 0);
688 		// use it
689 	}
690 */
691 
692 // note that HKEY_CLASSES_ROOT\pretty name\CLSID has the guid
693 
694 // note: https://en.wikipedia.org/wiki/Component_Object_Model#Registration-free_COM
695 
696 GUID guidForClassName(wstring c) {
697 	GUID id;
698 	ComCheck(CLSIDFromProgID((c ~ "\0").ptr, &id), "Name lookup failed");
699 	return id;
700 }
701 
702 interface Dynamic {}
703 
704 /++
705 	Create a COM object. The passed interface should be a child of IUnknown and from core.sys.windows or have a ComGuid UDA, or be something else entirely and you get dynamic binding.
706 
707 	The string version can take a GUID in the form of {xxxxx-xx-xxxx-xxxxxxxx} or a name it looks up in the registry.
708 	The overload takes a GUID object (e.g. CLSID_XXXX from the Windows headers or one you write in yourself).
709 
710 	It will return a wrapper to the COM object that conforms to a D translation of the COM interface with automatic refcounting.
711 +/
712 // FIXME: or you can request a fully dynamic version via opDispatch. That will have to be a thing
713 auto createComObject(T = Dynamic)(wstring c) {
714 	return createComObject!(T)(guidForClassName(c));
715 }
716 /// ditto
717 auto createComObject(T = Dynamic)(GUID classId) {
718 	initializeClassicCom();
719 
720 	static if(is(T : IUnknown) && hasGuidAttribute!T) {
721 		enum useIDispatch = false;
722 		auto iid = getGuidAttribute!(T).guid;
723 	// FIXME the below condition is just woof
724 	} else static if(is(T : IUnknown) && is(typeof(mixin("core.sys.windows.IID_" ~ T.stringof)))) {
725 		enum useIDispatch = false;
726 		auto iid = mixin("core.sys.windows.IID_" ~ T.stringof);
727 	} else {
728 		enum useIDispatch = true;
729 		auto iid = IID_IDispatch;
730 	}
731 
732 	static if(useIDispatch) {
733 		IDispatch obj;
734 	} else {
735 		static assert(is(T : IUnknown));
736 		T obj;
737 	}
738 
739 	ComCheck(CoCreateInstance(&classId, null, CLSCTX_INPROC_SERVER/*|CLSCTX_INPROC_HANDLER*/|CLSCTX_LOCAL_SERVER, &iid, cast(void**) &obj), "Failed to create object");
740 	// FIXME: if this fails we might retry with inproc_handler.
741 
742 	return ComClient!(Dify!T, typeof(obj))(obj);
743 }
744 
745 /// ditto
746 auto getComObject(T = Dynamic)(wstring c, bool tryCreateIfGetFails = true) {
747 	initializeClassicCom();
748 
749 	auto guid = guidForClassName(c);
750 
751 	auto get() {
752 		auto iid = IID_IDispatch;
753 		IUnknown obj;
754 		ComCheck(GetActiveObject(&guid, null, &obj), "Get Object"); // code 0x800401e3 is operation unavailable if it isn't there i think
755 		if(obj is null)
756 			throw new Exception("null");
757 
758 		IDispatch disp;
759 		ComCheck(obj.QueryInterface(&iid, cast(void**) &disp), "QueryInterface");
760 
761 		auto client = ComClient!(Dify!T, typeof(disp))(disp);
762 		disp.AddRef();
763 		return client;
764 	}
765 
766 	if(tryCreateIfGetFails)
767 		try
768 			return get();
769 		catch(Exception e)
770 			return createComObject(guid);
771 	else
772 		return get();
773 }
774 
775 
776 // FIXME: add one to get by ProgID rather than always guid
777 // FIXME: add a dynamic com object that uses IDispatch
778 
779 /* COM SERVER CODE */
780 
781 T getFromVariant(T)(VARIANT arg) {
782 	import std.traits;
783 	import std.conv;
784 	static if(is(T == void)) {
785 		return;
786 	} else static if(is(T == int)) {
787 		if(arg.vt == VARENUM.VT_I4)
788 			return arg.intVal;
789 	} else static if (is(T == float)) {
790 		if(arg.vt == VARENUM.VT_R4)
791 			return arg.fltVal;
792 	} else static if (is(T == double)) {
793 		if(arg.vt == VARENUM.VT_R8)
794 			return arg.dblVal;
795 	} else static if(is(T == bool)) {
796 		if(arg.vt == VARENUM.VT_BOOL)
797 			return arg.boolVal ? true : false;
798 	} else static if(is(T == string)) {
799 		if(arg.vt == VARENUM.VT_BSTR) {
800 			auto str = arg.bstrVal;
801 			scope(exit) SysFreeString(str);
802 			return to!string(str[0 .. SysStringLen(str)]);
803 		}
804 	} else static if(is(T == IDispatch)) {
805 		if(arg.vt == VARENUM.VT_DISPATCH)
806 			return arg.pdispVal;
807 	} else static if(is(T : IUnknown)) {
808 		// if(arg.vt == 13)
809 		static assert(0);
810 	} else static if(is(T == ComClient!(D, I), D, I)) {
811 		if(arg.vt == VARENUM.VT_DISPATCH)
812 			return ComClient!(D, I)(arg.pdispVal);
813 	} else static if(is(T == E[], E)) {
814 		if(arg.vt & 0x2000) {
815 			auto elevt = arg.vt & ~0x2000;
816 			auto a = arg.parray;
817 			scope(exit) SafeArrayDestroy(a);
818 
819 			auto bounds = a.rgsabound.ptr[0 .. a.cDims];
820 
821 			auto hr = SafeArrayLock(a);
822 			if(SUCCEEDED(hr)) {
823 				scope(exit) SafeArrayUnlock(a);
824 
825 				// BTW this is where things get interesting with the
826 				// mid-level wrapper. it can avoid these copies
827 
828 				// maybe i should check bounds.lLbound too.....
829 
830 				static if(is(E == int)) {
831 					if(elevt == 3) {
832 						assert(a.cbElements == E.sizeof);
833 						return (cast(E*)a.pvData)[0 .. bounds[0].cElements].dup;
834 					}
835 				} else static if(is(E == string)) {
836 					if(elevt == 8) {
837 						//assert(a.cbElements == E.sizeof);
838 						//return (cast(E*)a.pvData)[0 .. bounds[0].cElements].dup;
839 
840 						string[] ret;
841 						foreach(item; (cast(BSTR*) a.pvData)[0 .. bounds[0].cElements]) {
842 							auto str = item;
843 							scope(exit) SysFreeString(str);
844 							ret ~= to!string(str[0 .. SysStringLen(str)]);
845 						}
846 						return ret;
847 					}
848 				}
849 
850 			}
851 		}
852 	}
853 	throw new Exception("Type mismatch, needed "~ T.stringof ~" got " ~ to!string(cast(VARENUM) arg.vt));
854 	assert(0);
855 }
856 
857 /// Mixin to a low-level COM implementation class
858 mixin template IDispatchImpl() {
859 	override HRESULT GetIDsOfNames( REFIID riid, OLECHAR ** rgszNames, UINT cNames, LCID lcid, DISPID * rgDispId) {
860 		if(cNames == 0)
861 			return DISP_E_UNKNOWNNAME;
862 
863 		char[256] buffer;
864 		auto want = oleCharsToString(buffer, rgszNames[0]);
865 		foreach(idx, member; __traits(allMembers, typeof(this))) {
866 			if(member == want) {
867 				rgDispId[0] = idx + 1;
868 				return S_OK;
869 			}
870 		}
871 		return DISP_E_UNKNOWNNAME;
872 	}
873 
874 	override HRESULT GetTypeInfoCount(UINT* i) { *i = 0; return S_OK; }
875 	override HRESULT GetTypeInfo(UINT i, LCID l, LPTYPEINFO* p) { *p = null; return S_OK; }
876 	override HRESULT Invoke(DISPID dispIdMember, REFIID reserved, LCID locale, WORD wFlags, DISPPARAMS* params, VARIANT* result, EXCEPINFO* except, UINT* argErr) {
877 	// wFlags == 1 function call
878 	// wFlags == 2 property getter
879 	// wFlags == 4 property setter
880 		foreach(idx, member; __traits(allMembers, typeof(this))) {
881 			if(idx + 1 == dispIdMember) {
882 			static if(is(typeof(__traits(getMember, this, member)) == function))
883 				try {
884 					import std.traits;
885 					ParameterTypeTuple!(__traits(getMember, this, member)) args;
886 					alias argsStc = ParameterStorageClassTuple!(__traits(getMember, this, member));
887 
888 					static if(argsStc.length >= 1 && argsStc[0] == ParameterStorageClass.out_) {
889 						// the return value is often the first out param
890 						typeof(args[0]) returnedValue;
891 
892 						if(params !is null) {
893 							assert(params.cNamedArgs == 0); // FIXME
894 
895 							if(params.cArgs < args.length - 1)
896 								return DISP_E_BADPARAMCOUNT;
897 
898 							foreach(aidx, arg; args[1 .. $])
899 								args[1 + aidx] = getFromVariant!(typeof(arg))(params.rgvarg[aidx]);
900 						}
901 
902 						static if(is(ReturnType!(__traits(getMember, this, member)) == void)) {
903 							__traits(getMember, this, member)(returnedValue, args[1 .. $]);
904 						} else {
905 							auto returned = __traits(getMember, this, member)(returnedValue, args[1 .. $]);
906 							// FIXME: it probably returns HRESULT so we should forward that or something.
907 						}
908 
909 						if(result !is null) {
910 							static if(argsStc.length >= 1 && argsStc[0] == ParameterStorageClass.out_) {
911 								result.vt = 3; // int
912 								result.intVal = returnedValue;
913 							}
914 						}
915 					} else {
916 
917 						if(params !is null) {
918 							assert(params.cNamedArgs == 0); // FIXME
919 							if(params.cArgs < args.length)
920 								return DISP_E_BADPARAMCOUNT;
921 							foreach(aidx, arg; args)
922 								args[aidx] = getFromVariant!(typeof(arg))(params.rgvarg[aidx]);
923 						}
924 
925 						// no return value of note (just HRESULT at most)
926 						static if(is(ReturnType!(__traits(getMember, this, member)) == void)) {
927 							__traits(getMember, this, member)(args);
928 						} else {
929 							auto returned = __traits(getMember, this, member)(args);
930 							// FIXME: it probably returns HRESULT so we should forward that or something.
931 						}
932 					}
933 
934 					return S_OK;
935 				} catch(Throwable e) {
936 					// FIXME: fill in the exception info
937 					if(except !is null) {
938 						except.scode = 1;
939 						import std.utf;
940 						except.bstrDescription = SysAllocString(toUTFz!(wchar*)(e.toString()));
941 						except.bstrSource = SysAllocString("amazing"w.ptr);
942 					}
943 					return DISP_E_EXCEPTION;
944 				}
945 			}
946 		}
947 
948 		return DISP_E_MEMBERNOTFOUND;
949 	}
950 }
951 
952 /// Mixin to a low-level COM implementation class
953 mixin template ComObjectImpl() {
954 protected:
955 	IUnknown m_pUnkOuter;       // Controlling unknown
956 	PFNDESTROYED m_pfnDestroy;          // To call on closure
957 
958     /*
959      *  pUnkOuter       LPUNKNOWN of a controlling unknown.
960      *  pfnDestroy      PFNDESTROYED to call when an object
961      *                  is destroyed.
962      */
963 	public this(IUnknown pUnkOuter, PFNDESTROYED pfnDestroy) {
964 		m_pUnkOuter  = pUnkOuter;
965 		m_pfnDestroy = pfnDestroy;
966 	}
967 
968 	~this() {
969 		//MessageBoxA(null, "CHello.~this()", null, MB_OK);
970 	}
971 
972 	// Note: you can implement your own Init along with this mixin template and your function will automatically override this one
973     /*
974      *  Performs any intialization of a CHello that's prone to failure
975      *  that we also use internally before exposing the object outside.
976      * Return Value:
977      *  BOOL            true if the function is successful,
978      *                  false otherwise.
979      */
980 	public BOOL Init() {
981 		//MessageBoxA(null, "CHello.Init()", null, MB_OK);
982 		return true;
983 	}
984 
985 
986 	public
987 	override HRESULT QueryInterface(const (IID)*riid, LPVOID *ppv) {
988 		// wchar[200] lol; auto got = StringFromGUID2(riid, lol.ptr, lol.length); import std.conv;
989 		//MessageBoxA(null, toStringz("CHello.QueryInterface(g: "~to!string(lol[0 .. got])~")"), null, MB_OK);
990 
991 		assert(ppv !is null);
992 		*ppv = null;
993 
994 		import std.traits;
995 		foreach(iface; InterfacesTuple!(typeof(this))) {
996 			static if(hasGuidAttribute!iface()) {
997 				auto guid = getGuidAttribute!iface;
998 				if(*riid == guid.guid) {
999 					*ppv = cast(void*) cast(iface) this;
1000 					break;
1001 				}
1002 			} else static if(is(iface == IUnknown)) {
1003 				if (IID_IUnknown == *riid) {
1004 					*ppv = cast(void*) cast(IUnknown) this;
1005 					break;
1006 				}
1007 			} else static if(is(iface == IDispatch)) {
1008 				if (IID_IDispatch == *riid) {
1009 					*ppv = cast(void*) cast(IDispatch) this;
1010 					break;
1011 				}
1012 			}
1013 		}
1014 
1015 		if(*ppv !is null) {
1016 			AddRef();
1017 			return NOERROR;
1018 		} else {
1019 			return E_NOINTERFACE;
1020 		}
1021 	}
1022 
1023 	public
1024 	extern(Windows) ULONG AddRef() {
1025 		import core.atomic;
1026 		return atomicOp!"+="(*cast(shared)&count, 1);
1027 	}
1028 
1029 	public
1030 	extern(Windows) ULONG Release() {
1031 		import core.atomic;
1032 		LONG lRef = atomicOp!"-="(*cast(shared)&count, 1);
1033 		if (lRef == 0) {
1034 			// free object
1035 
1036 			/*
1037 			* Tell the housing that an object is going away so it can
1038 			* shut down if appropriate.
1039 			*/
1040 			//MessageBoxA(null, "CHello Destroy()", null, MB_OK);
1041 
1042 			if (m_pfnDestroy)
1043 				(*m_pfnDestroy)();
1044 
1045 			// delete this;
1046 			return 0;
1047 
1048 
1049 			// If we delete this object, then the postinvariant called upon
1050 			// return from Release() will fail.
1051 			// Just let the GC reap it.
1052 			//delete this;
1053 
1054 			return 0;
1055 		}
1056 
1057 		return cast(ULONG)lRef;
1058 	}
1059 
1060 	LONG count = 0;             // object reference count
1061 
1062 }
1063 
1064 
1065 
1066 
1067 // Type for an object-destroyed callback
1068 alias void function() PFNDESTROYED;
1069 
1070 // This class factory object creates Hello objects.
1071 class ClassFactory(Class) : IClassFactory {
1072 	extern (Windows) :
1073 
1074 	// IUnknown members
1075 	override HRESULT QueryInterface(const (IID)*riid, LPVOID *ppv) {
1076 		if (IID_IUnknown == *riid) {
1077 			*ppv = cast(void*) cast(IUnknown) this;
1078 		}
1079 		else if (IID_IClassFactory == *riid) {
1080 			*ppv = cast(void*) cast(IClassFactory) this;
1081 		}
1082 		else {
1083 			*ppv = null;
1084 			return E_NOINTERFACE;
1085 		}
1086 
1087 		AddRef();
1088 		return NOERROR;
1089 	}
1090 
1091 	LONG count = 0;             // object reference count
1092 	ULONG AddRef() {
1093 		return atomicOp!"+="(*cast(shared)&count, 1);
1094 	}
1095 
1096 	ULONG Release() {
1097 		return atomicOp!"-="(*cast(shared)&count, 1);
1098 	}
1099 
1100 	// IClassFactory members
1101 	override HRESULT CreateInstance(IUnknown pUnkOuter, IID*riid, LPVOID *ppvObj) {
1102 		HRESULT hr;
1103 
1104 		*ppvObj = null;
1105 		hr      = E_OUTOFMEMORY;
1106 
1107 		// Verify that a controlling unknown asks for IUnknown
1108 		if (null !is pUnkOuter && IID_IUnknown == *riid)
1109 			return CLASS_E_NOAGGREGATION;
1110 
1111 		// Create the object passing function to notify on destruction.
1112 		auto pObj = new Class(pUnkOuter, &ObjectDestroyed);
1113 
1114 		if (!pObj) {
1115 			MessageBoxA(null, "null", null, 0);
1116 			return hr;
1117 		}
1118 
1119 		if (pObj.Init()) {
1120 			hr = pObj.QueryInterface(riid, ppvObj);
1121 		}
1122 
1123 		// Kill the object if initial creation or Init failed.
1124 		if (FAILED(hr))
1125 			delete pObj;
1126 		else
1127 			g_cObj++;
1128 
1129 		return hr;
1130 	}
1131 
1132 	HRESULT LockServer(BOOL fLock) {
1133 		//MessageBoxA(null, "CHelloClassFactory.LockServer()", null, MB_OK);
1134 
1135 		if (fLock)
1136 			g_cLock++;
1137 		else
1138 			g_cLock--;
1139 
1140 		return NOERROR;
1141 	}
1142 }
1143 __gshared ULONG g_cLock=0;
1144 __gshared ULONG g_cObj =0;
1145 
1146 /*
1147  * ObjectDestroyed
1148  *
1149  * Purpose:
1150  *  Function for the Hello object to call when it gets destroyed.
1151  *  Since we're in a DLL we only track the number of objects here,
1152  *  letting DllCanUnloadNow take care of the rest.
1153  */
1154 
1155 extern (D) void ObjectDestroyed()
1156 {
1157     //MessageBoxA(null, "ObjectDestroyed()", null, MB_OK);
1158     g_cObj--;
1159 }
1160 
1161 
1162 char[] oleCharsToString(char[] buffer, OLECHAR* chars) {
1163 	auto c = cast(wchar*) chars;
1164 	auto orig = c;
1165 
1166 	size_t len = 0;
1167 	while(*c) {
1168 		len++;
1169 		c++;
1170 	}
1171 
1172 	auto c2 = orig[0 .. len];
1173 	int blen;
1174 	foreach(ch; c2) {
1175 		// FIXME breaks for non-ascii
1176 		assert(ch < 127);
1177 		buffer[blen] = cast(char) ch;
1178 		blen++;
1179 	}
1180 
1181 	return buffer[0 .. blen];
1182 }
1183 
1184 
1185 // usage: mixin ComServerMain!(CHello, CLSID_Hello, "Hello", "1.0");
1186 mixin template ComServerMain(Class, string progId, string ver) {
1187 	static assert(hasGuidAttribute!Class, "Add a @ComGuid(GUID()) to your class");
1188 
1189 	__gshared HINSTANCE g_hInst;
1190 
1191 	// initializing the runtime can fail on Windows XP when called via regsvr32...
1192 
1193 	extern (Windows)
1194 	BOOL DllMain(HINSTANCE hInstance, ULONG ulReason, LPVOID pvReserved) {
1195 		import core.sys.windows.dll;
1196 		g_hInst = hInstance;
1197 
1198 		switch (ulReason) {
1199 			case DLL_PROCESS_ATTACH:
1200 				return dll_process_attach(hInstance, true);
1201 			break;
1202 			case DLL_THREAD_ATTACH:
1203 				dll_thread_attach(true, true);
1204 			break;
1205 			case DLL_PROCESS_DETACH:
1206 				dll_process_detach(hInstance, true);
1207 			break;
1208 
1209 			case DLL_THREAD_DETACH:
1210 				return dll_thread_detach(true, true);
1211 			break;
1212 
1213 			default:
1214 				assert(0);
1215 		}
1216 
1217 		return true;
1218 	}
1219 
1220 	/*
1221 	 * DllGetClassObject
1222 	 *
1223 	 * Purpose:
1224 	 *  Provides an IClassFactory for a given CLSID that this DLL is
1225 	 *  registered to support.  This DLL is placed under the CLSID
1226 	 *  in the registration database as the InProcServer.
1227 	 *
1228 	 * Parameters:
1229 	 *  clsID           REFCLSID that identifies the class factory
1230 	 *                  desired.  Since this parameter is passed this
1231 	 *                  DLL can handle any number of objects simply
1232 	 *                  by returning different class factories here
1233 	 *                  for different CLSIDs.
1234 	 *
1235 	 *  riid            REFIID specifying the interface the caller wants
1236 	 *                  on the class object, usually IID_ClassFactory.
1237 	 *
1238 	 *  ppv             LPVOID * in which to return the interface
1239 	 *                  pointer.
1240 	 *
1241 	 * Return Value:
1242 	 *  HRESULT         NOERROR on success, otherwise an error code.
1243 	 */
1244 	pragma(mangle, "DllGetClassObject")
1245 	export
1246 	extern(Windows)
1247 	HRESULT DllGetClassObject(CLSID* rclsid, IID* riid, LPVOID* ppv) {
1248 		HRESULT hr;
1249 		ClassFactory!Class pObj;
1250 
1251 		//MessageBoxA(null, "DllGetClassObject()", null, MB_OK);
1252 
1253 		// printf("DllGetClassObject()\n");
1254 
1255 		if (clsid != *rclsid)
1256 			return E_FAIL;
1257 
1258 		pObj = new ClassFactory!Class();
1259 
1260 		if (!pObj)
1261 			return E_OUTOFMEMORY;
1262 
1263 		hr = pObj.QueryInterface(riid, ppv);
1264 
1265 		if (FAILED(hr))
1266 			delete pObj;
1267 
1268 		return hr;
1269 	}
1270 
1271 	/*
1272 	 *  Answers if the DLL can be freed, that is, if there are no
1273 	 *  references to anything this DLL provides.
1274 	 *
1275 	 * Return Value:
1276 	 *  BOOL            true if nothing is using us, false otherwise.
1277 	 */
1278 	pragma(mangle, "DllCanUnloadNow")
1279 	extern(Windows)
1280 	HRESULT DllCanUnloadNow() {
1281 		SCODE sc;
1282 
1283 		//MessageBoxA(null, "DllCanUnloadNow()", null, MB_OK);
1284 
1285 		// Any locks or objects?
1286 		sc = (0 == g_cObj && 0 == g_cLock) ? S_OK : S_FALSE;
1287 		return sc;
1288 	}
1289 
1290 	static immutable clsid = getGuidAttribute!Class.guid;
1291 
1292 	/*
1293 	 *  Instructs the server to create its own registry entries
1294 	 *
1295 	 * Return Value:
1296 	 *  HRESULT         NOERROR if registration successful, error
1297 	 *                  otherwise.
1298 	 */
1299 	pragma(mangle, "DllRegisterServer")
1300 	extern(Windows)
1301 	HRESULT DllRegisterServer() {
1302 		char[128] szID;
1303 		char[128] szCLSID;
1304 		char[512] szModule;
1305 
1306 		// Create some base key strings.
1307 		MessageBoxA(null, "DllRegisterServer", null, MB_OK);
1308 		auto len = StringFromGUID2(&clsid, cast(LPOLESTR) szID, 128);
1309 		unicode2ansi(szID.ptr);
1310 		szID[len] = 0;
1311 
1312 		//MessageBoxA(null, toStringz("DllRegisterServer("~szID[0 .. len] ~")"), null, MB_OK);
1313 
1314 		strcpy(szCLSID.ptr, "CLSID\\");
1315 		strcat(szCLSID.ptr, szID.ptr);
1316 
1317 		char[200] partialBuffer;
1318 		partialBuffer[0 .. progId.length] = progId[];
1319 		partialBuffer[progId.length] = 0;
1320 		auto partial = partialBuffer.ptr;
1321 
1322 		char[200] fullBuffer;
1323 		fullBuffer[0 .. progId.length] = progId[];
1324 		fullBuffer[progId.length .. progId.length + ver.length] = ver[];
1325 		fullBuffer[progId.length + ver.length] = 0;
1326 		auto full = fullBuffer.ptr;
1327 
1328 		// Create ProgID keys
1329 		SetKeyAndValue(full, null, "Hello Object");
1330 		SetKeyAndValue(full, "CLSID", szID.ptr);
1331 
1332 		// Create VersionIndependentProgID keys
1333 		SetKeyAndValue(partial, null, "Hello Object");
1334 		SetKeyAndValue(partial, "CurVer", full);
1335 		SetKeyAndValue(partial, "CLSID", szID.ptr);
1336 
1337 		// Create entries under CLSID
1338 		SetKeyAndValue(szCLSID.ptr, null, "Hello Object");
1339 		SetKeyAndValue(szCLSID.ptr, "ProgID", full);
1340 		SetKeyAndValue(szCLSID.ptr, "VersionIndependentProgID", partial);
1341 		SetKeyAndValue(szCLSID.ptr, "NotInsertable", null);
1342 
1343 		GetModuleFileNameA(g_hInst, szModule.ptr, szModule.length);
1344 
1345 		SetKeyAndValue(szCLSID.ptr, "InprocServer32", szModule.ptr);
1346 		return NOERROR;
1347 	}
1348 
1349 	/*
1350 	 * Purpose:
1351 	 *  Instructs the server to remove its own registry entries
1352 	 *
1353 	 * Return Value:
1354 	 *  HRESULT         NOERROR if registration successful, error
1355 	 *                  otherwise.
1356 	 */
1357 	pragma(mangle, "DllUnregisterServer")
1358 	extern(Windows)
1359 	HRESULT DllUnregisterServer() {
1360 		char[128] szID;
1361 		char[128] szCLSID;
1362 		char[256] szTemp;
1363 
1364 		MessageBoxA(null, "DllUnregisterServer()", null, MB_OK);
1365 
1366 		// Create some base key strings.
1367 		StringFromGUID2(&clsid, cast(LPOLESTR) szID, 128);
1368 		unicode2ansi(szID.ptr);
1369 		strcpy(szCLSID.ptr, "CLSID\\");
1370 		strcat(szCLSID.ptr, szID.ptr);
1371 
1372 		TmpStr tmp;
1373 		tmp.append(progId);
1374 		tmp.append("\\CurVer");
1375 		RegDeleteKeyA(HKEY_CLASSES_ROOT, tmp.getPtr());
1376 		tmp.clear();
1377 		tmp.append(progId);
1378 		tmp.append("\\CLSID");
1379 		RegDeleteKeyA(HKEY_CLASSES_ROOT, tmp.getPtr());
1380 		tmp.clear();
1381 		tmp.append(progId);
1382 		RegDeleteKeyA(HKEY_CLASSES_ROOT, tmp.getPtr());
1383 
1384 		tmp.clear();
1385 		tmp.append(progId);
1386 		tmp.append(ver);
1387 		tmp.append("\\CLSID");
1388 		RegDeleteKeyA(HKEY_CLASSES_ROOT, tmp.getPtr());
1389 		tmp.clear();
1390 		tmp.append(progId);
1391 		tmp.append(ver);
1392 		RegDeleteKeyA(HKEY_CLASSES_ROOT, tmp.getPtr());
1393 
1394 		strcpy(szTemp.ptr, szCLSID.ptr);
1395 		strcat(szTemp.ptr, "\\");
1396 		strcat(szTemp.ptr, "ProgID");
1397 		RegDeleteKeyA(HKEY_CLASSES_ROOT, szTemp.ptr);
1398 
1399 		strcpy(szTemp.ptr, szCLSID.ptr);
1400 		strcat(szTemp.ptr, "\\");
1401 		strcat(szTemp.ptr, "VersionIndependentProgID");
1402 		RegDeleteKeyA(HKEY_CLASSES_ROOT, szTemp.ptr);
1403 
1404 		strcpy(szTemp.ptr, szCLSID.ptr);
1405 		strcat(szTemp.ptr, "\\");
1406 		strcat(szTemp.ptr, "NotInsertable");
1407 		RegDeleteKeyA(HKEY_CLASSES_ROOT, szTemp.ptr);
1408 
1409 		strcpy(szTemp.ptr, szCLSID.ptr);
1410 		strcat(szTemp.ptr, "\\");
1411 		strcat(szTemp.ptr, "InprocServer32");
1412 		RegDeleteKeyA(HKEY_CLASSES_ROOT, szTemp.ptr);
1413 
1414 		RegDeleteKeyA(HKEY_CLASSES_ROOT, szCLSID.ptr);
1415 		return NOERROR;
1416 	}
1417 }
1418 
1419 /*
1420  * SetKeyAndValue
1421  *
1422  * Purpose:
1423  *  Private helper function for DllRegisterServer that creates
1424  *  a key, sets a value, and closes that key.
1425  *
1426  * Parameters:
1427  *  pszKey          LPTSTR to the name of the key
1428  *  pszSubkey       LPTSTR ro the name of a subkey
1429  *  pszValue        LPTSTR to the value to store
1430  *
1431  * Return Value:
1432  *  BOOL            true if successful, false otherwise.
1433  */
1434 BOOL SetKeyAndValue(LPCSTR pszKey, LPCSTR pszSubkey, LPCSTR pszValue)
1435 {
1436     HKEY hKey;
1437     char[256] szKey;
1438     BOOL result;
1439 
1440     strcpy(szKey.ptr, pszKey);
1441 
1442     if (pszSubkey)
1443     {
1444 	strcat(szKey.ptr, "\\");
1445 	strcat(szKey.ptr, pszSubkey);
1446     }
1447 
1448     result = true;
1449 
1450     if (ERROR_SUCCESS != RegCreateKeyExA(HKEY_CLASSES_ROOT,
1451 					  szKey.ptr, 0, null, REG_OPTION_NON_VOLATILE,
1452 					  KEY_ALL_ACCESS, null, &hKey, null))
1453 	result = false;
1454     else
1455     {
1456 	if (null != pszValue)
1457 	{
1458 	    if (RegSetValueExA(hKey, null, 0, REG_SZ, cast(BYTE *) pszValue,
1459                            cast(uint)((strlen(pszValue) + 1) * char.sizeof)) != ERROR_SUCCESS)
1460 		result = false;
1461 	}
1462 
1463 	if (RegCloseKey(hKey) != ERROR_SUCCESS)
1464 	    result = false;
1465     }
1466 
1467     if (!result)
1468 	MessageBoxA(null, "SetKeyAndValue() failed", null, MB_OK);
1469 
1470     return result;
1471 }
1472 
1473 void unicode2ansi(char *s)
1474 {
1475     wchar *w;
1476 
1477     for (w = cast(wchar *) s; *w; w++)
1478 	*s++ = cast(char)*w;
1479 
1480     *s = 0;
1481 }
1482 
1483 /**************************************
1484  * Register/unregister a DLL server.
1485  * Input:
1486  *      flag    !=0: register
1487  *              ==0: unregister
1488  * Returns:
1489  *      0       success
1490  *      !=0     failure
1491  */
1492 
1493 extern (Windows) alias HRESULT function() pfn_t;
1494 
1495 int dll_regserver(const (char) *dllname, int flag) {
1496 	char *fn = flag ? cast(char*) "DllRegisterServer"
1497 		: cast(char*) "DllUnregisterServer";
1498 	int result = 1;
1499 	pfn_t pfn;
1500 	HINSTANCE hMod;
1501 
1502 	if (SUCCEEDED(CoInitialize(null))) {
1503 		hMod=LoadLibraryA(dllname);
1504 
1505 		if (hMod > cast(HINSTANCE) HINSTANCE_ERROR) {
1506 			pfn = cast(pfn_t)(GetProcAddress(hMod, fn));
1507 
1508 			if (pfn && SUCCEEDED((*pfn)()))
1509 				result = 0;
1510 
1511 			CoFreeLibrary(hMod);
1512 			CoUninitialize();
1513 		}
1514 	}
1515 
1516 	return result;
1517 }
1518 
1519 struct TmpStr {
1520 	char[256] buffer;
1521 	int length;
1522 	void clear() { length = 0; }
1523 	char* getPtr() return {
1524 		buffer[length] = 0;
1525 		return buffer.ptr;
1526 	}
1527 
1528 	void append(string s) {
1529 		buffer[length .. length + s.length] = s[];
1530 		length += s.length;
1531 	}
1532 }
1533 
1534 /+
1535         Goals:
1536 
1537         * Use RoInitialize if present, OleInitialize or CoInitializeEx if not.
1538                 (if RoInitialize is present, webview can use Edge too, otherwise
1539                 gonna go single threaded for MSHTML. maybe you can require it via
1540                 a version switch)
1541 
1542                 or i could say this is simply not compatible with webview but meh.
1543 
1544         * idl2d ready to rock
1545         * RAII objects in use with natural auto-gen wrappers
1546         * Natural implementations type-checking the interface
1547 
1548         so like given
1549 
1550         interface Foo : IUnknown {
1551                 HRESULT test(BSTR a, out int b);
1552         }
1553 
1554         you can
1555 
1556         alias EasyCom!Foo Foo;
1557         Foo f = Foo.make; // or whatever
1558         int b = f.test("cool"); // throws if it doesn't return OK
1559 
1560         class MyFoo : ImplementsCom!(Foo) {
1561                 int test(string a) { return 5; }
1562         }
1563 
1564         and then you still use it through the interface.
1565 
1566         ImplementsCom takes the interface and translates it into
1567         a regular D interface for type checking.
1568         and then makes a proxy class to forward stuff. unless i can
1569         rig it with abstract methods
1570 
1571         class MyNewThing : IsCom!(MyNewThing) {
1572                 // indicates this implementation ought to
1573                 // become the interface
1574         }
1575 
1576         (basically in either case it converts the class to a COM
1577         wrapper, then asserts it actually implements the required
1578         interface)
1579 
1580 
1581 
1582         or what if i had a private implementation of the interface
1583         in the base class, auto-generated. then abstract hooks for
1584         the other things.
1585 +/
1586 
1587 /++
1588 
1589 module com;
1590 
1591 import com2;
1592 
1593 interface Refcounting {
1594         void AddRef();
1595         void Release();
1596 }
1597 
1598 interface Test : Refcounting {
1599         void test();
1600 }
1601 
1602 interface Test2 : Refcounting {
1603         void test2();
1604 }
1605 
1606 class Foo : Implements!Test, Implements!Test2 {
1607         override void test() {
1608                 import std.stdio;
1609                 writeln("amazing");
1610         }
1611 
1612         void test2() {}
1613 
1614         mixin Refcounts;
1615 }
1616 mixin RegisterComImplementation!(Foo, "some-guid");
1617 
1618 void main() {
1619         auto foo = new Foo();
1620         auto c = foo.getComProxy();
1621         c.test();
1622 
1623 }
1624 
1625 +/
1626 
1627 /++
1628 
1629 module com2;
1630 
1631 /+
1632         The COM interface's implementation is done by a
1633         generated class, forwarding it to the other D
1634         implementation
1635 
1636         if it implements IDispatch then it can do the dynamic
1637         thing too automatically!
1638 +/
1639 
1640 template Implements(Interface) {
1641         private static class Helper : Interface {
1642                 Implements i;
1643                 this(Implements i) {
1644                         this.i = i;
1645                 }
1646 
1647                 static foreach(memberName; __traits(allMembers, Interface))
1648                 mixin(q{ void } ~ memberName ~ q{ () {
1649                         import std.stdio; writeln("wrapper begin");
1650                         __traits(getMember, i, memberName)();
1651                         writeln("wrapper end");
1652                 }});
1653         }
1654 
1655         interface Implements {
1656                 final Helper getComProxy() {
1657                         return new Helper(this);
1658                 }
1659 
1660                 static foreach(memberName; __traits(allMembers, Interface))
1661                 mixin(q{ void } ~ memberName ~ q{ (); });
1662 
1663                 mixin template Refcounts() {
1664                         int refcount;
1665                         void AddRef() { refcount ++; }
1666                         void Release() { refcount--; }
1667                 }
1668         }
1669 }
1670 
1671 // the guid may also be a UDA on Class, but you do need to register your implementations
1672 mixin template RegisterComImplementation(Class, string guid = null) {
1673 
1674 }
1675 
1676 // wraps the interface with D-friendly type and provides RAII for the object
1677 struct ComClient(I) {}
1678 // eg: alias XmlDocument = ComClient!IXmlDocument;
1679 // then you get it through a com factory
1680 
1681 ComClient!I getCom(T)(string guid) { return ComClient!I(); }
1682 
1683 +/