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