arsd/comhelpers.d

951 lines
23 KiB
D

/++
My old com helper code. I haven't used it for years.
+/
module arsd.comhelpers;
/+
see: program\comtest.d on the laptop.
as administrator: from program\cs
c:\Windows\Microsoft.NEt\Framework64\v4.0.30319\regasm.exe /regfile /codebase test.dll
sn -k key.snk
program\cs\makefile
test.js in there shows it form wsh too
i can make it work through IDispatch easily enough, though
ideally you'd have a real interface, that requires cooperation
that the idispatch doesn't thanks to .net doing it for us.
passing other objects should work too btw thanks to idispatch
in the variants... not sure about arrays tho
and then fully dynamic can be done with opDispatch for teh lulz.
+/
version(Windows):
import core.sys.windows.windows;
import core.sys.windows.com;
import core.sys.windows.oaidl;
public import core.stdc.string;
import core.atomic;
pragma(lib, "advapi32");
pragma(lib, "uuid");
pragma(lib, "ole32");
pragma(lib, "oleaut32");
/* Attributes that help with automation */
static immutable struct ComGuid {
GUID guid;
}
bool hasGuidAttribute(T)() {
foreach(attr; __traits(getAttributes, T))
static if(is(typeof(attr) == ComGuid))
return true;
return false;
}
template getGuidAttribute(T) {
static ComGuid helper() {
foreach(attr; __traits(getAttributes, T))
static if(is(typeof(attr) == ComGuid))
return attr;
assert(0);
}
__gshared static immutable getGuidAttribute = helper();
}
/* COM CLIENT CODE */
__gshared int coInitializeCalled;
static ~this() {
CoFreeUnusedLibraries();
if(coInitializeCalled) {
CoUninitialize();
coInitializeCalled--;
}
}
void initializeCom() {
if(coInitializeCalled)
return;
/*
// Make sure COM is the right version
auto dwVer = CoBuildVersion();
if (rmm != HIWORD(dwVer))
throw new Exception("Incorrect OLE 2 version number\n");
*/
auto hr = CoInitialize(null);
if (FAILED(hr))
throw new Exception("OLE 2 failed to initialize\n");
coInitializeCalled++;
}
struct AutoComPtr(T) {
T t;
this(T t) {
this.t = t;
}
this(this) {
t.AddRef();
}
~this() {
t.Release();
}
alias t this;
}
/*
If you want to do self-registration:
if(dll_regserver("filename.dll", 1) == 0) {
scope(exit)
dll_regserver("filename.dll", 0);
// use it
}
*/
// note that HKEY_CLASSES_ROOT\pretty name\CLSID has the guid
/// Create a COM object. the string params are GUID literals that i mixin (this sux i know)
/// or if the interface has no IID it will try to IDispatch it
/// or you can request a fully dynamic version via opDispatch.
/// note i can try `import core.sys.windows.uuid; IID_IDispatch` for example to generically look up ones from the system if they are not attached and come from the windows namespace
AutoComPtr!T createObject(T, string iidStr = null)(GUID classId) {
initializeCom();
static if(iidStr == null) {
auto iid = getGuidAttribute!(T).guid;
} else
auto iid = mixin(iidStr);
T obj;
auto hr = CoCreateInstance(&classId, null, CLSCTX_INPROC_SERVER, &iid, cast(void**) &obj);
import std.format;
if(FAILED(hr))
throw new Exception("Failed to create object " ~ format("%08x", hr));
return AutoComPtr!T(obj);
}
// FIXME: add one to get by ProgID rather than always guid
// FIXME: add a dynamic com object that uses IDispatch
/* COM SERVER CODE */
T getFromVariant(T)(VARIANT arg) {
import std.traits;
import std.conv;
static if(is(T == int)) {
if(arg.vt == 3)
return arg.intVal;
} else static if(is(T == string)) {
if(arg.vt == 8) {
auto str = arg.bstrVal;
return to!string(str[0 .. SysStringLen(str)]);
}
} else static if(is(T == IDispatch)) {
if(arg.vt == 9)
return arg.pdispVal;
}
throw new Exception("Type mismatch, needed "~ T.stringof ~"got " ~ to!string(arg.vt));
assert(0);
}
mixin template IDispatchImpl() {
override HRESULT GetIDsOfNames( REFIID riid, OLECHAR ** rgszNames, UINT cNames, LCID lcid, DISPID * rgDispId) {
if(cNames == 0)
return DISP_E_UNKNOWNNAME;
char[256] buffer;
auto want = oleCharsToString(buffer, rgszNames[0]);
foreach(idx, member; __traits(allMembers, typeof(this))) {
if(member == want) {
rgDispId[0] = idx + 1;
return S_OK;
}
}
return DISP_E_UNKNOWNNAME;
}
override HRESULT GetTypeInfoCount(UINT* i) { *i = 0; return S_OK; }
override HRESULT GetTypeInfo(UINT i, LCID l, LPTYPEINFO* p) { *p = null; return S_OK; }
override HRESULT Invoke(DISPID dispIdMember, REFIID reserved, LCID locale, WORD wFlags, DISPPARAMS* params, VARIANT* result, EXCEPINFO* except, UINT* argErr) {
// wFlags == 1 function call
// wFlags == 2 property getter
// wFlags == 4 property setter
foreach(idx, member; __traits(allMembers, typeof(this))) {
if(idx + 1 == dispIdMember) {
static if(is(typeof(__traits(getMember, this, member)) == function))
try {
import std.traits;
ParameterTypeTuple!(__traits(getMember, this, member)) args;
alias argsStc = ParameterStorageClassTuple!(__traits(getMember, this, member));
static if(argsStc.length >= 1 && argsStc[0] == ParameterStorageClass.out_) {
// the return value is often the first out param
typeof(args[0]) returnedValue;
if(params !is null) {
assert(params.cNamedArgs == 0); // FIXME
if(params.cArgs < args.length - 1)
return DISP_E_BADPARAMCOUNT;
foreach(aidx, arg; args[1 .. $])
args[1 + aidx] = getFromVariant!(typeof(arg))(params.rgvarg[aidx]);
}
static if(is(ReturnType!(__traits(getMember, this, member)) == void)) {
__traits(getMember, this, member)(returnedValue, args[1 .. $]);
} else {
auto returned = __traits(getMember, this, member)(returnedValue, args[1 .. $]);
// FIXME: it probably returns HRESULT so we should forward that or something.
}
if(result !is null) {
static if(argsStc.length >= 1 && argsStc[0] == ParameterStorageClass.out_) {
result.vt = 3; // int
result.intVal = returnedValue;
}
}
} else {
if(params !is null) {
assert(params.cNamedArgs == 0); // FIXME
if(params.cArgs < args.length)
return DISP_E_BADPARAMCOUNT;
foreach(aidx, arg; args)
args[aidx] = getFromVariant!(typeof(arg))(params.rgvarg[aidx]);
}
// no return value of note (just HRESULT at most)
static if(is(ReturnType!(__traits(getMember, this, member)) == void)) {
__traits(getMember, this, member)(args);
} else {
auto returned = __traits(getMember, this, member)(args);
// FIXME: it probably returns HRESULT so we should forward that or something.
}
}
return S_OK;
} catch(Throwable e) {
// FIXME: fill in the exception info
if(except !is null) {
except.wCode = 1;
import std.utf;
except.bstrDescription = SysAllocString(toUTFz!(wchar*)(e.toString()));
except.bstrSource = SysAllocString("amazing"w.ptr);
}
return DISP_E_EXCEPTION;
}
}
}
return DISP_E_MEMBERNOTFOUND;
}
}
mixin template ComObjectImpl() {
protected:
IUnknown m_pUnkOuter; // Controlling unknown
PFNDESTROYED m_pfnDestroy; // To call on closure
/*
* pUnkOuter LPUNKNOWN of a controlling unknown.
* pfnDestroy PFNDESTROYED to call when an object
* is destroyed.
*/
public this(IUnknown pUnkOuter, PFNDESTROYED pfnDestroy) {
m_pUnkOuter = pUnkOuter;
m_pfnDestroy = pfnDestroy;
}
~this() {
//MessageBoxA(null, "CHello.~this()", null, MB_OK);
}
// Note: you can implement your own Init along with this mixin template and your function will automatically override this one
/*
* Performs any intialization of a CHello that's prone to failure
* that we also use internally before exposing the object outside.
* Return Value:
* BOOL true if the function is successful,
* false otherwise.
*/
public BOOL Init() {
//MessageBoxA(null, "CHello.Init()", null, MB_OK);
return true;
}
public
override HRESULT QueryInterface(const (IID)*riid, LPVOID *ppv) {
// wchar[200] lol; auto got = StringFromGUID2(riid, lol.ptr, lol.length); import std.conv;
//MessageBoxA(null, toStringz("CHello.QueryInterface(g: "~to!string(lol[0 .. got])~")"), null, MB_OK);
assert(ppv !is null);
*ppv = null;
import std.traits;
foreach(iface; InterfacesTuple!(typeof(this))) {
static if(hasGuidAttribute!iface()) {
auto guid = getGuidAttribute!iface;
if(*riid == guid.guid) {
*ppv = cast(void*) cast(iface) this;
break;
}
} else static if(is(iface == IUnknown)) {
if (IID_IUnknown == *riid) {
*ppv = cast(void*) cast(IUnknown) this;
break;
}
} else static if(is(iface == IDispatch)) {
if (IID_IDispatch == *riid) {
*ppv = cast(void*) cast(IDispatch) this;
break;
}
}
}
if(*ppv !is null) {
AddRef();
return NOERROR;
} else {
return E_NOINTERFACE;
}
}
public
extern(Windows) ULONG AddRef() {
import core.atomic;
return atomicOp!"+="(*cast(shared)&count, 1);
}
public
extern(Windows) ULONG Release() {
import core.atomic;
LONG lRef = atomicOp!"-="(*cast(shared)&count, 1);
if (lRef == 0) {
// free object
/*
* Tell the housing that an object is going away so it can
* shut down if appropriate.
*/
//MessageBoxA(null, "CHello Destroy()", null, MB_OK);
if (m_pfnDestroy)
(*m_pfnDestroy)();
// delete this;
return 0;
// If we delete this object, then the postinvariant called upon
// return from Release() will fail.
// Just let the GC reap it.
//delete this;
return 0;
}
return cast(ULONG)lRef;
}
LONG count = 0; // object reference count
}
// Type for an object-destroyed callback
alias void function() PFNDESTROYED;
extern (C)
{
void rt_init();
void rt_term();
void gc_init();
void gc_term();
}
// This class factory object creates Hello objects.
class ClassFactory(Class) : IClassFactory {
extern (Windows) :
// IUnknown members
override HRESULT QueryInterface(const (IID)*riid, LPVOID *ppv) {
if (IID_IUnknown == *riid) {
*ppv = cast(void*) cast(IUnknown) this;
}
else if (IID_IClassFactory == *riid) {
*ppv = cast(void*) cast(IClassFactory) this;
}
else {
*ppv = null;
return E_NOINTERFACE;
}
AddRef();
return NOERROR;
}
LONG count = 0; // object reference count
ULONG AddRef() {
return atomicOp!"+="(*cast(shared)&count, 1);
}
ULONG Release() {
return atomicOp!"-="(*cast(shared)&count, 1);
}
// IClassFactory members
override HRESULT CreateInstance(IUnknown pUnkOuter, IID*riid, LPVOID *ppvObj) {
HRESULT hr;
*ppvObj = null;
hr = E_OUTOFMEMORY;
// Verify that a controlling unknown asks for IUnknown
if (null !is pUnkOuter && IID_IUnknown == *riid)
return CLASS_E_NOAGGREGATION;
// Create the object passing function to notify on destruction.
auto pObj = new Class(pUnkOuter, &ObjectDestroyed);
if (!pObj) {
MessageBoxA(null, "null", null, 0);
return hr;
}
if (pObj.Init()) {
hr = pObj.QueryInterface(riid, ppvObj);
}
// Kill the object if initial creation or Init failed.
if (FAILED(hr))
delete pObj;
else
g_cObj++;
return hr;
}
HRESULT LockServer(BOOL fLock) {
//MessageBoxA(null, "CHelloClassFactory.LockServer()", null, MB_OK);
if (fLock)
g_cLock++;
else
g_cLock--;
return NOERROR;
}
}
__gshared ULONG g_cLock=0;
__gshared ULONG g_cObj =0;
/*
* ObjectDestroyed
*
* Purpose:
* Function for the Hello object to call when it gets destroyed.
* Since we're in a DLL we only track the number of objects here,
* letting DllCanUnloadNow take care of the rest.
*/
extern (D) void ObjectDestroyed()
{
//MessageBoxA(null, "ObjectDestroyed()", null, MB_OK);
g_cObj--;
}
char[] oleCharsToString(char[] buffer, OLECHAR* chars) {
auto c = cast(wchar*) chars;
auto orig = c;
size_t len = 0;
while(*c) {
len++;
c++;
}
auto c2 = orig[0 .. len];
int blen;
foreach(ch; c2) {
// FIXME breaks for non-ascii
assert(ch < 127);
buffer[blen] = cast(char) ch;
blen++;
}
return buffer[0 .. blen];
}
// usage: mixin ComServerMain!(CHello, CLSID_Hello, "Hello", "1.0");
mixin template ComServerMain(Class, string progId, string ver) {
static assert(hasGuidAttribute!Class, "Add a @ComGuid(GUID()) to your class");
__gshared HINSTANCE g_hInst;
// initializing the runtime can fail on Windows XP when called via regsvr32...
extern (Windows)
BOOL DllMain(HINSTANCE hInstance, ULONG ulReason, LPVOID pvReserved) {
import core.sys.windows.dll;
g_hInst = hInstance;
switch (ulReason) {
case DLL_PROCESS_ATTACH:
return dll_process_attach(hInstance, true);
break;
case DLL_THREAD_ATTACH:
dll_thread_attach(true, true);
break;
case DLL_PROCESS_DETACH:
dll_process_detach(hInstance, true);
break;
case DLL_THREAD_DETACH:
return dll_thread_detach(true, true);
break;
default:
assert(0);
}
return true;
}
/*
* DllGetClassObject
*
* Purpose:
* Provides an IClassFactory for a given CLSID that this DLL is
* registered to support. This DLL is placed under the CLSID
* in the registration database as the InProcServer.
*
* Parameters:
* clsID REFCLSID that identifies the class factory
* desired. Since this parameter is passed this
* DLL can handle any number of objects simply
* by returning different class factories here
* for different CLSIDs.
*
* riid REFIID specifying the interface the caller wants
* on the class object, usually IID_ClassFactory.
*
* ppv LPVOID * in which to return the interface
* pointer.
*
* Return Value:
* HRESULT NOERROR on success, otherwise an error code.
*/
pragma(mangle, "DllGetClassObject")
extern(Windows)
HRESULT DllGetClassObject(CLSID* rclsid, IID* riid, LPVOID* ppv) {
HRESULT hr;
ClassFactory!Class pObj;
//MessageBoxA(null, "DllGetClassObject()", null, MB_OK);
// printf("DllGetClassObject()\n");
if (clsid != *rclsid)
return E_FAIL;
pObj = new ClassFactory!Class();
if (!pObj)
return E_OUTOFMEMORY;
hr = pObj.QueryInterface(riid, ppv);
if (FAILED(hr))
delete pObj;
return hr;
}
/*
* Answers if the DLL can be freed, that is, if there are no
* references to anything this DLL provides.
*
* Return Value:
* BOOL true if nothing is using us, false otherwise.
*/
pragma(mangle, "DllCanUnloadNow")
extern(Windows)
HRESULT DllCanUnloadNow() {
SCODE sc;
//MessageBoxA(null, "DllCanUnloadNow()", null, MB_OK);
// Any locks or objects?
sc = (0 == g_cObj && 0 == g_cLock) ? S_OK : S_FALSE;
return sc;
}
static immutable clsid = getGuidAttribute!Class.guid;
/*
* Instructs the server to create its own registry entries
*
* Return Value:
* HRESULT NOERROR if registration successful, error
* otherwise.
*/
pragma(mangle, "DllRegisterServer")
extern(Windows)
HRESULT DllRegisterServer() {
char[128] szID;
char[128] szCLSID;
char[512] szModule;
// Create some base key strings.
MessageBoxA(null, "DllRegisterServer", null, MB_OK);
auto len = StringFromGUID2(&clsid, cast(LPOLESTR) szID, 128);
unicode2ansi(szID.ptr);
szID[len] = 0;
//MessageBoxA(null, toStringz("DllRegisterServer("~szID[0 .. len] ~")"), null, MB_OK);
strcpy(szCLSID.ptr, "CLSID\\");
strcat(szCLSID.ptr, szID.ptr);
char[200] partialBuffer;
partialBuffer[0 .. progId.length] = progId[];
partialBuffer[progId.length] = 0;
auto partial = partialBuffer.ptr;
char[200] fullBuffer;
fullBuffer[0 .. progId.length] = progId[];
fullBuffer[progId.length .. progId.length + ver.length] = ver[];
fullBuffer[progId.length + ver.length] = 0;
auto full = fullBuffer.ptr;
// Create ProgID keys
SetKeyAndValue(full, null, "Hello Object");
SetKeyAndValue(full, "CLSID", szID.ptr);
// Create VersionIndependentProgID keys
SetKeyAndValue(partial, null, "Hello Object");
SetKeyAndValue(partial, "CurVer", full);
SetKeyAndValue(partial, "CLSID", szID.ptr);
// Create entries under CLSID
SetKeyAndValue(szCLSID.ptr, null, "Hello Object");
SetKeyAndValue(szCLSID.ptr, "ProgID", full);
SetKeyAndValue(szCLSID.ptr, "VersionIndependentProgID", partial);
SetKeyAndValue(szCLSID.ptr, "NotInsertable", null);
GetModuleFileNameA(g_hInst, szModule.ptr, szModule.length);
SetKeyAndValue(szCLSID.ptr, "InprocServer32", szModule.ptr);
return NOERROR;
}
/*
* Purpose:
* Instructs the server to remove its own registry entries
*
* Return Value:
* HRESULT NOERROR if registration successful, error
* otherwise.
*/
pragma(mangle, "DllUnregisterServer")
extern(Windows)
HRESULT DllUnregisterServer() {
char[128] szID;
char[128] szCLSID;
char[256] szTemp;
MessageBoxA(null, "DllUnregisterServer()", null, MB_OK);
// Create some base key strings.
StringFromGUID2(&clsid, cast(LPOLESTR) szID, 128);
unicode2ansi(szID.ptr);
strcpy(szCLSID.ptr, "CLSID\\");
strcat(szCLSID.ptr, szID.ptr);
TmpStr tmp;
tmp.append(progId);
tmp.append("\\CurVer");
RegDeleteKeyA(HKEY_CLASSES_ROOT, tmp.getPtr());
tmp.clear();
tmp.append(progId);
tmp.append("\\CLSID");
RegDeleteKeyA(HKEY_CLASSES_ROOT, tmp.getPtr());
tmp.clear();
tmp.append(progId);
RegDeleteKeyA(HKEY_CLASSES_ROOT, tmp.getPtr());
tmp.clear();
tmp.append(progId);
tmp.append(ver);
tmp.append("\\CLSID");
RegDeleteKeyA(HKEY_CLASSES_ROOT, tmp.getPtr());
tmp.clear();
tmp.append(progId);
tmp.append(ver);
RegDeleteKeyA(HKEY_CLASSES_ROOT, tmp.getPtr());
strcpy(szTemp.ptr, szCLSID.ptr);
strcat(szTemp.ptr, "\\");
strcat(szTemp.ptr, "ProgID");
RegDeleteKeyA(HKEY_CLASSES_ROOT, szTemp.ptr);
strcpy(szTemp.ptr, szCLSID.ptr);
strcat(szTemp.ptr, "\\");
strcat(szTemp.ptr, "VersionIndependentProgID");
RegDeleteKeyA(HKEY_CLASSES_ROOT, szTemp.ptr);
strcpy(szTemp.ptr, szCLSID.ptr);
strcat(szTemp.ptr, "\\");
strcat(szTemp.ptr, "NotInsertable");
RegDeleteKeyA(HKEY_CLASSES_ROOT, szTemp.ptr);
strcpy(szTemp.ptr, szCLSID.ptr);
strcat(szTemp.ptr, "\\");
strcat(szTemp.ptr, "InprocServer32");
RegDeleteKeyA(HKEY_CLASSES_ROOT, szTemp.ptr);
RegDeleteKeyA(HKEY_CLASSES_ROOT, szCLSID.ptr);
return NOERROR;
}
}
/*
* SetKeyAndValue
*
* Purpose:
* Private helper function for DllRegisterServer that creates
* a key, sets a value, and closes that key.
*
* Parameters:
* pszKey LPTSTR to the name of the key
* pszSubkey LPTSTR ro the name of a subkey
* pszValue LPTSTR to the value to store
*
* Return Value:
* BOOL true if successful, false otherwise.
*/
BOOL SetKeyAndValue(LPCSTR pszKey, LPCSTR pszSubkey, LPCSTR pszValue)
{
HKEY hKey;
char[256] szKey;
BOOL result;
strcpy(szKey.ptr, pszKey);
if (pszSubkey)
{
strcat(szKey.ptr, "\\");
strcat(szKey.ptr, pszSubkey);
}
result = true;
if (ERROR_SUCCESS != RegCreateKeyExA(HKEY_CLASSES_ROOT,
szKey.ptr, 0, null, REG_OPTION_NON_VOLATILE,
KEY_ALL_ACCESS, null, &hKey, null))
result = false;
else
{
if (null != pszValue)
{
if (RegSetValueExA(hKey, null, 0, REG_SZ, cast(BYTE *) pszValue,
cast(uint)((strlen(pszValue) + 1) * char.sizeof)) != ERROR_SUCCESS)
result = false;
}
if (RegCloseKey(hKey) != ERROR_SUCCESS)
result = false;
}
if (!result)
MessageBoxA(null, "SetKeyAndValue() failed", null, MB_OK);
return result;
}
void unicode2ansi(char *s)
{
wchar *w;
for (w = cast(wchar *) s; *w; w++)
*s++ = cast(char)*w;
*s = 0;
}
/**************************************
* Register/unregister a DLL server.
* Input:
* flag !=0: register
* ==0: unregister
* Returns:
* 0 success
* !=0 failure
*/
extern (Windows) alias HRESULT function() pfn_t;
int dll_regserver(const (char) *dllname, int flag) {
char *fn = flag ? cast(char*) "DllRegisterServer"
: cast(char*) "DllUnregisterServer";
int result = 1;
pfn_t pfn;
HINSTANCE hMod;
if (SUCCEEDED(CoInitialize(null))) {
hMod=LoadLibraryA(dllname);
if (hMod > cast(HINSTANCE) HINSTANCE_ERROR) {
pfn = cast(pfn_t)(GetProcAddress(hMod, fn));
if (pfn && SUCCEEDED((*pfn)()))
result = 0;
CoFreeLibrary(hMod);
CoUninitialize();
}
}
return result;
}
struct TmpStr {
char[256] buffer;
int length;
void clear() { length = 0; }
char* getPtr() {
buffer[length] = 0;
return buffer.ptr;
}
void append(string s) {
buffer[length .. length + s.length] = s[];
length += s.length;
}
}
/++
module com;
import com2;
interface Refcounting {
void AddRef();
void Release();
}
interface Test : Refcounting {
void test();
}
interface Test2 : Refcounting {
void test2();
}
class Foo : Implements!Test, Implements!Test2 {
override void test() {
import std.stdio;
writeln("amazing");
}
void test2() {}
mixin Refcounts;
}
mixin RegisterComImplementation!(Foo, "some-guid");
void main() {
auto foo = new Foo();
auto c = foo.getComProxy();
c.test();
}
+/
/++
module com2;
/+
The COM interface's implementation is done by a
generated class, forwarding it to the other D
implementation
if it implements IDispatch then it can do the dynamic
thing too automatically!
+/
template Implements(Interface) {
private static class Helper : Interface {
Implements i;
this(Implements i) {
this.i = i;
}
static foreach(memberName; __traits(allMembers, Interface))
mixin(q{ void } ~ memberName ~ q{ () {
import std.stdio; writeln("wrapper begin");
__traits(getMember, i, memberName)();
writeln("wrapper end");
}});
}
interface Implements {
final Helper getComProxy() {
return new Helper(this);
}
static foreach(memberName; __traits(allMembers, Interface))
mixin(q{ void } ~ memberName ~ q{ (); });
mixin template Refcounts() {
int refcount;
void AddRef() { refcount ++; }
void Release() { refcount--; }
}
}
}
// the guid may also be a UDA on Class, but you do need to register your implementations
mixin template RegisterComImplementation(Class, string guid = null) {
}
// wraps the interface with D-friendly type and provides RAII for the object
struct ComClient(I) {}
// eg: alias XmlDocument = ComClient!IXmlDocument;
// then you get it through a com factory
ComClient!I getCom(T)(string guid) { return ComClient!I(); }
+/