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