How DynamicWrapper Works
The code to implement the DynamicWrapper object relies on Ton Plooy's DynaCall() code for creating dynamic calls to DLL functions at runtime. This month's code archive includes a copy of that code. The layer above DynaCall() that does all of the OLE automation work resides in the C++ class CDynamicWrapper in dynwrap.cpp (Listing One).
//----------------------------------------------------------------- // Dynamic Procedure Call COM object. Jeff Stong 1998 //----------------------------------------------------------------- #define WIN32_LEAN_AND_MEAN #define INC_OLE2 #include <windows.h> #include <malloc.h> // Using non-DLL version of DynaCall, so don't need to have the // methods imported. #undef DECLSPEC_IMPORT #define DECLSPEC_IMPORT extern "C" { #include "DynaCall.h" } // Global optimizations cause crash in release builds made with // Microsoft 32-bit C/C++ Compiler Version 11.00.7022 #ifdef _MSC_VER #pragma optimize("g",off) #endif // Allocate on-the-stack LPSTR from LPCWSTR LPSTR W2AHelp(LPSTR a, LPCWSTR w, int n) { a[0] = '\0'; WideCharToMultiByte(CP_ACP, 0, w, -1, a, n, NULL, NULL); return a; } #define W2A(w) (((LPCWSTR)w == NULL) ? NULL : (_clen = \ (lstrlenW(w)+1)*2,W2AHelp((LPSTR) _alloca(_clen), w, _clen))) int _clen; // Locate index for which c is equal to id in array of n elements template <class T> UINT Find(WCHAR c, const T* arr, UINT n) { for (UINT i = 0; i < n; i++) if (arr[i].id == c) return i; return -1; } // Allowable tags procedure calling convention class CDynCall; typedef struct tagTAGINFO { WCHAR id; // Character HRESULT (*pfn)(CDynCall*, LPWSTR, int); // Parsing callback procedure } TAGINFO; HRESULT iParse(CDynCall* p, LPWSTR w, int c); HRESULT rParse(CDynCall* p, LPWSTR w, int c); HRESULT fParse(CDynCall* p, LPWSTR w, int c); const TAGINFO TagInfo[] = { {'i',iParse}, // Input arguments (see ARGTYPEINFO entries) {'r',rParse}, // Return type (see ARGTYPEINFO entries) {'f',fParse}, // Calling convention (see FLAGINFO entries) }; #define FindIndexOfTag(wc) \ Find<TAGINFO>(wc,TagInfo,sizeof(TagInfo)/sizeof(TAGINFO)) // Parameter and return values typedef struct tagARGTYPEINFO { WCHAR id; // Character UINT size; // Size of type VARTYPE vt; // Compatible VARTYPE } ARGTYPEINFO; const ARGTYPEINFO ArgInfo[] = { {'a', sizeof(IDispatch*), VT_DISPATCH}, // a IDispatch* {'c', sizeof(unsigned char), VT_I4}, // c signed char {'d', sizeof(double), VT_R8}, // d 8 byte real {'f', sizeof(float), VT_R4}, // f 4 byte real {'k', sizeof(IUnknown*), VT_UNKNOWN}, // k IUnknown* {'h', sizeof(long), VT_I4}, // h HANDLE {'l', sizeof(long), VT_I4}, // l long {'p', sizeof(void*), VT_PTR}, // p pointer {'s', sizeof(BSTR), VT_LPSTR}, // s string {'t', sizeof(short), VT_I2}, // t short {'u', sizeof(UINT), VT_UINT}, // u unsigned int {'w', sizeof(BSTR), VT_LPWSTR}, // w wide string }; #define FindIndexOfArg(c) \ Find<ARGTYPEINFO> \ (c,ArgInfo,sizeof(ArgInfo)/sizeof(ARGTYPEINFO)) // Calling conventions flags typedef struct tagFLAGINFO { WCHAR id; // Character WORD wFlag; // Flag for id WORD wMask; // Mask for flag value replacement } FLAGINFO; const FLAGINFO FlagInfo[] = { {'m', DC_MICROSOFT, ~(DC_MICROSOFT|DC_BORLAND)}, {'b', DC_BORLAND, ~(DC_MICROSOFT|DC_BORLAND)}, {'s', DC_CALL_STD, ~(DC_CALL_STD|DC_CALL_CDECL)}, {'c', DC_CALL_CDECL, ~(DC_CALL_STD|DC_CALL_CDECL)}, {'4', DC_RETVAL_MATH4, ~(DC_RETVAL_MATH4|DC_RETVAL_MATH8)}, {'8', DC_RETVAL_MATH8, ~(DC_RETVAL_MATH4|DC_RETVAL_MATH8)}, }; #define FindIndexOfFlag(c) \ Find<FLAGINFO>(c,FlagInfo,sizeof(FlagInfo)/sizeof(FLAGINFO)) // DISPID for "Register" method and all those after #define REGISTERDISPID 1 DISPID dispidLastUsed = REGISTERDISPID; // CServer class holds global object count class CServer { public: CServer() : m_hInstance(NULL), m_dwRef(0) {} HINSTANCE m_hInstance; DWORD m_dwRef; }; CServer m_Server; // CDynCall class manages dynamic procedure calls class CDynCall { public: // ctor/dtor CDynCall() : dwAddress(0), cArgs(0), iArg(NULL), iRet(-1), wFlags(DC_MICROSOFT|DC_CALL_STD), hDLL(NULL), pNext(NULL), bstrMethod(NULL) {} ~CDynCall() { SysFreeString(bstrMethod); FreeLibrary(hDLL); delete [] iArg; } // Equivalance operators used by CDynCallChain class bool operator==(DISPID l) const { return l == dispid; } bool operator==(LPCWSTR l) const { return !lstrcmpiW(l,bstrMethod); } // Register the procedure HRESULT Register(DISPPARAMS* pDispParams, VARIANT* pVarResult) { // Require at least DLL and procedure name if (pDispParams->cArgs < 2) return DISP_E_BADPARAMCOUNT; VARIANTARG* rgvarg = pDispParams->rgvarg; int cArgs = pDispParams->cArgs; HRESULT hr = E_INVALIDARG; // Can the library be loaded? if ((hDLL = LoadLibraryW(rgvarg[cArgs-1].bstrVal)) != NULL) { // Find the address of the procedure bstrMethod = SysAllocString(rgvarg[cArgs-2].bstrVal); if ((dwAddress = SearchProcAddress(hDLL,W2A(bstrMethod)))) { // Load the tags describing the procedure hr = S_OK; for (int i = cArgs-3; i >= 0 && SUCCEEDED(hr); i--) hr = GetTags(rgvarg[i].bstrVal); } } if (SUCCEEDED(hr)) dispid = ++dispidLastUsed; // Assign a dispid if (pVarResult) // Return result if requested by caller { V_VT(pVarResult) = VT_BOOL; V_BOOL(pVarResult) = SUCCEEDED(hr); } return hr; } // Parse the tags HRESULT GetTags(LPWSTR wstrParms) { while (*wstrParms && iswspace(*wstrParms)) wstrParms++; *wstrParms = towlower(*wstrParms); // Find the tag, check format and invoke callback int len = lstrlenW(wstrParms); UINT i = FindIndexOfTag(*wstrParms); if ((i == -1) || (len < 3) || (wstrParms[1] != L'=')) return E_INVALIDARG; wstrParms += 2; return TagInfo[i].pfn(this,wstrParms,len-2); } // Invokes the procedure HRESULT Invoke(DISPPARAMS* pDispParams, VARIANT* pVarResult) { // Check argument count if (cArgs != pDispParams->cArgs) return DISP_E_BADPARAMCOUNT; HRESULT hr = S_OK; // Allocate DYNPARM structure on stack DYNAPARM* Parms = (DYNAPARM*)_alloca(sizeof(DYNAPARM)*cArgs); ZeroMemory(Parms,sizeof(DYNAPARM) * cArgs); DYNAPARM* Parm = Parms + (cArgs - 1); // Work last to first VARIANTARG* rgvarg = pDispParams->rgvarg; VARIANT va; VariantInit(&va); // Fill in each DYNPARM entry for (UINT i = 0; (i < cArgs) && !FAILED(hr); i++, Parm--) { // Parameter width from table Parm->nWidth = ArgInfo[iArg[i]].size; if (Parm->nWidth > 4) Parm->dwFlags = DC_FLAG_ARGPTR; // Parameter value VariantClear(&va); hr = VariantChangeType(&va,&rgvarg[i],0,ArgInfo[iArg[i]].vt); if (SUCCEEDED(hr)) { if (Parm->dwFlags & DC_FLAG_ARGPTR) { Parm->pArg = _alloca(Parm->nWidth); CopyMemory(Parm->pArg,&va.byref,Parm->nWidth); } else Parm->pArg = va.byref; } else { // Cases for which VariantChangeType doesn't work hr = S_OK; switch (ArgInfo[iArg[i]].vt) { case (VT_I4): // Handle if (rgvarg[i].vt <= VT_NULL) Parm->pArg = 0; else hr = E_INVALIDARG; break; case (VT_LPSTR): Parm->pArg = W2A(rgvarg[i].bstrVal); break; case (VT_LPWSTR): Parm->pArg = rgvarg[i].bstrVal; break; default: hr = E_INVALIDARG; break; } } } // Make the dynamic call RESULT rc; if (SUCCEEDED(hr)) rc = DynaCall(wFlags,dwAddress,cArgs,Parms,NULL,0); // Get the return value if requested if (pVarResult) { CopyMemory(&pVarResult->lVal,&rc.Long,ArgInfo[iRet].size); pVarResult->vt = ArgInfo[iRet].vt; } // Cleanup VariantClear(&va); // Done return hr; } BSTR bstrMethod; // Name of procedure DISPID dispid; // Assigned DISPID HINSTANCE hDLL; // Handle to DLL containing procedure DWORD dwAddress; // Address of procedure WORD wFlags; // Flags describing calling convention UINT cArgs; // Number of arguments LPUINT iArg; // Indexes to input arguments UINT iRet; // Index of return type CDynCall* pNext; // Pointer to next object in chain }; // Parses the input arguments (i=) HRESULT iParse(CDynCall* pThis, LPWSTR w, int c) { pThis->iArg = new UINT[c]; pThis->cArgs = c; UINT* p = pThis->iArg + (c - 1); for (; *w; w++) { UINT j = FindIndexOfArg(towlower(*w)); if (j == -1) return E_INVALIDARG; if (p) *p = j; p--; } return S_OK; } // Parses the return argument (r=) HRESULT rParse(CDynCall* pThis, LPWSTR w, int c) { pThis->iRet = FindIndexOfArg(towlower(*w)); return (pThis->iRet != -1) ? S_OK : E_INVALIDARG; } // Parses the calling convention flags (f=) HRESULT fParse(CDynCall* pThis, LPWSTR w, int c) { for (; *w; w++) { UINT i = FindIndexOfFlag(towlower(*w)); if (i == -1) return E_INVALIDARG; pThis->wFlags = (pThis->wFlags & FlagInfo[i].wMask) | FlagInfo[i].wFlag; } return S_OK; } // CDynCallChain class manages a simple CDynCall linked-list class CDynCallChain { public: // ctor/dtor CDynCallChain() : m_pFirst(NULL) { } ~CDynCallChain() { while (m_pFirst) { CDynCall* p = m_pFirst; m_pFirst = m_pFirst->pNext; delete p; } } // Find the DISPID for the given name s DISPID FindDISPID(LPWSTR s) { CDynCall* p = Find(s); if (p) return p->dispid; else if (!lstrcmpiW(s,L"Register")) return REGISTERDISPID; return DISPID_UNKNOWN; } // Register the procedure (creates a new CDynCall object and // adds it to the chain) HRESULT Register(DISPPARAMS* pDispParams, VARIANT* pVarResult) { CDynCall* p = new CDynCall; if (!p) return E_OUTOFMEMORY; HRESULT hr = p->Register(pDispParams,pVarResult); if (SUCCEEDED(hr)) { p->pNext = m_pFirst; m_pFirst = p; } else delete p; return hr; } // Invoke the procedure identifies by dispid HRESULT Invoke(DISPID dispid, DISPPARAMS* pParams, VARIANT* pResult) { CDynCall* p = Find(dispid); if (p) return p->Invoke(pParams,pResult); else if (dispid == REGISTERDISPID) return Register(pParams,pResult); return DISPID_UNKNOWN; } protected: // Find CDynCall object in chain with value l of type T template <class T> CDynCall* Find(T l) { for (CDynCall* p = m_pFirst; p; p = p->pNext) { if (*p == l) break; } return p; } protected: CDynCall* m_pFirst; // First object in chain }; // Template class that provides basic IUnknown implementation template <class T, const IID* piid> class CInterface : public T { public: CInterface() : m_dwRef(0) { m_Server.m_dwRef++; } virtual ~CInterface() { m_Server.m_dwRef--; } STDMETHOD(QueryInterface)(REFIID riid, void** ppvObject) { if ((riid == IID_IUnknown) || (riid == *piid)) { *ppvObject = (T*)static_cast<T*>(this); m_dwRef++; return S_OK; } return E_NOINTERFACE; } STDMETHOD_(ULONG,AddRef)() { return ++m_dwRef; } STDMETHOD_(ULONG,Release)() { if (!(--m_dwRef)) { delete this; return 0; } return m_dwRef; } DWORD m_dwRef; }; // COM class that provides for registering and invoking // dynamic procedure calls class CDynamicWrapper : public CInterface<IDispatch,&IID_IDispatch> { // IDispatch interface implementation public: // These methods not implemented STDMETHOD(GetTypeInfoCount)(UINT* pctinfo) { return E_NOTIMPL; } STDMETHOD(GetTypeInfo)(UINT, LCID, ITypeInfo**) { return E_NOTIMPL; } // Defer to CDynCallChain for everything else STDMETHOD(GetIDsOfNames)(REFIID, LPOLESTR* rgszNames, UINT cNames, LCID, DISPID* rgDispId) { for (UINT i = 0; i < cNames; i++) { rgDispId[i] = m_Chain.FindDISPID(rgszNames[i]); if (rgDispId[i] == DISPID_UNKNOWN) return DISP_E_MEMBERNOTFOUND; } return S_OK; } STDMETHOD(Invoke)(DISPID dispIdMember, REFIID, LCID, WORD, DISPPARAMS* pDispParams, VARIANT* pVarResult, EXCEPINFO* pExcepInfo, UINT *puArgErr) { return m_Chain.Invoke(dispIdMember,pDispParams,pVarResult); } protected: CDynCallChain m_Chain; }; // Class factory to create CDynamicWrapper COM objects class CClassFactory : public CInterface<IClassFactory,&IID_IClassFactory> { public: // IClassFactory interface implementation STDMETHOD(CreateInstance)(IUnknown* pUnkOuter, REFIID riid, void** ppvObject) { if (pUnkOuter) return CLASS_E_NOAGGREGATION; CDynamicWrapper* pObject = new CDynamicWrapper; HRESULT hr = pObject->QueryInterface(riid,ppvObject); if (FAILED(hr)) delete pObject; return hr; } STDMETHOD(LockServer)(BOOL fLock) { return CoLockObjectExternal(this,fLock,TRUE); } }; // DllMain extern "C" BOOL WINAPI DllMain(HINSTANCE hInstance, DWORD dwReason, LPVOID) { if (dwReason == DLL_PROCESS_ATTACH) { m_Server.m_hInstance = hInstance; DisableThreadLibraryCalls(hInstance); } return TRUE; } // Required COM in-proc server exports follow STDAPI DllRegisterServer(void) { LPCSTR CLSIDVAL = "{202774D1-D479-11d1-ACD1-00A024BBB05E}"; LPCSTR CLASSKEY = "CLSID\\{202774D1-D479-11d1-ACD1-00A024BBB05E}\\InProcServer32"; LPCSTR PRODIDKEY = "DynamicWrapper\\CLSID"; HRESULT hr = E_FAIL; HKEY key = NULL; if (!RegCreateKey(HKEY_CLASSES_ROOT,CLASSKEY,&key)) { char szModulePath[_MAX_PATH]; GetModuleFileName(m_Server.m_hInstance,szModulePath, _MAX_PATH); if(!RegSetValue(key,NULL,REG_SZ,szModulePath,0)) { RegCloseKey(key); if (!RegCreateKey(HKEY_CLASSES_ROOT,PRODIDKEY,&key)) { if (!RegSetValue(key,NULL,REG_SZ,CLSIDVAL,0)) hr = S_OK; } } } RegCloseKey(key); return hr; } STDAPI DllGetClassObject(REFCLSID rclsid, REFIID riid, LPVOID* ppv) { const GUID CLSID_DynWrap = { 0x202774d1, 0xd479, 0x11d1, { 0xac, 0xd1, 0x0, 0xa0, 0x24, 0xbb, 0xb0, 0x5e } }; HRESULT hr = CLASS_E_CLASSNOTAVAILABLE; if (rclsid == CLSID_DynWrap) { CClassFactory* pFactory = new CClassFactory; if (FAILED(hr = pFactory->QueryInterface(riid,ppv))) delete pFactory; hr = S_OK; } return hr; } STDAPI DllCanUnloadNow() { return (m_Server.m_dwRef) ? S_FALSE : S_OK; } //End of File
OLE automation objects are COM objects that implement the IDispatch interface. For more information on the IDispatch interface, see the COM documentation. A program that wants to invoke a method provided by an automation object first calls IDispatch::GetIDsOfNames() to get the numeric ID (type DISPID) associated with the desired method, and then passes that ID to IDispatch::Invoke() to actually invoke that method with the desired set of parameters. IDispatch also allows an object to return detailed type information about methods and parameters supported. I chose not to support type information, so my implementation of IDispatch::GetTypeInfoCount() and IDispatch::GetTypeInfo() both simply return E_NOTIMPL. That means that tools that rely on type information (such as object browsers or compilers) won't work with DynamicWrapper objects, but that's okay since I'm really targeting VBScript and similar environments that don't rely on accessing object type information.
The CDynamicWrapper implementations of GetIDsOfNames() and Invoke() rely primarily on an instance of the CDynCallChain class. This class is a linked list of CDynCall objects. The CDynCall class manages the information required to dynamically call a single registered procedure. When GetIDsofNames() is called, the CDynamicWrapper class defers to the CDynCallChain to find the DISPID of the given procedure name. When Invoke() is called, CDynamicWrapper again defers to the CDynCallChain instance, which locates and calls Invoke() on the appropriate CDynCall instance.
The Registermethod causes CDynCallChain to create a new instance of CDynCall. It then calls CDynCall::Register(), which uses the input parameters to determine the DLL name and procedure name. The DLL is loaded and the procedure address located using SearchProcAddress(). The tags describing the input parameters, calling convention, and return type are then parsed. The tag parsing is table-driven and is easily modified to support additional parameter types. Finally, an unused DISPID is assigned to the new method. If the CDynCall instance successfully completes all of the proceeding steps, CDynCallChain adds the object to its linked list.
When a client wants to call a previously registered procedure, say "MessageBoxA", it first passes that name to GetIDsOfNames(). The CDynCallChain instance searches its linked list for a CDynCall instance that matches the given procedure name and returns its DISPID. The client then calls Invoke(). The CDynCallChain instance again searches its linked list for a CDynCall instance that matches the given DISPID, then calls CDynCall::Invoke().
CDynCall::Invoke() parses the parameter list passed as an array of DISPPARAM structures. The previously stored input parameter information is used to convert the data in each DISPPARAM to the data type required by the exported DLL function. The exported DLL function is then called using Ton Plooy's DynaCall().DynaCall() returns a structure containing the return value. This value is packaged appropriately and returned to the client.
The remaining classes and functions in the file are boilerplate code required to properly implement an in-proc COM server. See the COM documentation for further information on in-proc COM servers. Conclusion
The DynamicWrapper object provides an enhancement to VBScript that is familiar to Visual Basic programmers and easy to use. In addition, the implementation of the DynamicWrapper object provides some useful insights into the flexibility of the OLE automation object model.