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 +/