From e1378f8f51da7f7662e8d52bbaaaaef0534dab5b Mon Sep 17 00:00:00 2001 From: Thomas Geppert Date: Fri, 3 May 2024 19:17:36 +0200 Subject: [PATCH 01/11] Implement record pointers as method parameters of a Dispatch Interface to get server side modifications of a record marshaled back to the client. --- comtypes/automation.py | 28 ++++++++++++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/comtypes/automation.py b/comtypes/automation.py index d864519f..664caa31 100644 --- a/comtypes/automation.py +++ b/comtypes/automation.py @@ -393,12 +393,36 @@ def _set_value(self, value): ref = value._obj self._.c_void_p = addressof(ref) self.__keepref = value - self.vt = _ctype_to_vartype[type(ref)] | VT_BYREF + if isinstance(ref, Structure) and hasattr(ref, "_recordinfo_"): + guids = ref._recordinfo_ + from comtypes.typeinfo import GetRecordInfoFromGuids + + ri = GetRecordInfoFromGuids(*guids) + self.vt = VT_RECORD | VT_BYREF + # Assigning a COM pointer to a structure field does NOT + # call AddRef(), have to call it manually: + ri.AddRef() + self._.pRecInfo = ri + self._.pvRecord = cast(value, c_void_p) + else: + self.vt = _ctype_to_vartype[type(ref)] | VT_BYREF elif isinstance(value, _Pointer): ref = value.contents self._.c_void_p = addressof(ref) self.__keepref = value - self.vt = _ctype_to_vartype[type(ref)] | VT_BYREF + if isinstance(ref, Structure) and hasattr(ref, "_recordinfo_"): + guids = ref._recordinfo_ + from comtypes.typeinfo import GetRecordInfoFromGuids + + ri = GetRecordInfoFromGuids(*guids) + self.vt = VT_RECORD | VT_BYREF + # Assigning a COM pointer to a structure field does NOT + # call AddRef(), have to call it manually: + ri.AddRef() + self._.pRecInfo = ri + self._.pvRecord = cast(value, c_void_p) + else: + self.vt = _ctype_to_vartype[type(ref)] | VT_BYREF else: raise TypeError("Cannot put %r in VARIANT" % value) # buffer -> SAFEARRAY of VT_UI1 ? From 22daf9a2112da63f7871dfdea292a5b7111f99c5 Mon Sep 17 00:00:00 2001 From: geppi Date: Wed, 22 May 2024 21:04:02 +0200 Subject: [PATCH 02/11] Adding the source files for a simple out of process COM-server under 'source/OutProcSrv'. The COM-server implements a dual interface and the corresponding dispinterface with currently just a single method 'InitRecord' for testing record pointer parameters. The added unittest 'test_dispifc_recordptr.py' passes a record by reference and also as a record pointer to the 'InitRecord' method of the dispinterface and checks the initialized records. --- comtypes/test/test_dispifc_recordptr.py | 58 ++++ source/OutProcSrv/CFACTORY.CPP | 281 +++++++++++++++++++ source/OutProcSrv/CFACTORY.H | 150 +++++++++++ source/OutProcSrv/CMPNT.CPP | 243 +++++++++++++++++ source/OutProcSrv/CMPNT.H | 75 ++++++ source/OutProcSrv/CUNKNOWN.CPP | 131 +++++++++ source/OutProcSrv/CUNKNOWN.H | 104 +++++++ source/OutProcSrv/MAKEFILE | 88 ++++++ source/OutProcSrv/OUTPROC.CPP | 280 +++++++++++++++++++ source/OutProcSrv/REGISTRY.CPP | 345 ++++++++++++++++++++++++ source/OutProcSrv/REGISTRY.H | 31 +++ source/OutProcSrv/SERVER.CPP | 46 ++++ source/OutProcSrv/SERVER.IDL | 64 +++++ source/OutProcSrv/UTIL.CPP | 109 ++++++++ source/OutProcSrv/UTIL.H | 31 +++ 15 files changed, 2036 insertions(+) create mode 100644 comtypes/test/test_dispifc_recordptr.py create mode 100644 source/OutProcSrv/CFACTORY.CPP create mode 100644 source/OutProcSrv/CFACTORY.H create mode 100644 source/OutProcSrv/CMPNT.CPP create mode 100644 source/OutProcSrv/CMPNT.H create mode 100644 source/OutProcSrv/CUNKNOWN.CPP create mode 100644 source/OutProcSrv/CUNKNOWN.H create mode 100644 source/OutProcSrv/MAKEFILE create mode 100644 source/OutProcSrv/OUTPROC.CPP create mode 100644 source/OutProcSrv/REGISTRY.CPP create mode 100644 source/OutProcSrv/REGISTRY.H create mode 100644 source/OutProcSrv/SERVER.CPP create mode 100644 source/OutProcSrv/SERVER.IDL create mode 100644 source/OutProcSrv/UTIL.CPP create mode 100644 source/OutProcSrv/UTIL.H diff --git a/comtypes/test/test_dispifc_recordptr.py b/comtypes/test/test_dispifc_recordptr.py new file mode 100644 index 00000000..fb6eccb3 --- /dev/null +++ b/comtypes/test/test_dispifc_recordptr.py @@ -0,0 +1,58 @@ +# coding: utf-8 + +import unittest + +from comtypes import CLSCTX_LOCAL_SERVER +from comtypes.client import CreateObject, GetModule +from ctypes import byref, pointer + +ComtypesTestLib_GUID = "07D2AEE5-1DF8-4D2C-953A-554ADFD25F99" +ProgID = "ComtypesTest.COM.Server" + +try: + GetModule([f"{{{ComtypesTestLib_GUID}}}", 1, 0, 0]) + import comtypes.gen.ComtypesTestLib as ComtypesTestLib + + IMPORT_FAILED = False +except (ImportError, OSError): + IMPORT_FAILED = True + + +@unittest.skipIf(IMPORT_FAILED, "This depends on the out of process COM-server.") +class Test(unittest.TestCase): + """Test dispmethods with record pointer parameters.""" + + def test(self): + # Explicitely ask for the dispinterface of the COM-server. + dispifc = CreateObject( + ProgID, clsctx=CLSCTX_LOCAL_SERVER, interface=ComtypesTestLib.IComtypesTest + ) + + # Test passing a record by reference. + test_record = ComtypesTestLib.T_TEST_RECORD() + self.assertEqual(test_record.question, None) + self.assertEqual(test_record.answer, 0) + self.assertEqual(test_record.needs_clarification, False) + dispifc.InitRecord(byref(test_record)) + self.assertEqual( + test_record.question, "The meaning of life, the universe and everything?" + ) + self.assertEqual(test_record.answer, 42) + self.assertEqual(test_record.needs_clarification, True) + + # Test passing a record pointer. + test_record = ComtypesTestLib.T_TEST_RECORD() + self.assertEqual(test_record.question, None) + self.assertEqual(test_record.answer, 0) + self.assertEqual(test_record.needs_clarification, False) + test_record_pointer = pointer(test_record) + dispifc.InitRecord(test_record_pointer) + self.assertEqual( + test_record.question, "The meaning of life, the universe and everything?" + ) + self.assertEqual(test_record.answer, 42) + self.assertEqual(test_record.needs_clarification, True) + + +if __name__ == "__main__": + unittest.main() diff --git a/source/OutProcSrv/CFACTORY.CPP b/source/OutProcSrv/CFACTORY.CPP new file mode 100644 index 00000000..a618767d --- /dev/null +++ b/source/OutProcSrv/CFACTORY.CPP @@ -0,0 +1,281 @@ +/* + This code is based on example code to the book: + Inside COM + by Dale E. Rogerson + Microsoft Press 1997 + ISBN 1-57231-349-8 +*/ + +/////////////////////////////////////////////////////////// +// +// CFactory +// - Base class for reusing a single class factory for +// all components in a DLL +// +#include + +#include "Registry.h" +#include "CFactory.h" + +/////////////////////////////////////////////////////////// +// +// Static variables +// +LONG CFactory::s_cServerLocks = 0 ; // Count of locks + +HMODULE CFactory::s_hModule = NULL ; // DLL module handle + +DWORD CFactory::s_dwThreadID = 0 ; + +/////////////////////////////////////////////////////////// +// +// CFactory implementation +// + +CFactory::CFactory(const CFactoryData* pFactoryData) +: m_cRef(1) +{ + m_pFactoryData = pFactoryData ; +} + +// +// IUnknown implementation +// +HRESULT __stdcall CFactory::QueryInterface(REFIID iid, void** ppv) +{ + IUnknown* pI ; + if ((iid == IID_IUnknown) || (iid == IID_IClassFactory)) + { + pI = this ; + } + else + { + *ppv = NULL; + return E_NOINTERFACE; + } + pI->AddRef() ; + *ppv = pI ; + return S_OK; +} + +ULONG __stdcall CFactory::AddRef() +{ + return ::InterlockedIncrement(&m_cRef) ; +} + +ULONG __stdcall CFactory::Release() +{ + if (::InterlockedDecrement(&m_cRef) == 0) + { + delete this; + return 0 ; + } + return m_cRef; +} + +// +// IClassFactory implementation +// + +HRESULT __stdcall CFactory::CreateInstance(IUnknown* pUnknownOuter, + const IID& iid, + void** ppv) +{ + + // Aggregate only if the requested IID is IID_IUnknown. + if ((pUnknownOuter != NULL) && (iid != IID_IUnknown)) + { + return CLASS_E_NOAGGREGATION ; + } + + // Create the component. + CUnknown* pNewComponent ; + HRESULT hr = m_pFactoryData->CreateInstance(pUnknownOuter, + &pNewComponent) ; + if (FAILED(hr)) + { + return hr ; + } + + // Initialize the component. + hr = pNewComponent->Init(); + if (FAILED(hr)) + { + // Initialization failed. Release the component. + pNewComponent->NondelegatingRelease() ; + return hr ; + } + + // Get the requested interface. + hr = pNewComponent->NondelegatingQueryInterface(iid, ppv) ; + + // Release the reference held by the class factory. + pNewComponent->NondelegatingRelease() ; + return hr ; +} + +// LockServer +HRESULT __stdcall CFactory::LockServer(BOOL bLock) +{ + if (bLock) + { + ::InterlockedIncrement(&s_cServerLocks) ; + } + else + { + ::InterlockedDecrement(&s_cServerLocks) ; + } + // If this is an out-of-proc server, check to see + // whether we should shut down. + CloseExe() ; //@local + + return S_OK ; +} + + +/////////////////////////////////////////////////////////// +// +// GetClassObject +// - Create a class factory based on a CLSID. +// +HRESULT CFactory::GetClassObject(const CLSID& clsid, + const IID& iid, + void** ppv) +{ + if ((iid != IID_IUnknown) && (iid != IID_IClassFactory)) + { + return E_NOINTERFACE ; + } + + // Traverse the array of data looking for this class ID. + for (int i = 0; i < g_cFactoryDataEntries; i++) + { + const CFactoryData* pData = &g_FactoryDataArray[i] ; + if (pData->IsClassID(clsid)) + { + + // Found the ClassID in the array of components we can + // create. So create a class factory for this component. + // Pass the CFactoryData structure to the class factory + // so that it knows what kind of components to create. + *ppv = (IUnknown*) new CFactory(pData) ; + if (*ppv == NULL) + { + return E_OUTOFMEMORY ; + } + return NOERROR ; + } + } + return CLASS_E_CLASSNOTAVAILABLE ; +} + +// +// Determine if the component can be unloaded. +// +HRESULT CFactory::CanUnloadNow() +{ + if (CUnknown::ActiveComponents() || IsLocked()) + { + return S_FALSE ; + } + else + { + return S_OK ; + } +} + +// +// Register all components. +// +HRESULT CFactory::RegisterAll() +{ + for(int i = 0 ; i < g_cFactoryDataEntries ; i++) + { + RegisterServer(s_hModule, + *(g_FactoryDataArray[i].m_pCLSID), + g_FactoryDataArray[i].m_RegistryName, + g_FactoryDataArray[i].m_szVerIndProgID, + g_FactoryDataArray[i].m_szProgID, + *(g_FactoryDataArray[i].m_pLIBID)) ; + } + return S_OK ; +} + +HRESULT CFactory::UnregisterAll() +{ + for(int i = 0 ; i < g_cFactoryDataEntries ; i++) + { + UnregisterServer(*(g_FactoryDataArray[i].m_pCLSID), + g_FactoryDataArray[i].m_szVerIndProgID, + g_FactoryDataArray[i].m_szProgID) ; + } + return S_OK ; +} + + +// +// Start factories +// +BOOL CFactory::StartFactories() +{ + CFactoryData* pStart = &g_FactoryDataArray[0] ; + const CFactoryData* pEnd = + &g_FactoryDataArray[g_cFactoryDataEntries - 1] ; + + for(CFactoryData* pData = pStart ; pData <= pEnd ; pData++) + { + // Initialize the class factory pointer and cookie. + pData->m_pIClassFactory = NULL ; + pData->m_dwRegister = NULL ; + + // Create the class factory for this component. + IClassFactory* pIFactory = new CFactory(pData) ; + + // Register the class factory. + DWORD dwRegister ; + HRESULT hr = ::CoRegisterClassObject( + *pData->m_pCLSID, + static_cast(pIFactory), + CLSCTX_LOCAL_SERVER, + REGCLS_MULTIPLEUSE, + // REGCLS_MULTI_SEPARATE, //@Multi + &dwRegister) ; + if (FAILED(hr)) + { + pIFactory->Release() ; + return FALSE ; + } + + // Set the data. + pData->m_pIClassFactory = pIFactory ; + pData->m_dwRegister = dwRegister ; + } + return TRUE ; +} + +// +// Stop factories +// +void CFactory::StopFactories() +{ + CFactoryData* pStart = &g_FactoryDataArray[0] ; + const CFactoryData* pEnd = + &g_FactoryDataArray[g_cFactoryDataEntries - 1] ; + + for (CFactoryData* pData = pStart ; pData <= pEnd ; pData++) + { + // Get the magic cookie and stop the factory from running. + DWORD dwRegister = pData->m_dwRegister ; + if (dwRegister != 0) + { + ::CoRevokeClassObject(dwRegister) ; + } + + // Release the class factory. + IClassFactory* pIFactory = pData->m_pIClassFactory ; + if (pIFactory != NULL) + { + pIFactory->Release() ; + } + } +} diff --git a/source/OutProcSrv/CFACTORY.H b/source/OutProcSrv/CFACTORY.H new file mode 100644 index 00000000..e942c956 --- /dev/null +++ b/source/OutProcSrv/CFACTORY.H @@ -0,0 +1,150 @@ +/* + This code is based on example code to the book: + Inside COM + by Dale E. Rogerson + Microsoft Press 1997 + ISBN 1-57231-349-8 +*/ + +#ifndef __CFactory_h__ +#define __CFactory_h__ + +#include "CUnknown.h" +/////////////////////////////////////////////////////////// + +// Forward reference +class CFactoryData ; + +// Global data used by CFactory +extern CFactoryData g_FactoryDataArray[] ; +extern int g_cFactoryDataEntries ; + +////////////////////////////////////////////////////////// +// +// Component creation function +// +class CUnknown ; + +typedef HRESULT (*FPCREATEINSTANCE)(IUnknown*, CUnknown**) ; + +/////////////////////////////////////////////////////////// +// +// CFactoryData +// - Information CFactory needs to create a component +// supported by the DLL +// +class CFactoryData +{ +public: + // The class ID for the component + const CLSID* m_pCLSID ; + + // Pointer to the function that creates it + FPCREATEINSTANCE CreateInstance ; + + // Name of the component to register in the registry + LPCWSTR m_RegistryName ; + + // ProgID + LPCWSTR m_szProgID ; + + // Version-independent ProgID + LPCWSTR m_szVerIndProgID ; + + // Helper function for finding the class ID + BOOL IsClassID(const CLSID& clsid) const + { return (*m_pCLSID == clsid) ;} + + // Type Library ID + const GUID* m_pLIBID ; + + // + // Out of process server support + // + + // Pointer to running class factory for this component + IClassFactory* m_pIClassFactory ; + + // Magic cookie to identify running object + DWORD m_dwRegister ; +} ; + + +/////////////////////////////////////////////////////////// +// +// Class Factory +// +class CFactory : public IClassFactory +{ +public: + // IUnknown + virtual HRESULT __stdcall QueryInterface(const IID& iid, void** ppv) ; + virtual ULONG __stdcall AddRef() ; + virtual ULONG __stdcall Release() ; + + // IClassFactory + virtual HRESULT __stdcall CreateInstance(IUnknown* pUnknownOuter, + const IID& iid, + void** ppv) ; + virtual HRESULT __stdcall LockServer(BOOL bLock) ; + + // Constructor - Pass pointer to data of component to create. + CFactory(const CFactoryData* pFactoryData) ; + + // Destructor + ~CFactory() { } + + // + // Static FactoryData support functions + // + + // DllGetClassObject support + static HRESULT GetClassObject(const CLSID& clsid, + const IID& iid, + void** ppv) ; + + // Helper function for DllCanUnloadNow + static BOOL IsLocked() + { return (s_cServerLocks > 0) ;} + + // Functions to [un]register all components + static HRESULT RegisterAll() ; + static HRESULT UnregisterAll() ; + + // Function to determine if component can be unloaded + static HRESULT CanUnloadNow() ; + + + // + // Out-of-process server support + // + + static BOOL StartFactories() ; + static void StopFactories() ; + + static DWORD s_dwThreadID ; + + // Shut down the application. + static void CloseExe() + { + if (CanUnloadNow() == S_OK) + { + ::PostThreadMessage(s_dwThreadID, WM_QUIT, 0, 0) ; + } + } + +public: + // Reference Count + LONG m_cRef ; + + // Pointer to information about class this factory creates + const CFactoryData* m_pFactoryData ; + + // Count of locks + static LONG s_cServerLocks ; + + // Module handle + static HMODULE s_hModule ; +} ; + +#endif diff --git a/source/OutProcSrv/CMPNT.CPP b/source/OutProcSrv/CMPNT.CPP new file mode 100644 index 00000000..38231f51 --- /dev/null +++ b/source/OutProcSrv/CMPNT.CPP @@ -0,0 +1,243 @@ +/* + This code is based on example code to the book: + Inside COM + by Dale E. Rogerson + Microsoft Press 1997 + ISBN 1-57231-349-8 +*/ + +// +// Cmpnt.cpp - Component +// +#include +#include +#include + +#include "Iface.h" +#include "Util.h" +#include "CUnknown.h" +#include "CFactory.h" // Needed for module handle +#include "Cmpnt.h" + +// We need to put this declaration here because we explicitely expose a dispinterface +// in parallel to the dual interface but dispinterfaces don't appear in the +// MIDL-generated header file. +EXTERN_C const IID DIID_IComtypesTest; + +static inline void trace(const char* msg) + { Util::Trace("Component", msg, S_OK) ;} +static inline void trace(const char* msg, HRESULT hr) + { Util::Trace("Component", msg, hr) ;} + +/////////////////////////////////////////////////////////// +// +// Interface IDualComtypesTest - Implementation +// + +HRESULT __stdcall CA::InitRecord(T_TEST_RECORD* test_record) +{ + // Display the received T_TEST_RECORD structure. + if (test_record->question == NULL){ + test_record->question = ::SysAllocString(L"") ; + } + std::ostringstream sout ; + sout << "Received T_TEST_RECORD structure contains: " << std::ends ; + trace(sout.str().c_str()) ; + sout.str("") ; + sout << "\n\t\t" << "question: " << test_record->question << std::ends ; + trace(sout.str().c_str()) ; + sout.str("") ; + sout << "\n\t\t" << "answer: " << test_record->answer << std::ends ; + trace(sout.str().c_str()) ; + sout.str("") ; + sout << "\n\t\t" << "needs_clarification: " << test_record->needs_clarification << std::ends ; + trace(sout.str().c_str()) ; + sout.str("") ; + + if (! ::SysReAllocString(&(test_record->question), L"The meaning of life, the universe and everything?")) + { + return E_OUTOFMEMORY ; + } + test_record->answer = 42 ; + test_record->needs_clarification = VARIANT_TRUE ; + + return S_OK ; +} + +// +// Constructor +// +CA::CA(IUnknown* pUnknownOuter) +: CUnknown(pUnknownOuter), + m_pITypeInfo(NULL) +{ + // Empty +} + +// +// Destructor +// +CA::~CA() +{ + if (m_pITypeInfo != NULL) + { + m_pITypeInfo->Release() ; + } + + trace("Destroy self.") ; +} + +// +// NondelegatingQueryInterface implementation +// +HRESULT __stdcall CA::NondelegatingQueryInterface(const IID& iid, + void** ppv) +{ + if (iid == IID_IDualComtypesTest) + { + return FinishQI(static_cast(this), ppv) ; + } + else if (iid == DIID_IComtypesTest) + { + trace("Queried for IComtypesTest.") ; + return FinishQI(static_cast(this), ppv) ; + } + else if (iid == IID_IDispatch) + { + trace("Queried for IDispatch.") ; + return FinishQI(static_cast(this), ppv) ; + } + else + { + return CUnknown::NondelegatingQueryInterface(iid, ppv) ; + } +} + +// +// Load and register the type library. +// +HRESULT CA::Init() +{ + HRESULT hr ; + + // Load TypeInfo on demand if we haven't already loaded it. + if (m_pITypeInfo == NULL) + { + ITypeLib* pITypeLib = NULL ; + hr = ::LoadRegTypeLib(LIBID_ComtypesTestLib, + 1, 0, // Major/Minor version numbers + 0x00, + &pITypeLib) ; + if (FAILED(hr)) + { + trace("LoadRegTypeLib Failed.", hr) ; + return hr ; + } + + // Get type information for the interface of the object. + hr = pITypeLib->GetTypeInfoOfGuid(IID_IDualComtypesTest, + &m_pITypeInfo) ; + pITypeLib->Release() ; + if (FAILED(hr)) + { + trace("GetTypeInfoOfGuid failed.", hr) ; + return hr ; + } + } + return S_OK ; +} + +/////////////////////////////////////////////////////////// +// +// Creation function used by CFactory +// +HRESULT CA::CreateInstance(IUnknown* pUnknownOuter, + CUnknown** ppNewComponent ) +{ + if (pUnknownOuter != NULL) + { + // Don't allow aggregation (just for the heck of it). + return CLASS_E_NOAGGREGATION ; + } + + *ppNewComponent = new CA(pUnknownOuter) ; + return S_OK ; +} + +/////////////////////////////////////////////////////////// +// +// IDispatch implementation +// +HRESULT __stdcall CA::GetTypeInfoCount(UINT* pCountTypeInfo) +{ + trace("GetTypeInfoCount call succeeded.") ; + *pCountTypeInfo = 1 ; + return S_OK ; +} + +HRESULT __stdcall CA::GetTypeInfo( + UINT iTypeInfo, + LCID, // This object does not support localization. + ITypeInfo** ppITypeInfo) +{ + *ppITypeInfo = NULL ; + + if(iTypeInfo != 0) + { + trace("GetTypeInfo call failed -- bad iTypeInfo index.") ; + return DISP_E_BADINDEX ; + } + + trace("GetTypeInfo call succeeded.") ; + + // Call AddRef and return the pointer. + m_pITypeInfo->AddRef() ; + *ppITypeInfo = m_pITypeInfo ; + return S_OK ; +} + +HRESULT __stdcall CA::GetIDsOfNames( + const IID& iid, + OLECHAR** arrayNames, + UINT countNames, + LCID, // Localization is not supported. + DISPID* arrayDispIDs) +{ + if (iid != IID_NULL) + { + trace("GetIDsOfNames call failed -- bad IID.") ; + return DISP_E_UNKNOWNINTERFACE ; + } + + trace("GetIDsOfNames call succeeded.") ; + HRESULT hr = m_pITypeInfo->GetIDsOfNames(arrayNames, + countNames, + arrayDispIDs) ; + return hr ; +} + +HRESULT __stdcall CA::Invoke( + DISPID dispidMember, + const IID& iid, + LCID, // Localization is not supported. + WORD wFlags, + DISPPARAMS* pDispParams, + VARIANT* pvarResult, + EXCEPINFO* pExcepInfo, + UINT* pArgErr) +{ + if (iid != IID_NULL) + { + trace("Invoke call failed -- bad IID.") ; + return DISP_E_UNKNOWNINTERFACE ; + } + + ::SetErrorInfo(0, NULL) ; + + trace("Invoke call succeeded.") ; + HRESULT hr = m_pITypeInfo->Invoke( + static_cast(this), + dispidMember, wFlags, pDispParams, + pvarResult, pExcepInfo, pArgErr) ; + return hr ; +} diff --git a/source/OutProcSrv/CMPNT.H b/source/OutProcSrv/CMPNT.H new file mode 100644 index 00000000..12ce5bc7 --- /dev/null +++ b/source/OutProcSrv/CMPNT.H @@ -0,0 +1,75 @@ +/* + This code is based on example code to the book: + Inside COM + by Dale E. Rogerson + Microsoft Press 1997 + ISBN 1-57231-349-8 +*/ + +// +// Cmpnt.cpp - Component +// + +#include "Iface.h" +#include "CUnknown.h" + +/////////////////////////////////////////////////////////// +// +// Component A +// +class CA : public CUnknown, + public IDualComtypesTest +{ +public: + // Creation + static HRESULT CreateInstance(IUnknown* pUnknownOuter, + CUnknown** ppNewComponent ) ; + +private: + // Declare the delegating IUnknown. + DECLARE_IUNKNOWN + + // IUnknown + virtual HRESULT __stdcall NondelegatingQueryInterface(const IID& iid, + void** ppv) ; + + // IDispatch + virtual HRESULT __stdcall GetTypeInfoCount(UINT* pCountTypeInfo) ; + + virtual HRESULT __stdcall GetTypeInfo( + UINT iTypeInfo, + LCID, // Localization is not supported. + ITypeInfo** ppITypeInfo) ; + + virtual HRESULT __stdcall GetIDsOfNames( + const IID& iid, + OLECHAR** arrayNames, + UINT countNames, + LCID, // Localization is not supported. + DISPID* arrayDispIDs) ; + + virtual HRESULT __stdcall Invoke( + DISPID dispidMember, + const IID& iid, + LCID, // Localization is not supported. + WORD wFlags, + DISPPARAMS* pDispParams, + VARIANT* pvarResult, + EXCEPINFO* pExcepInfo, + UINT* pArgErr) ; + + // Interface IDualComtypesTest + virtual HRESULT __stdcall InitRecord(T_TEST_RECORD* test_record) ; + + // Initialization + virtual HRESULT Init() ; + + // Constructor + CA(IUnknown* pUnknownOuter) ; + + // Destructor + ~CA() ; + + // Pointer to type information. + ITypeInfo* m_pITypeInfo ; +} ; diff --git a/source/OutProcSrv/CUNKNOWN.CPP b/source/OutProcSrv/CUNKNOWN.CPP new file mode 100644 index 00000000..69edc00d --- /dev/null +++ b/source/OutProcSrv/CUNKNOWN.CPP @@ -0,0 +1,131 @@ +/* + This code is based on example code to the book: + Inside COM + by Dale E. Rogerson + Microsoft Press 1997 + ISBN 1-57231-349-8 +*/ + +/////////////////////////////////////////////////////////// +// +// CUnknown.cpp +// +// Implementation of IUnknown Base class +// +#include "CUnknown.h" +#include "CFactory.h" +#include "Util.h" + +static inline void trace(char* msg) + {Util::Trace("CUnknown", msg, S_OK) ;} +static inline void trace(char* msg, HRESULT hr) + {Util::Trace("CUnknown", msg, hr) ;} + +/////////////////////////////////////////////////////////// +// +// Count of active objects +// - Use to determine if we can unload the DLL. +// +long CUnknown::s_cActiveComponents = 0 ; + + +/////////////////////////////////////////////////////////// +// +// Constructor +// +CUnknown::CUnknown(IUnknown* pUnknownOuter) +: m_cRef(1) +{ + // Set m_pUnknownOuter pointer. + if (pUnknownOuter == NULL) + { + trace("Not aggregating; delegate to nondelegating IUnknown.") ; + m_pUnknownOuter = reinterpret_cast + (static_cast + (this)) ; // notice cast + } + else + { + trace("Aggregating; delegate to outer IUnknown.") ; + m_pUnknownOuter = pUnknownOuter ; + } + + // Increment count of active components. + ::InterlockedIncrement(&s_cActiveComponents) ; +} + +// +// Destructor +// +CUnknown::~CUnknown() +{ + ::InterlockedDecrement(&s_cActiveComponents) ; + + // If this is an EXE server, shut it down. + CFactory::CloseExe() ; +} + +// +// FinalRelease - called by Release before it deletes the component +// +void CUnknown::FinalRelease() +{ + trace("Increment reference count for final release.") ; + m_cRef = 1 ; +} + +// +// Nondelegating IUnknown +// - Override to handle custom interfaces. +// +HRESULT __stdcall + CUnknown::NondelegatingQueryInterface(const IID& iid, void** ppv) +{ + // CUnknown supports only IUnknown. + if (iid == IID_IUnknown) + { + return FinishQI(reinterpret_cast + (static_cast(this)), + ppv) ; + } + else + { + *ppv = NULL ; + return E_NOINTERFACE ; + } +} + +// +// AddRef +// +ULONG __stdcall CUnknown::NondelegatingAddRef() +{ + return InterlockedIncrement(&m_cRef) ; +} + +// +// Release +// +ULONG __stdcall CUnknown::NondelegatingRelease() +{ + InterlockedDecrement(&m_cRef) ; + if (m_cRef == 0) + { + FinalRelease() ; + delete this ; + return 0 ; + } + return m_cRef ; +} + +// +// FinishQI +// - Helper function to simplify overriding +// NondelegatingQueryInterface +// +HRESULT CUnknown::FinishQI(IUnknown* pI, void** ppv) +{ + *ppv = pI ; + pI->AddRef() ; + return S_OK ; +} diff --git a/source/OutProcSrv/CUNKNOWN.H b/source/OutProcSrv/CUNKNOWN.H new file mode 100644 index 00000000..ff995858 --- /dev/null +++ b/source/OutProcSrv/CUNKNOWN.H @@ -0,0 +1,104 @@ +/* + This code is based on example code to the book: + Inside COM + by Dale E. Rogerson + Microsoft Press 1997 + ISBN 1-57231-349-8 +*/ + +#ifndef __CUnknown_h__ +#define __CUnknown_h__ + +#include + +/////////////////////////////////////////////////////////// +// +// Nondelegating IUnknown interface +// - Nondelegating version of IUnknown +// +interface INondelegatingUnknown +{ + virtual HRESULT __stdcall + NondelegatingQueryInterface(const IID& iid, void** ppv) = 0 ; + virtual ULONG __stdcall NondelegatingAddRef() = 0 ; + virtual ULONG __stdcall NondelegatingRelease() = 0 ; +} ; + + +/////////////////////////////////////////////////////////// +// +// Declaration of CUnknown +// - Base class for implementing IUnknown +// + +class CUnknown : public INondelegatingUnknown +{ +public: + // Nondelegating IUnknown implementation + virtual HRESULT __stdcall NondelegatingQueryInterface(const IID&, + void**) ; + virtual ULONG __stdcall NondelegatingAddRef() ; + virtual ULONG __stdcall NondelegatingRelease() ; + + // Constructor + CUnknown(IUnknown* pUnknownOuter) ; + + // Destructor + virtual ~CUnknown() ; + + // Initialization (especially for aggregates) + virtual HRESULT Init() { return S_OK ;} + + // Notification to derived classes that we are releasing + virtual void FinalRelease() ; + + // Count of currently active components + static long ActiveComponents() + { return s_cActiveComponents ;} + + // Helper function + HRESULT FinishQI(IUnknown* pI, void** ppv) ; + +protected: + // Support for delegation + IUnknown* GetOuterUnknown() const + { return m_pUnknownOuter ;} + +private: + // Reference count for this object + long m_cRef ; + + // Pointer to (external) outer IUnknown + IUnknown* m_pUnknownOuter ; + + // Count of all active instances + static long s_cActiveComponents ; +} ; + + +/////////////////////////////////////////////////////////// +// +// Delegating IUnknown +// - Delegates to the nondelegating IUnknown, or to the +// outer IUnknown if the component is aggregated. +// +#define DECLARE_IUNKNOWN \ + virtual HRESULT __stdcall \ + QueryInterface(const IID& iid, void** ppv) \ + { \ + return GetOuterUnknown()->QueryInterface(iid,ppv) ; \ + } ; \ + virtual ULONG __stdcall AddRef() \ + { \ + return GetOuterUnknown()->AddRef() ; \ + } ; \ + virtual ULONG __stdcall Release() \ + { \ + return GetOuterUnknown()->Release() ; \ + } ; + + +/////////////////////////////////////////////////////////// + + +#endif diff --git a/source/OutProcSrv/MAKEFILE b/source/OutProcSrv/MAKEFILE new file mode 100644 index 00000000..8f6bf065 --- /dev/null +++ b/source/OutProcSrv/MAKEFILE @@ -0,0 +1,88 @@ +# +# Builds the out-of-proc (local server) version of component. +# Call with: nmake -f makefile. +# +# +!MESSAGE Building local out-of-proc server. +TARGETS = server.exe + +# +# Flags - Always compiles debug. +# +CPP_FLAGS = /c /MTd /Zi /Od /D_DEBUG /DUNICODE /EHsc +EXE_LINK_FLAGS = /DEBUG /NODEFAULTLIB:LIBCMT + +LIBS = kernel32.lib uuid.lib advapi32.lib ole32.lib oleaut32.lib + +################################################# +# +# Targets +# + +all : $(TARGETS) + +################################################# +# +# Proxy source files +# +iface.h server.tlb proxy.c guids.c dlldata.c : server.idl + midl /h iface.h /iid guids.c /proxy proxy.c server.idl + +################################################# +# +# Shared source files +# + +guids.obj : guids.c + cl /c /DWIN32 /DUNICODE /DREGISTER_PROXY_DLL guids.c + +################################################# +# +# Component/server source files +# + +server.obj : server.cpp cunknown.h cfactory.h iface.h + cl $(CPP_FLAGS) server.cpp + +cmpnt.obj : cmpnt.cpp cmpnt.h iface.h registry.h \ + CUnknown.h + cl $(CPP_FLAGS) cmpnt.cpp + +# +# Helper classes +# + +CUnknown.obj : CUnknown.cpp CUnknown.h + cl $(CPP_FLAGS) CUnknown.cpp + +CFactory.obj : CFactory.cpp CFactory.h + cl $(CPP_FLAGS) CFactory.cpp + +registry.obj : registry.cpp registry.h + cl $(CPP_FLAGS) registry.cpp + +# util.cpp compiled for server. +util.obj : util.cpp util.h + cl $(CPP_FLAGS) util.cpp + +outproc.obj : outproc.cpp CFactory.h CUnknown.h + cl $(CPP_FLAGS) outproc.cpp + + +################################################# +# +# Link component - Automatically register component. +# + +SERVER_OBJS = Server.obj \ + Cmpnt.obj \ + Registry.obj \ + Cfactory.obj \ + Cunknown.obj \ + Util.obj \ + Guids.obj + +Server.exe: $(SERVER_OBJS) outproc.obj + link $(EXE_LINK_FLAGS) $(SERVER_OBJS) \ + outproc.obj libcmtd.lib \ + libcpmtd.lib $(LIBS) user32.lib gdi32.lib diff --git a/source/OutProcSrv/OUTPROC.CPP b/source/OutProcSrv/OUTPROC.CPP new file mode 100644 index 00000000..a2d1fce2 --- /dev/null +++ b/source/OutProcSrv/OUTPROC.CPP @@ -0,0 +1,280 @@ +/* + This code is based on example code to the book: + Inside COM + by Dale E. Rogerson + Microsoft Press 1997 + ISBN 1-57231-349-8 +*/ + +#include +#include +#include + +#include "CUnknown.h" +#include "CFactory.h" + +/////////////////////////////////////////////////////////// +// +// Function to retrieving the Last-Error Code. +// Copied from: +// https://learn.microsoft.com/en-us/windows/win32/Debug/retrieving-the-last-error-code +// +void ErrorExit(LPTSTR lpszFunction) +{ + // Retrieve the system error message for the last-error code + + LPVOID lpMsgBuf; + LPVOID lpDisplayBuf; + DWORD dw = GetLastError(); + + FormatMessage( + FORMAT_MESSAGE_ALLOCATE_BUFFER | + FORMAT_MESSAGE_FROM_SYSTEM | + FORMAT_MESSAGE_IGNORE_INSERTS, + NULL, + dw, + MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), + (LPTSTR) &lpMsgBuf, + 0, NULL ); + + // Display the error message and exit the process + + lpDisplayBuf = (LPVOID)LocalAlloc(LMEM_ZEROINIT, + (lstrlen((LPCTSTR)lpMsgBuf) + lstrlen((LPCTSTR)lpszFunction) + 40) * sizeof(TCHAR)); + StringCchPrintf((LPTSTR)lpDisplayBuf, + LocalSize(lpDisplayBuf) / sizeof(TCHAR), + TEXT("%s failed with error %d: %s"), + lpszFunction, dw, lpMsgBuf); + MessageBox(NULL, (LPCTSTR)lpDisplayBuf, TEXT("Error"), MB_OK); + + LocalFree(lpMsgBuf); + LocalFree(lpDisplayBuf); +} + +/////////////////////////////////////////////////////////// +// +// Outproc.cpp +// - the component server +// +HWND g_hWndListBox = NULL ; + +BOOL InitWindow(int nCmdShow) ; +extern "C" LONG APIENTRY MainWndProc(HWND hWnd, + UINT message, + WPARAM wParam, + LPARAM lParam) ; + + +// +// WinMain procedure +// +extern "C" int WINAPI WinMain(HINSTANCE hInstance, + HINSTANCE hPrevInstance, + LPSTR lpCmdLine, + int nCmdShow) +{ + // Controls whether UI is shown or not + BOOL bUI = TRUE ; + + // If TRUE, don't loop. + BOOL bExit = FALSE ; + + // Initialize the COM Library. + HRESULT hr = OleInitialize(NULL) ; + if (FAILED(hr)) + { + return 0 ; + } + + + // Get Thread ID. + CFactory::s_dwThreadID = ::GetCurrentThreadId() ; + CFactory::s_hModule = hInstance ; + + // Read the command line. + char szTokens[] = "-/" ; + + char* szToken = strtok(lpCmdLine, szTokens) ; + while (szToken != NULL) + { + if (_stricmp(szToken, "UnregServer") == 0) + { + CFactory::UnregisterAll() ; + // MessageBox(0,L"Successfully removed COM-server registration.", L"server.exe /UnregServer", MB_OK) ; + // We are done, so exit. + bExit = TRUE ; + bUI = FALSE ; + } + else if (_stricmp(szToken, "RegServer") == 0) + { + CFactory::RegisterAll() ; + // MessageBox(0,L"Successfully registered COM-server", L"server.exe /RegServer", MB_OK) ; + // We are done, so exit. + bExit = TRUE ; + bUI = FALSE ; + } + else if (_stricmp(szToken, "Embedding") == 0) + { + // Don't display a window if we are embedded. + bUI = FALSE ; + break ; + } + szToken = strtok(NULL, szTokens) ; + } + + // If the user started us, then show UI. + if (bUI) + { + if (!InitWindow(nCmdShow)) + { + // Exit since we can't show UI. + bExit = TRUE ; + } + else + { + ::InterlockedIncrement(&CFactory::s_cServerLocks) ; + } + } + + if (!bExit) + { + // Register all of the class factories. + CFactory::StartFactories() ; + + // Wait for shutdown. + MSG msg ; + while (::GetMessage(&msg, 0, 0, 0)) + { + ::DispatchMessage(&msg) ; + } + + // Unregister the class factories. + CFactory::StopFactories() ; + } + + // Uninitialize the COM Library. + OleUninitialize() ; + return 0 ; +} + + +// +// Initialize window +// +BOOL InitWindow(int nCmdShow) +{ + // Fill in window class structure with parameters + // that describe the main window. + WNDCLASS wcListview ; + wcListview.style = 0 ; + wcListview.lpfnWndProc = (WNDPROC)MainWndProc ; + wcListview.cbClsExtra = 0 ; + wcListview.cbWndExtra = 0 ; + wcListview.hInstance = CFactory::s_hModule ; + wcListview.hIcon = ::LoadIcon(NULL, IDI_APPLICATION) ; + wcListview.hCursor = ::LoadCursor(NULL, IDC_ARROW) ; + wcListview.hbrBackground = static_cast(::GetStockObject(WHITE_BRUSH)) ; + wcListview.lpszMenuName = NULL ; + wcListview.lpszClassName = L"MyServerWinClass" ; + + BOOL bResult = ::RegisterClass(&wcListview) ; + if (!bResult) + { + return bResult ; + } + + HWND hWndMain ; + + hWndMain = ::CreateWindow( + L"MyServerWinClass", + L"Component Server", + WS_OVERLAPPEDWINDOW, + CW_USEDEFAULT, CW_USEDEFAULT, + CW_USEDEFAULT, CW_USEDEFAULT, + NULL, + NULL, + CFactory::s_hModule, + NULL) ; + + // If window could not be created, return "failure". + if (!hWndMain) + { + ErrorExit(TEXT("CreateWindow")); + return FALSE ; + } + + // Make the window visible; update its client area; + // and return "success". + ::ShowWindow(hWndMain, nCmdShow) ; + ::UpdateWindow(hWndMain) ; + return TRUE ; +} + +// +// Main window procedure +// +extern "C" LONG APIENTRY MainWndProc( + HWND hWnd, // window handle + UINT message, // type of message + WPARAM wParam, // additional information + LPARAM lParam) // additional information +{ + DWORD dwStyle ; + + switch (message) + { + case WM_CREATE: + { + // Get size of main window + CREATESTRUCT* pcs = (CREATESTRUCT*) lParam ; + + // Create a listbox for output. + g_hWndListBox = ::CreateWindow( + L"LISTBOX", + NULL, + WS_CHILD | WS_VISIBLE | LBS_USETABSTOPS + | WS_VSCROLL | LBS_NOINTEGRALHEIGHT, + 0, 0, pcs->cx, pcs->cy, + hWnd, + NULL, + CFactory::s_hModule, + NULL) ; + if (g_hWndListBox == NULL) + { + // Listbox not created. + ::MessageBox(NULL, + L"Listbox not created!", + NULL, + MB_OK) ; + return -1 ; + } + } + break ; + + case WM_SIZE: + ::MoveWindow(g_hWndListBox, 0, 0, + LOWORD(lParam), HIWORD(lParam), TRUE) ; + break; + + case WM_DESTROY: // message: window being destroyed + if (CFactory::CanUnloadNow() == S_OK) + { + // Only post the quit message, if there is + // no one using the program. + ::PostQuitMessage(0) ; + } + break ; + + case WM_CLOSE: + // Decrement the lock count. + ::InterlockedDecrement(&CFactory::s_cServerLocks) ; + + // The list box is going away. + g_hWndListBox = NULL ; + + //Fall through + default: + return (DefWindowProc(hWnd, message, wParam, lParam)) ; + } + return 0 ; +} diff --git a/source/OutProcSrv/REGISTRY.CPP b/source/OutProcSrv/REGISTRY.CPP new file mode 100644 index 00000000..3f7cf50e --- /dev/null +++ b/source/OutProcSrv/REGISTRY.CPP @@ -0,0 +1,345 @@ +/* + This code is based on example code to the book: + Inside COM + by Dale E. Rogerson + Microsoft Press 1997 + ISBN 1-57231-349-8 +*/ + +// +// Registry.cpp +// + +#include +#include +#include // sprintf +#include // splitpath + +#include "Iface.h" +#include "Registry.h" + +//////////////////////////////////////////////////////// +// +// Internal helper functions prototypes +// + +// Set the given key and its value. +BOOL setKeyAndValue(LPCWSTR pszPath, + LPCWSTR szSubkey, + LPCWSTR szValue) ; + +// Convert a GUID into a char string. +void GUIDtochar(const GUID& guid, + LPWSTR szGUID, + int length) ; + +// Determine if a particular subkey exists. +BOOL SubkeyExists(LPCWSTR pszPath, + LPCWSTR szSubkey) ; + +// Delete szKeyChild and all of its descendents. +LONG recursiveDeleteKey(HKEY hKeyParent, LPCWSTR szKeyChild) ; + +// +// Type library name +// +const wchar_t szTypeLibName[] = L"server.tlb" ; + +//////////////////////////////////////////////////////// +// +// Constants +// + +// Size of a GUID as a string +const int GUID_STRING_SIZE = 39 ; + +///////////////////////////////////////////////////////// +// +// Public function implementation +// + +// +// Register the component in the registry. +// +HRESULT RegisterServer(HMODULE hModule, // DLL module handle + const CLSID& clsid, // Class ID + LPCWSTR szFriendlyName, // Friendly Name + LPCWSTR szVerIndProgID, // Programmatic + LPCWSTR szProgID, // IDs + const GUID& libid) // Library ID +{ + // Get server location. + wchar_t szModule[512] ; + DWORD dwResult = + ::GetModuleFileName(hModule, + szModule, + sizeof(szModule)/sizeof(wchar_t)) ; + assert(dwResult != 0) ; + + // Convert the CLSID into a char. + wchar_t szCLSID[GUID_STRING_SIZE] ; + GUIDtochar(clsid, szCLSID, GUID_STRING_SIZE) ; + + // Build the key CLSID\\{...} + wchar_t szKey[64] ; + wcscpy(szKey, L"CLSID\\") ; + wcscat(szKey, szCLSID) ; + + // Add the CLSID to the registry. + setKeyAndValue(szKey, NULL, szFriendlyName) ; + + // Add the server filename subkey under the CLSID key. + setKeyAndValue(szKey, L"LocalServer32", szModule) ; + + // Add the ProgID subkey under the CLSID key. + setKeyAndValue(szKey, L"ProgID", szProgID) ; + + // Add the version-independent ProgID subkey under CLSID key. + setKeyAndValue(szKey, L"VersionIndependentProgID", + szVerIndProgID) ; + + // Add the Type Library ID subkey under the CLSID key. + wchar_t szLIBID[GUID_STRING_SIZE] ; + GUIDtochar(libid, szLIBID, GUID_STRING_SIZE) ; + setKeyAndValue(szKey, L"TypeLib", szLIBID) ; + + + // Add the version-independent ProgID subkey under HKEY_CLASSES_ROOT. + setKeyAndValue(szVerIndProgID, NULL, szFriendlyName) ; + setKeyAndValue(szVerIndProgID, L"CLSID", szCLSID) ; + setKeyAndValue(szVerIndProgID, L"CurVer", szProgID) ; + + // Add the versioned ProgID subkey under HKEY_CLASSES_ROOT. + setKeyAndValue(szProgID, NULL, szFriendlyName) ; + setKeyAndValue(szProgID, L"CLSID", szCLSID) ; + + // Register the Type Library. + ITypeLib* pITypeLib = NULL ; + // Try to load it from the path. + wchar_t szDrive[_MAX_DRIVE]; + wchar_t szDir[_MAX_DIR]; + // Split the fullname to get the pathname. + _wsplitpath(szModule, szDrive, szDir, NULL, NULL) ; + + // Append name of registry. + wchar_t szTypeLibFullName[_MAX_PATH]; + _swprintf(szTypeLibFullName, + L"%s%s%s", + szDrive, + szDir, + szTypeLibName) ; + + // convert to wide char + // wchar_t wszTypeLibFullName[_MAX_PATH] ; + // mbstowcs(wszTypeLibFullName, szTypeLibFullName, _MAX_PATH) ; + + // if LoadTypeLib succeeds, it will have registered + // the type library for us. + // for the next time. + HRESULT hr = ::LoadTypeLib(szTypeLibFullName, + &pITypeLib) ; + assert(hr == S_OK) ; + + // Ensure that the type library is registered. + hr = RegisterTypeLib(pITypeLib, szTypeLibFullName, NULL) ; + assert(hr == S_OK) ; + + return S_OK ; +} + +// +// Remove the component from the registry. +// +LONG UnregisterServer(const CLSID& clsid, // Class ID + LPCWSTR szVerIndProgID, // Programmatic + LPCWSTR szProgID) // IDs +{ + // Convert the CLSID into a char. + wchar_t szCLSID[GUID_STRING_SIZE] ; + GUIDtochar(clsid, szCLSID, GUID_STRING_SIZE) ; + + // Build the key CLSID\\{...} + wchar_t szKey[80] ; + wcscpy(szKey, L"CLSID\\") ; + wcscat(szKey, szCLSID) ; + + // Check for a another server for this component. + if (SubkeyExists(szKey, L"InprocServer32")) + { + // Delete only the path for this server. + wcscat(szKey, L"\\LocalServer32") ; + LONG lResult = recursiveDeleteKey(HKEY_CLASSES_ROOT, szKey) ; + assert(lResult == ERROR_SUCCESS) ; + } + else + { + // Delete all related keys. + // Delete the CLSID Key - CLSID\{...} + LONG lResult = recursiveDeleteKey(HKEY_CLASSES_ROOT, szKey) ; + assert((lResult == ERROR_SUCCESS) || + (lResult == ERROR_FILE_NOT_FOUND)) ; // Subkey may not exist. + + // Delete the version-independent ProgID Key. + lResult = recursiveDeleteKey(HKEY_CLASSES_ROOT, szVerIndProgID) ; + assert((lResult == ERROR_SUCCESS) || + (lResult == ERROR_FILE_NOT_FOUND)) ; // Subkey may not exist. + + // Delete the ProgID key. + lResult = recursiveDeleteKey(HKEY_CLASSES_ROOT, szProgID) ; + assert((lResult == ERROR_SUCCESS) || + (lResult == ERROR_FILE_NOT_FOUND)) ; // Subkey may not exist. + } + + // Unregister the Type Library. + HRESULT hr = UnRegisterTypeLib(LIBID_ComtypesTestLib, + 1, 0, // Major/Minor version numbers + 0x00, + SYS_WIN64) ; + assert(hr == S_OK) ; + + return S_OK ; +} + +/////////////////////////////////////////////////////////// +// +// Internal helper functions +// + +// Convert a GUID to a wchar_t string. +void GUIDtochar(const GUID& guid, + LPWSTR szGUID, + int length) +{ + assert(length >= GUID_STRING_SIZE) ; + // Get wide string version. + LPOLESTR wszGUID = NULL ; + HRESULT hr = StringFromCLSID(guid, &wszGUID) ; + assert(SUCCEEDED(hr)) ; + + // Covert from wide characters to non-wide. + // wcstombs(szGUID, wszGUID, length) ; + // Copy the retrieved string. + wcscpy_s(szGUID, length, wszGUID) ; + + + // Free memory. + CoTaskMemFree(wszGUID) ; +} + +// +// Delete a key and all of its descendents. +// +LONG recursiveDeleteKey(HKEY hKeyParent, // Parent of key to delete + LPCWSTR lpszKeyChild) // Key to delete +{ + // Open the child. + HKEY hKeyChild ; + LONG lRes = RegOpenKeyEx(hKeyParent, lpszKeyChild, 0, + KEY_ALL_ACCESS, &hKeyChild) ; + if (lRes != ERROR_SUCCESS) + { + return lRes ; + } + + // Enumerate all of the decendents of this child. + FILETIME time ; + wchar_t szBuffer[256] ; + DWORD dwSize = 256 ; + while (RegEnumKeyEx(hKeyChild, 0, szBuffer, &dwSize, NULL, + NULL, NULL, &time) == S_OK) + { + // Delete the decendents of this child. + lRes = recursiveDeleteKey(hKeyChild, szBuffer) ; + if (lRes != ERROR_SUCCESS) + { + // Cleanup before exiting. + RegCloseKey(hKeyChild) ; + return lRes; + } + dwSize = 256 ; + } + + // Close the child. + RegCloseKey(hKeyChild) ; + + // Delete this child. + return RegDeleteKey(hKeyParent, lpszKeyChild) ; +} + +// +// Determine if a particular subkey exists. +// +BOOL SubkeyExists(LPCWSTR pszPath, // Path of key to check + LPCWSTR szSubkey) // Key to check +{ + HKEY hKey ; + wchar_t szKeyBuf[80] ; + + // Copy keyname into buffer. + wcscpy(szKeyBuf, pszPath) ; + + // Add subkey name to buffer. + if (szSubkey != NULL) + { + wcscat(szKeyBuf, L"\\") ; + wcscat(szKeyBuf, szSubkey ) ; + } + + // Determine if key exists by trying to open it. + LONG lResult = ::RegOpenKeyEx(HKEY_CLASSES_ROOT, + szKeyBuf, + 0, + KEY_ALL_ACCESS, + &hKey) ; + if (lResult == ERROR_SUCCESS) + { + RegCloseKey(hKey) ; + return TRUE ; + } + return FALSE ; +} + +// +// Create a key and set its value. +// - This helper function was borrowed and modifed from +// Kraig Brockschmidt's book Inside OLE. +// +BOOL setKeyAndValue(LPCWSTR szKey, + LPCWSTR szSubkey, + LPCWSTR szValue) +{ + HKEY hKey; + wchar_t szKeyBuf[1024] ; + + // Copy keyname into buffer. + wcscpy(szKeyBuf, szKey) ; + + // Add subkey name to buffer. + if (szSubkey != NULL) + { + wcscat(szKeyBuf, L"\\") ; + wcscat(szKeyBuf, szSubkey ) ; + } + + // Create and open key and subkey. + long lResult = RegCreateKeyEx(HKEY_CLASSES_ROOT , + szKeyBuf, + 0, NULL, REG_OPTION_NON_VOLATILE, + KEY_ALL_ACCESS, NULL, + &hKey, NULL) ; + if (lResult != ERROR_SUCCESS) + { + return FALSE ; + } + + // Set the Value. + if (szValue != NULL) + { + RegSetValueEx(hKey, NULL, 0, REG_SZ, + (BYTE *)szValue, + sizeof(wchar_t) * (wcslen(szValue)+1)) ; + } + + RegCloseKey(hKey) ; + return TRUE ; +} diff --git a/source/OutProcSrv/REGISTRY.H b/source/OutProcSrv/REGISTRY.H new file mode 100644 index 00000000..cf3f807d --- /dev/null +++ b/source/OutProcSrv/REGISTRY.H @@ -0,0 +1,31 @@ +/* + This code is based on example code to the book: + Inside COM + by Dale E. Rogerson + Microsoft Press 1997 + ISBN 1-57231-349-8 +*/ + +#ifndef __Registry_H__ +#define __Registry_H__ +// +// Registry.h +// - Helper functions registering and unregistering a component. +// + +// This function will register a component in the Registry. +// The component calls this function from its DllRegisterServer function. +HRESULT RegisterServer(HMODULE hModule, + const CLSID& clsid, + LPCWSTR szFriendlyName, + LPCWSTR szVerIndProgID, + LPCWSTR szProgID, + const GUID& libid) ; + +// This function will unregister a component. Components +// call this function from their DllUnregisterServer function. +HRESULT UnregisterServer(const CLSID& clsid, + LPCWSTR szVerIndProgID, + LPCWSTR szProgID) ; + +#endif \ No newline at end of file diff --git a/source/OutProcSrv/SERVER.CPP b/source/OutProcSrv/SERVER.CPP new file mode 100644 index 00000000..3465b05e --- /dev/null +++ b/source/OutProcSrv/SERVER.CPP @@ -0,0 +1,46 @@ +/* + This code is based on example code to the book: + Inside COM + by Dale E. Rogerson + Microsoft Press 1997 + ISBN 1-57231-349-8 +*/ + +#include "CFactory.h" +#include "Iface.h" +#include "Cmpnt.h" + + +/////////////////////////////////////////////////////////// +// +// Server.cpp +// +// This file contains the component server code. +// The FactoryDataArray contains the components that +// can be served. +// + +// Each component derived from CUnknown defines a static function +// for creating the component with the following prototype. +// HRESULT CreateInstance(IUnknown* pUnknownOuter, +// CUnknown** ppNewComponent) ; +// This function is used to create the component. +// + +// +// The following array contains the data used by CFactory +// to create components. Each element in the array contains +// the CLSID, the pointer to the creation function, and the name +// of the component to place in the Registry. +// +CFactoryData g_FactoryDataArray[] = +{ + {&CLSID_Component, CA::CreateInstance, + L"Comtypes Test automation server component", // Friendly Name + L"ComtypesTest.COM.Server.1", // ProgID + L"ComtypesTest.COM.Server", // Version-independent ProgID + &LIBID_ComtypesTestLib, // Type Library ID + NULL, 0} +} ; +int g_cFactoryDataEntries + = sizeof(g_FactoryDataArray) / sizeof(CFactoryData) ; diff --git a/source/OutProcSrv/SERVER.IDL b/source/OutProcSrv/SERVER.IDL new file mode 100644 index 00000000..39d772ba --- /dev/null +++ b/source/OutProcSrv/SERVER.IDL @@ -0,0 +1,64 @@ +// +// Server.idl +// +// This file will be processed by the MIDL compiler to +// produce the type library (server.tlb) and marshaling code. +// + +import "oaidl.idl" ; + +typedef [uuid(00FABB0F-5691-41A6-B7C1-11606671F8E5)] +struct T_TEST_RECORD { + BSTR question ; + long answer ; + VARIANT_BOOL needs_clarification ; +} T_TEST_RECORD ; + +// Interface IDualComtypesTest +[ + odl, + uuid(0C4E01E8-4625-46A2-BC4C-2E889A8DBBD6), + dual, + helpstring("IDualComtypesTest Interface"), + nonextensible, + oleautomation +] +interface IDualComtypesTest : IDispatch +{ + [id(0x00000001)] + HRESULT InitRecord([in, out] T_TEST_RECORD* test_record) ; +} ; + +// Interface IComtypesTest +[ + uuid(033E4C10-0A7F-4E93-8377-499AD4B6583A) +] +dispinterface IComtypesTest +{ + interface IDualComtypesTest; +} ; + +// +// Component and type library descriptions +// +[ + uuid(07D2AEE5-1DF8-4D2C-953A-554ADFD25F99), + version(1.0), + helpstring("Comtypes Test COM Server 1.0 Type Library") +] +library ComtypesTestLib +{ + // TLib : OLE Automation : {00020430-0000-0000-C000-000000000046} + importlib("stdole2.tlb") ; + + // Component + [ + uuid(06571915-2431-4CA3-9C01-53002B060DAB), + helpstring("Component Class") + ] + coclass Component + { + [default] interface IDualComtypesTest ; + dispinterface IComtypesTest ; + } ; +} ; diff --git a/source/OutProcSrv/UTIL.CPP b/source/OutProcSrv/UTIL.CPP new file mode 100644 index 00000000..df8f3a4a --- /dev/null +++ b/source/OutProcSrv/UTIL.CPP @@ -0,0 +1,109 @@ +/* + This code is based on example code to the book: + Inside COM + by Dale E. Rogerson + Microsoft Press 1997 + ISBN 1-57231-349-8 +*/ + +// +// +// util.cpp - Common utilities for printing out messages +// +// +#include +#include //sprintf +#include +#include +// #include + +#include "util.h" + +// We are building a local server. + // Listbox window handle + extern HWND g_hWndListBox ; + + static inline void output(const char* sz) + { + size_t newsize = strlen(sz) + 1; + wchar_t* wcstring = new wchar_t[newsize]; + size_t convertedChars = 0; + mbstowcs_s(&convertedChars, wcstring, newsize, sz, _TRUNCATE); + ::SendMessage(g_hWndListBox, LB_ADDSTRING, 0, (LPARAM)wcstring) ; + delete []wcstring; + } + +// +// Utilities +// +namespace Util +{ + +// +// Print out a message with a label. +// +void Trace(const char* szLabel, const char* szText, HRESULT hr) +{ + char buf[256] ; + sprintf(buf, "%s: \t%s", szLabel, szText) ; + output(buf) ; + + if (FAILED(hr)) + { + ErrorMessage(hr) ; + } +} + +// +// Print out the COM/OLE error string for an HRESULT. +// +void ErrorMessage(HRESULT hr) +{ + LPTSTR pMsgBuf = NULL; + + ::FormatMessage( + FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM, + NULL, + hr, + MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // Default language + (LPTSTR)&pMsgBuf, + 0, + NULL + ) ; + + char buf[256] ; + int iLength = wcslen(pMsgBuf)+1 ; + char* psz = new char[iLength] ; + wcstombs(psz, pMsgBuf, iLength) ; + sprintf(buf, "Error (%x): %s", hr, psz) ; + output(buf) ; + + // Free the buffer. + LocalFree(pMsgBuf) ; +} + +} ; // End Namespace Util + + +// +// Overloaded ostream insertion operator +// Converts from wchar_t to char +// +std::ostream& operator<< ( std::ostream& os, const wchar_t* wsz ) +{ + // Length of incoming string + int iLength = wcslen(wsz)+1 ; + + // Allocate buffer for converted string. + char* psz = new char[iLength] ; + + // Convert from wchar_t to char. + wcstombs(psz, wsz, iLength) ; + + // Send it out. + os << psz ; + + // cleanup + delete [] psz ; + return os ; +} diff --git a/source/OutProcSrv/UTIL.H b/source/OutProcSrv/UTIL.H new file mode 100644 index 00000000..a923b2bd --- /dev/null +++ b/source/OutProcSrv/UTIL.H @@ -0,0 +1,31 @@ +/* + This code is based on example code to the book: + Inside COM + by Dale E. Rogerson + Microsoft Press 1997 + ISBN 1-57231-349-8 +*/ + +#ifndef __Util_h__ +#define __Util_h__ + +// +// Util.h - Shared utilities +// +#include + +namespace Util +{ + void Trace(const char* szLabel, const char* szText, HRESULT hr) ; + + void ErrorMessage(HRESULT hr) ; +} ; + + +// +// Overloaded insertion operator for converting from +// Unicode (wchar_t) to non-Unicode. +// +std::ostream& operator<< ( std::ostream& os, const wchar_t* wsz ) ; + +#endif // __Util_h__ \ No newline at end of file From c000104d64b74336bdd81d733b214f33adb2ce59 Mon Sep 17 00:00:00 2001 From: geppi Date: Thu, 23 May 2024 11:01:18 +0200 Subject: [PATCH 03/11] Modified the testing workflow to compile the out of process COM-server and register it. --- .github/workflows/autotest.yml | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/.github/workflows/autotest.yml b/.github/workflows/autotest.yml index 4acb56d1..3edabc2b 100644 --- a/.github/workflows/autotest.yml +++ b/.github/workflows/autotest.yml @@ -25,6 +25,17 @@ jobs: python setup.py install pip uninstall comtypes -y python test_pip_install.py + - name: Set up MSVC + uses: ilammy/msvc-dev-cmd@v1 + - name: Build and register the OutProc COM server + run: | + cd source/OutProcSrv + nmake /f Makefile + ./server.exe /RegServer - name: unittest comtypes run: | python -m unittest discover -v -s ./comtypes/test -t comtypes\test + - name: Unregister the OutProc COM server + run: | + cd source/OutProcSrv + ./server.exe /UnregServer \ No newline at end of file From 8b2fe44c35f95992734dd0b2ecb1eaf8840fcc15 Mon Sep 17 00:00:00 2001 From: Thomas Geppert Date: Sun, 26 May 2024 01:04:15 +0200 Subject: [PATCH 04/11] Added test for passing a record as a pure [in] parameter. --- ...c_recordptr.py => test_dispifc_records.py} | 31 ++++++++++++-- source/OutProcSrv/CMPNT.CPP | 40 +++++++++++++++++++ source/OutProcSrv/CMPNT.H | 3 ++ source/OutProcSrv/SERVER.IDL | 4 ++ 4 files changed, 75 insertions(+), 3 deletions(-) rename comtypes/test/{test_dispifc_recordptr.py => test_dispifc_records.py} (52%) diff --git a/comtypes/test/test_dispifc_recordptr.py b/comtypes/test/test_dispifc_records.py similarity index 52% rename from comtypes/test/test_dispifc_recordptr.py rename to comtypes/test/test_dispifc_records.py index fb6eccb3..0cab4e11 100644 --- a/comtypes/test/test_dispifc_recordptr.py +++ b/comtypes/test/test_dispifc_records.py @@ -23,12 +23,14 @@ class Test(unittest.TestCase): """Test dispmethods with record pointer parameters.""" def test(self): - # Explicitely ask for the dispinterface of the COM-server. + # Explicitely ask for the dispinterface of the component. dispifc = CreateObject( ProgID, clsctx=CLSCTX_LOCAL_SERVER, interface=ComtypesTestLib.IComtypesTest ) - # Test passing a record by reference. + # Passing a record by reference to a method that has declared the parameter + # as [in, out] we expect modifications of the record on the server side to + # also change the record on the client side. test_record = ComtypesTestLib.T_TEST_RECORD() self.assertEqual(test_record.question, None) self.assertEqual(test_record.answer, 0) @@ -40,7 +42,9 @@ def test(self): self.assertEqual(test_record.answer, 42) self.assertEqual(test_record.needs_clarification, True) - # Test passing a record pointer. + # Passing a record pointer to a method that has declared the parameter + # as [in, out] we expect modifications of the record on the server side to + # also change the record on the client side. test_record = ComtypesTestLib.T_TEST_RECORD() self.assertEqual(test_record.question, None) self.assertEqual(test_record.answer, 0) @@ -53,6 +57,27 @@ def test(self): self.assertEqual(test_record.answer, 42) self.assertEqual(test_record.needs_clarification, True) + # Passing a record to a method that has declared the parameter just as [in] + # we expect modifications of the record on the server side NOT to change + # the record on the client side. + # We also need to test if the record gets properly passed to the method on + # the server side. For this, the 'VerifyRecord' method returns 'True' if + # all record fields have the initialization values provided by 'InitRecord'. + self.assertTrue(dispifc.VerifyRecord(test_record)) + # Check if the 'answer' field is unchanged although the method modifies this + # field on the server side. + self.assertEqual(test_record.answer, 42) + # Also perform the inverted test. + # For this, first create a blank record. + test_record = ComtypesTestLib.T_TEST_RECORD() + self.assertEqual(test_record.question, None) + self.assertEqual(test_record.answer, 0) + self.assertEqual(test_record.needs_clarification, False) + # Perform the check on initialization values. + self.assertFalse(dispifc.VerifyRecord(test_record)) + # The record on the client side should be unchanged. + self.assertEqual(test_record.answer, 0) + if __name__ == "__main__": unittest.main() diff --git a/source/OutProcSrv/CMPNT.CPP b/source/OutProcSrv/CMPNT.CPP index 38231f51..545becc0 100644 --- a/source/OutProcSrv/CMPNT.CPP +++ b/source/OutProcSrv/CMPNT.CPP @@ -10,6 +10,7 @@ // Cmpnt.cpp - Component // #include +#include #include #include @@ -64,6 +65,45 @@ HRESULT __stdcall CA::InitRecord(T_TEST_RECORD* test_record) return S_OK ; } +HRESULT __stdcall CA::VerifyRecord(T_TEST_RECORD* test_record, + VARIANT_BOOL* result) +{ + // Display the received T_TEST_RECORD structure. + if (test_record->question == NULL){ + test_record->question = ::SysAllocString(L"") ; + } + std::ostringstream sout ; + sout << "Received T_TEST_RECORD structure contains: " << std::ends ; + trace(sout.str().c_str()) ; + sout.str("") ; + sout << "\n\t\t" << "question: " << test_record->question << std::ends ; + trace(sout.str().c_str()) ; + sout.str("") ; + sout << "\n\t\t" << "answer: " << test_record->answer << std::ends ; + trace(sout.str().c_str()) ; + sout.str("") ; + sout << "\n\t\t" << "needs_clarification: " << test_record->needs_clarification << std::ends ; + trace(sout.str().c_str()) ; + sout.str("") ; + + // Check if we received an initialization record. + if (_wcsicmp(test_record->question, L"The meaning of life, the universe and everything?") == 0 + && test_record->answer == 42 + && test_record->needs_clarification == VARIANT_TRUE){ + *result = VARIANT_TRUE ; + } + else { + *result = VARIANT_FALSE ; + } + + // Modify the received record. + // This modification should not change the record on the client side + // because it is just an [in] parameter and not passed with VT_BYREF. + test_record->answer == 12 ; + + return S_OK ; +} + // // Constructor // diff --git a/source/OutProcSrv/CMPNT.H b/source/OutProcSrv/CMPNT.H index 12ce5bc7..9ac7c0fc 100644 --- a/source/OutProcSrv/CMPNT.H +++ b/source/OutProcSrv/CMPNT.H @@ -60,6 +60,9 @@ private: // Interface IDualComtypesTest virtual HRESULT __stdcall InitRecord(T_TEST_RECORD* test_record) ; + virtual HRESULT __stdcall VerifyRecord( + T_TEST_RECORD* test_record, + VARIANT_BOOL* result) ; // Initialization virtual HRESULT Init() ; diff --git a/source/OutProcSrv/SERVER.IDL b/source/OutProcSrv/SERVER.IDL index 39d772ba..c01fa606 100644 --- a/source/OutProcSrv/SERVER.IDL +++ b/source/OutProcSrv/SERVER.IDL @@ -27,6 +27,10 @@ interface IDualComtypesTest : IDispatch { [id(0x00000001)] HRESULT InitRecord([in, out] T_TEST_RECORD* test_record) ; + [id(0x00000002)] + HRESULT VerifyRecord( + [in] T_TEST_RECORD* test_record, + [out, retval] VARIANT_BOOL* result); } ; // Interface IComtypesTest From d91402391d90ed533e6bd49d0ced0a5d285e6b37 Mon Sep 17 00:00:00 2001 From: Thomas Geppert Date: Mon, 27 May 2024 11:14:10 +0200 Subject: [PATCH 05/11] Apply suggestions from code review Co-authored-by: Jun Komoda <45822440+junkmd@users.noreply.github.com> --- comtypes/test/test_dispifc_records.py | 45 +++++++++++++++++---------- 1 file changed, 28 insertions(+), 17 deletions(-) diff --git a/comtypes/test/test_dispifc_records.py b/comtypes/test/test_dispifc_records.py index 0cab4e11..6d7b5355 100644 --- a/comtypes/test/test_dispifc_records.py +++ b/comtypes/test/test_dispifc_records.py @@ -22,12 +22,16 @@ class Test(unittest.TestCase): """Test dispmethods with record pointer parameters.""" - def test(self): + EXPECTED_INITED_QUESTIONS = "The meaning of life, the universe and everything?" + + def _create_dispifc(self) -> "ComtypesTestLib.IComtypesTest": # Explicitely ask for the dispinterface of the component. - dispifc = CreateObject( + return CreateObject( ProgID, clsctx=CLSCTX_LOCAL_SERVER, interface=ComtypesTestLib.IComtypesTest ) + def test_byref(self): + dispifc = self._create_dispifc() # Passing a record by reference to a method that has declared the parameter # as [in, out] we expect modifications of the record on the server side to # also change the record on the client side. @@ -42,6 +46,8 @@ def test(self): self.assertEqual(test_record.answer, 42) self.assertEqual(test_record.needs_clarification, True) + def test_pointer(self): + dispifc = self._create_dispifc() # Passing a record pointer to a method that has declared the parameter # as [in, out] we expect modifications of the record on the server side to # also change the record on the client side. @@ -57,26 +63,31 @@ def test(self): self.assertEqual(test_record.answer, 42) self.assertEqual(test_record.needs_clarification, True) + def test_record(self): # Passing a record to a method that has declared the parameter just as [in] # we expect modifications of the record on the server side NOT to change # the record on the client side. # We also need to test if the record gets properly passed to the method on # the server side. For this, the 'VerifyRecord' method returns 'True' if - # all record fields have the initialization values provided by 'InitRecord'. - self.assertTrue(dispifc.VerifyRecord(test_record)) - # Check if the 'answer' field is unchanged although the method modifies this - # field on the server side. - self.assertEqual(test_record.answer, 42) - # Also perform the inverted test. - # For this, first create a blank record. - test_record = ComtypesTestLib.T_TEST_RECORD() - self.assertEqual(test_record.question, None) - self.assertEqual(test_record.answer, 0) - self.assertEqual(test_record.needs_clarification, False) - # Perform the check on initialization values. - self.assertFalse(dispifc.VerifyRecord(test_record)) - # The record on the client side should be unchanged. - self.assertEqual(test_record.answer, 0) + # all record fields have values equivalent to the initialization values + # provided by 'InitRecord'. + inited_record = ComtypesTestLib.T_TEST_RECORD() + inited_record.question = self.EXPECTED_INITED_QUESTIONS + inited_record.answer = 42 + inited_record.needs_clarification = True + for rec, expected, (q, a, nc) in [ + (inited_record, True, (self.EXPECTED_INITED_QUESTIONS, 42, True)), + # Also perform the inverted test. For this, create a blank record. + (ComtypesTestLib.T_TEST_RECORD(), False, (None, 0, False)), + ]: + with self.subTest(expected=expected, q=q, a=a, nc=nc): + # Perform the check on initialization values. + self.assertEqual(self._create_dispifc().VerifyRecord(rec), expected) + self.assertEqual(rec.question, q) + # Check if the 'answer' field is unchanged although the method + # modifies this field on the server side. + self.assertEqual(rec.answer, a) + self.assertEqual(rec.needs_clarification, nc) if __name__ == "__main__": From c86326fe4b6d0918b4554e028a53fc531eeadc10 Mon Sep 17 00:00:00 2001 From: Jun Komoda <45822440+junkmd@users.noreply.github.com> Date: Mon, 27 May 2024 19:43:59 +0900 Subject: [PATCH 06/11] Apply suggestions from code review --- comtypes/test/test_dispifc_records.py | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/comtypes/test/test_dispifc_records.py b/comtypes/test/test_dispifc_records.py index 6d7b5355..27ca9708 100644 --- a/comtypes/test/test_dispifc_records.py +++ b/comtypes/test/test_dispifc_records.py @@ -40,9 +40,7 @@ def test_byref(self): self.assertEqual(test_record.answer, 0) self.assertEqual(test_record.needs_clarification, False) dispifc.InitRecord(byref(test_record)) - self.assertEqual( - test_record.question, "The meaning of life, the universe and everything?" - ) + self.assertEqual(test_record.question, self.EXPECTED_INITED_QUESTIONS) self.assertEqual(test_record.answer, 42) self.assertEqual(test_record.needs_clarification, True) @@ -55,11 +53,8 @@ def test_pointer(self): self.assertEqual(test_record.question, None) self.assertEqual(test_record.answer, 0) self.assertEqual(test_record.needs_clarification, False) - test_record_pointer = pointer(test_record) - dispifc.InitRecord(test_record_pointer) - self.assertEqual( - test_record.question, "The meaning of life, the universe and everything?" - ) + dispifc.InitRecord(pointer(test_record)) + self.assertEqual(test_record.question, self.EXPECTED_INITED_QUESTIONS) self.assertEqual(test_record.answer, 42) self.assertEqual(test_record.needs_clarification, True) From 7720cac1a98f24a846acb834300d6cc3cc9adda8 Mon Sep 17 00:00:00 2001 From: Thomas Geppert Date: Tue, 28 May 2024 10:37:20 +0200 Subject: [PATCH 07/11] Apply suggestions from code review Co-authored-by: Jun Komoda <45822440+junkmd@users.noreply.github.com> --- comtypes/test/test_dispifc_records.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/comtypes/test/test_dispifc_records.py b/comtypes/test/test_dispifc_records.py index 27ca9708..e752c6a8 100644 --- a/comtypes/test/test_dispifc_records.py +++ b/comtypes/test/test_dispifc_records.py @@ -6,11 +6,10 @@ from comtypes.client import CreateObject, GetModule from ctypes import byref, pointer -ComtypesTestLib_GUID = "07D2AEE5-1DF8-4D2C-953A-554ADFD25F99" -ProgID = "ComtypesTest.COM.Server" +ComtypesTestLib_GUID = "{07D2AEE5-1DF8-4D2C-953A-554ADFD25F99}" try: - GetModule([f"{{{ComtypesTestLib_GUID}}}", 1, 0, 0]) + GetModule((ComtypesTestLib_GUID, 1, 0, 0)) import comtypes.gen.ComtypesTestLib as ComtypesTestLib IMPORT_FAILED = False @@ -27,7 +26,9 @@ class Test(unittest.TestCase): def _create_dispifc(self) -> "ComtypesTestLib.IComtypesTest": # Explicitely ask for the dispinterface of the component. return CreateObject( - ProgID, clsctx=CLSCTX_LOCAL_SERVER, interface=ComtypesTestLib.IComtypesTest + "ComtypesTest.COM.Server", + clsctx=CLSCTX_LOCAL_SERVER, + interface=ComtypesTestLib.IComtypesTest, ) def test_byref(self): From c882cfb55c1bb53595368b695d3200cd720837d7 Mon Sep 17 00:00:00 2001 From: geppi Date: Tue, 28 May 2024 11:02:32 +0200 Subject: [PATCH 08/11] Commit for renaming. Changed the names of: - Interfaces - Component - ProgID - Type Library Renamed the source files of the C++ server component to reflect the component name. Changed the name of the C++ COM server sources subdirectory. --- .github/workflows/autotest.yml | 4 +-- comtypes/test/test_dispifc_records.py | 22 ++++++------- .../{OutProcSrv => CppTestSrv}/CFACTORY.CPP | 0 source/{OutProcSrv => CppTestSrv}/CFACTORY.H | 0 .../{OutProcSrv => CppTestSrv}/CUNKNOWN.CPP | 0 source/{OutProcSrv => CppTestSrv}/CUNKNOWN.H | 0 .../CoComtypesDispIfcParamTests.cpp} | 20 ++++++------ .../CoComtypesDispIfcParamTests.h} | 6 ++-- source/{OutProcSrv => CppTestSrv}/MAKEFILE | 8 ++--- source/{OutProcSrv => CppTestSrv}/OUTPROC.CPP | 0 .../{OutProcSrv => CppTestSrv}/REGISTRY.CPP | 2 +- source/{OutProcSrv => CppTestSrv}/REGISTRY.H | 0 source/{OutProcSrv => CppTestSrv}/SERVER.CPP | 12 +++---- source/{OutProcSrv => CppTestSrv}/SERVER.IDL | 32 +++++++++++-------- source/{OutProcSrv => CppTestSrv}/UTIL.CPP | 0 source/{OutProcSrv => CppTestSrv}/UTIL.H | 0 16 files changed, 55 insertions(+), 51 deletions(-) rename source/{OutProcSrv => CppTestSrv}/CFACTORY.CPP (100%) rename source/{OutProcSrv => CppTestSrv}/CFACTORY.H (100%) rename source/{OutProcSrv => CppTestSrv}/CUNKNOWN.CPP (100%) rename source/{OutProcSrv => CppTestSrv}/CUNKNOWN.H (100%) rename source/{OutProcSrv/CMPNT.CPP => CppTestSrv/CoComtypesDispIfcParamTests.cpp} (92%) rename source/{OutProcSrv/CMPNT.H => CppTestSrv/CoComtypesDispIfcParamTests.h} (93%) rename source/{OutProcSrv => CppTestSrv}/MAKEFILE (88%) rename source/{OutProcSrv => CppTestSrv}/OUTPROC.CPP (100%) rename source/{OutProcSrv => CppTestSrv}/REGISTRY.CPP (99%) rename source/{OutProcSrv => CppTestSrv}/REGISTRY.H (100%) rename source/{OutProcSrv => CppTestSrv}/SERVER.CPP (74%) rename source/{OutProcSrv => CppTestSrv}/SERVER.IDL (58%) rename source/{OutProcSrv => CppTestSrv}/UTIL.CPP (100%) rename source/{OutProcSrv => CppTestSrv}/UTIL.H (100%) diff --git a/.github/workflows/autotest.yml b/.github/workflows/autotest.yml index 3edabc2b..efeaad4e 100644 --- a/.github/workflows/autotest.yml +++ b/.github/workflows/autotest.yml @@ -29,7 +29,7 @@ jobs: uses: ilammy/msvc-dev-cmd@v1 - name: Build and register the OutProc COM server run: | - cd source/OutProcSrv + cd source/CppTestSrv nmake /f Makefile ./server.exe /RegServer - name: unittest comtypes @@ -37,5 +37,5 @@ jobs: python -m unittest discover -v -s ./comtypes/test -t comtypes\test - name: Unregister the OutProc COM server run: | - cd source/OutProcSrv + cd source/CppTestSrv ./server.exe /UnregServer \ No newline at end of file diff --git a/comtypes/test/test_dispifc_records.py b/comtypes/test/test_dispifc_records.py index e752c6a8..0588351f 100644 --- a/comtypes/test/test_dispifc_records.py +++ b/comtypes/test/test_dispifc_records.py @@ -6,11 +6,11 @@ from comtypes.client import CreateObject, GetModule from ctypes import byref, pointer -ComtypesTestLib_GUID = "{07D2AEE5-1DF8-4D2C-953A-554ADFD25F99}" +ComtypesCppTestSrvLib_GUID = "{07D2AEE5-1DF8-4D2C-953A-554ADFD25F99}" try: - GetModule((ComtypesTestLib_GUID, 1, 0, 0)) - import comtypes.gen.ComtypesTestLib as ComtypesTestLib + GetModule((ComtypesCppTestSrvLib_GUID, 1, 0, 0)) + import comtypes.gen.ComtypesCppTestSrvLib as ComtypesCppTestSrvLib IMPORT_FAILED = False except (ImportError, OSError): @@ -19,16 +19,16 @@ @unittest.skipIf(IMPORT_FAILED, "This depends on the out of process COM-server.") class Test(unittest.TestCase): - """Test dispmethods with record pointer parameters.""" + """Test dispmethods with record and record pointer parameters.""" EXPECTED_INITED_QUESTIONS = "The meaning of life, the universe and everything?" - def _create_dispifc(self) -> "ComtypesTestLib.IComtypesTest": + def _create_dispifc(self) -> "ComtypesCppTestSrvLib.IDispRecordParamTest": # Explicitely ask for the dispinterface of the component. return CreateObject( - "ComtypesTest.COM.Server", + "Comtypes.DispIfcParamTests", clsctx=CLSCTX_LOCAL_SERVER, - interface=ComtypesTestLib.IComtypesTest, + interface=ComtypesCppTestSrvLib.IDispRecordParamTest, ) def test_byref(self): @@ -36,7 +36,7 @@ def test_byref(self): # Passing a record by reference to a method that has declared the parameter # as [in, out] we expect modifications of the record on the server side to # also change the record on the client side. - test_record = ComtypesTestLib.T_TEST_RECORD() + test_record = ComtypesCppTestSrvLib.T_TEST_RECORD() self.assertEqual(test_record.question, None) self.assertEqual(test_record.answer, 0) self.assertEqual(test_record.needs_clarification, False) @@ -50,7 +50,7 @@ def test_pointer(self): # Passing a record pointer to a method that has declared the parameter # as [in, out] we expect modifications of the record on the server side to # also change the record on the client side. - test_record = ComtypesTestLib.T_TEST_RECORD() + test_record = ComtypesCppTestSrvLib.T_TEST_RECORD() self.assertEqual(test_record.question, None) self.assertEqual(test_record.answer, 0) self.assertEqual(test_record.needs_clarification, False) @@ -67,14 +67,14 @@ def test_record(self): # the server side. For this, the 'VerifyRecord' method returns 'True' if # all record fields have values equivalent to the initialization values # provided by 'InitRecord'. - inited_record = ComtypesTestLib.T_TEST_RECORD() + inited_record = ComtypesCppTestSrvLib.T_TEST_RECORD() inited_record.question = self.EXPECTED_INITED_QUESTIONS inited_record.answer = 42 inited_record.needs_clarification = True for rec, expected, (q, a, nc) in [ (inited_record, True, (self.EXPECTED_INITED_QUESTIONS, 42, True)), # Also perform the inverted test. For this, create a blank record. - (ComtypesTestLib.T_TEST_RECORD(), False, (None, 0, False)), + (ComtypesCppTestSrvLib.T_TEST_RECORD(), False, (None, 0, False)), ]: with self.subTest(expected=expected, q=q, a=a, nc=nc): # Perform the check on initialization values. diff --git a/source/OutProcSrv/CFACTORY.CPP b/source/CppTestSrv/CFACTORY.CPP similarity index 100% rename from source/OutProcSrv/CFACTORY.CPP rename to source/CppTestSrv/CFACTORY.CPP diff --git a/source/OutProcSrv/CFACTORY.H b/source/CppTestSrv/CFACTORY.H similarity index 100% rename from source/OutProcSrv/CFACTORY.H rename to source/CppTestSrv/CFACTORY.H diff --git a/source/OutProcSrv/CUNKNOWN.CPP b/source/CppTestSrv/CUNKNOWN.CPP similarity index 100% rename from source/OutProcSrv/CUNKNOWN.CPP rename to source/CppTestSrv/CUNKNOWN.CPP diff --git a/source/OutProcSrv/CUNKNOWN.H b/source/CppTestSrv/CUNKNOWN.H similarity index 100% rename from source/OutProcSrv/CUNKNOWN.H rename to source/CppTestSrv/CUNKNOWN.H diff --git a/source/OutProcSrv/CMPNT.CPP b/source/CppTestSrv/CoComtypesDispIfcParamTests.cpp similarity index 92% rename from source/OutProcSrv/CMPNT.CPP rename to source/CppTestSrv/CoComtypesDispIfcParamTests.cpp index 545becc0..bbfad10f 100644 --- a/source/OutProcSrv/CMPNT.CPP +++ b/source/CppTestSrv/CoComtypesDispIfcParamTests.cpp @@ -7,7 +7,7 @@ */ // -// Cmpnt.cpp - Component +// CoComtypesDispIfcParamTests.cpp - Component // #include #include @@ -18,12 +18,12 @@ #include "Util.h" #include "CUnknown.h" #include "CFactory.h" // Needed for module handle -#include "Cmpnt.h" +#include "CoComtypesDispIfcParamTests.h" // We need to put this declaration here because we explicitely expose a dispinterface // in parallel to the dual interface but dispinterfaces don't appear in the // MIDL-generated header file. -EXTERN_C const IID DIID_IComtypesTest; +EXTERN_C const IID DIID_IDispRecordParamTest; static inline void trace(const char* msg) { Util::Trace("Component", msg, S_OK) ;} @@ -32,7 +32,7 @@ static inline void trace(const char* msg, HRESULT hr) /////////////////////////////////////////////////////////// // -// Interface IDualComtypesTest - Implementation +// Interface IDualRecordParamTest - Implementation // HRESULT __stdcall CA::InitRecord(T_TEST_RECORD* test_record) @@ -133,13 +133,13 @@ CA::~CA() HRESULT __stdcall CA::NondelegatingQueryInterface(const IID& iid, void** ppv) { - if (iid == IID_IDualComtypesTest) + if (iid == IID_IDualRecordParamTest) { - return FinishQI(static_cast(this), ppv) ; + return FinishQI(static_cast(this), ppv) ; } - else if (iid == DIID_IComtypesTest) + else if (iid == DIID_IDispRecordParamTest) { - trace("Queried for IComtypesTest.") ; + trace("Queried for IDispRecordParamTest.") ; return FinishQI(static_cast(this), ppv) ; } else if (iid == IID_IDispatch) @@ -164,7 +164,7 @@ HRESULT CA::Init() if (m_pITypeInfo == NULL) { ITypeLib* pITypeLib = NULL ; - hr = ::LoadRegTypeLib(LIBID_ComtypesTestLib, + hr = ::LoadRegTypeLib(LIBID_ComtypesCppTestSrvLib, 1, 0, // Major/Minor version numbers 0x00, &pITypeLib) ; @@ -175,7 +175,7 @@ HRESULT CA::Init() } // Get type information for the interface of the object. - hr = pITypeLib->GetTypeInfoOfGuid(IID_IDualComtypesTest, + hr = pITypeLib->GetTypeInfoOfGuid(IID_IDualRecordParamTest, &m_pITypeInfo) ; pITypeLib->Release() ; if (FAILED(hr)) diff --git a/source/OutProcSrv/CMPNT.H b/source/CppTestSrv/CoComtypesDispIfcParamTests.h similarity index 93% rename from source/OutProcSrv/CMPNT.H rename to source/CppTestSrv/CoComtypesDispIfcParamTests.h index 9ac7c0fc..d082068f 100644 --- a/source/OutProcSrv/CMPNT.H +++ b/source/CppTestSrv/CoComtypesDispIfcParamTests.h @@ -7,7 +7,7 @@ */ // -// Cmpnt.cpp - Component +// CoComtypesDispIfcParamTests.cpp - Component // #include "Iface.h" @@ -18,7 +18,7 @@ // Component A // class CA : public CUnknown, - public IDualComtypesTest + public IDualRecordParamTest { public: // Creation @@ -58,7 +58,7 @@ class CA : public CUnknown, EXCEPINFO* pExcepInfo, UINT* pArgErr) ; - // Interface IDualComtypesTest + // Interface IDualRecordParamTest virtual HRESULT __stdcall InitRecord(T_TEST_RECORD* test_record) ; virtual HRESULT __stdcall VerifyRecord( T_TEST_RECORD* test_record, diff --git a/source/OutProcSrv/MAKEFILE b/source/CppTestSrv/MAKEFILE similarity index 88% rename from source/OutProcSrv/MAKEFILE rename to source/CppTestSrv/MAKEFILE index 8f6bf065..f19ba1e5 100644 --- a/source/OutProcSrv/MAKEFILE +++ b/source/CppTestSrv/MAKEFILE @@ -44,9 +44,9 @@ guids.obj : guids.c server.obj : server.cpp cunknown.h cfactory.h iface.h cl $(CPP_FLAGS) server.cpp -cmpnt.obj : cmpnt.cpp cmpnt.h iface.h registry.h \ - CUnknown.h - cl $(CPP_FLAGS) cmpnt.cpp +CoComtypesDispIfcParamTests.obj : CoComtypesDispIfcParamTests.cpp CoComtypesDispIfcParamTests.h iface.h \ + registry.h CUnknown.h + cl $(CPP_FLAGS) CoComtypesDispIfcParamTests.cpp # # Helper classes @@ -75,7 +75,7 @@ outproc.obj : outproc.cpp CFactory.h CUnknown.h # SERVER_OBJS = Server.obj \ - Cmpnt.obj \ + CoComtypesDispIfcParamTests.obj \ Registry.obj \ Cfactory.obj \ Cunknown.obj \ diff --git a/source/OutProcSrv/OUTPROC.CPP b/source/CppTestSrv/OUTPROC.CPP similarity index 100% rename from source/OutProcSrv/OUTPROC.CPP rename to source/CppTestSrv/OUTPROC.CPP diff --git a/source/OutProcSrv/REGISTRY.CPP b/source/CppTestSrv/REGISTRY.CPP similarity index 99% rename from source/OutProcSrv/REGISTRY.CPP rename to source/CppTestSrv/REGISTRY.CPP index 3f7cf50e..10fe020d 100644 --- a/source/OutProcSrv/REGISTRY.CPP +++ b/source/CppTestSrv/REGISTRY.CPP @@ -191,7 +191,7 @@ LONG UnregisterServer(const CLSID& clsid, // Class ID } // Unregister the Type Library. - HRESULT hr = UnRegisterTypeLib(LIBID_ComtypesTestLib, + HRESULT hr = UnRegisterTypeLib(LIBID_ComtypesCppTestSrvLib, 1, 0, // Major/Minor version numbers 0x00, SYS_WIN64) ; diff --git a/source/OutProcSrv/REGISTRY.H b/source/CppTestSrv/REGISTRY.H similarity index 100% rename from source/OutProcSrv/REGISTRY.H rename to source/CppTestSrv/REGISTRY.H diff --git a/source/OutProcSrv/SERVER.CPP b/source/CppTestSrv/SERVER.CPP similarity index 74% rename from source/OutProcSrv/SERVER.CPP rename to source/CppTestSrv/SERVER.CPP index 3465b05e..d9a6a151 100644 --- a/source/OutProcSrv/SERVER.CPP +++ b/source/CppTestSrv/SERVER.CPP @@ -8,7 +8,7 @@ #include "CFactory.h" #include "Iface.h" -#include "Cmpnt.h" +#include "CoComtypesDispIfcParamTests.h" /////////////////////////////////////////////////////////// @@ -35,11 +35,11 @@ // CFactoryData g_FactoryDataArray[] = { - {&CLSID_Component, CA::CreateInstance, - L"Comtypes Test automation server component", // Friendly Name - L"ComtypesTest.COM.Server.1", // ProgID - L"ComtypesTest.COM.Server", // Version-independent ProgID - &LIBID_ComtypesTestLib, // Type Library ID + {&CLSID_CoComtypesDispIfcParamTests, CA::CreateInstance, + L"Comtypes component for dispinterface parameter tests", // Friendly Name + L"Comtypes.DispIfcParamTests.1", // ProgID + L"Comtypes.DispIfcParamTests", // Version-independent ProgID + &LIBID_ComtypesCppTestSrvLib, // Type Library ID NULL, 0} } ; int g_cFactoryDataEntries diff --git a/source/OutProcSrv/SERVER.IDL b/source/CppTestSrv/SERVER.IDL similarity index 58% rename from source/OutProcSrv/SERVER.IDL rename to source/CppTestSrv/SERVER.IDL index c01fa606..7b7702c3 100644 --- a/source/OutProcSrv/SERVER.IDL +++ b/source/CppTestSrv/SERVER.IDL @@ -14,16 +14,17 @@ struct T_TEST_RECORD { VARIANT_BOOL needs_clarification ; } T_TEST_RECORD ; -// Interface IDualComtypesTest + +// Interface IDualRecordParamTest [ odl, uuid(0C4E01E8-4625-46A2-BC4C-2E889A8DBBD6), dual, - helpstring("IDualComtypesTest Interface"), + helpstring("Dual Interface for testing record parameters."), nonextensible, oleautomation ] -interface IDualComtypesTest : IDispatch +interface IDualRecordParamTest : IDispatch { [id(0x00000001)] HRESULT InitRecord([in, out] T_TEST_RECORD* test_record) ; @@ -33,36 +34,39 @@ interface IDualComtypesTest : IDispatch [out, retval] VARIANT_BOOL* result); } ; -// Interface IComtypesTest + +// Interface IDispRecordParamTest [ - uuid(033E4C10-0A7F-4E93-8377-499AD4B6583A) + uuid(033E4C10-0A7F-4E93-8377-499AD4B6583A), + helpstring("Dispinterface for testing record parameters.") ] -dispinterface IComtypesTest +dispinterface IDispRecordParamTest { - interface IDualComtypesTest; + interface IDualRecordParamTest; } ; + // // Component and type library descriptions // [ uuid(07D2AEE5-1DF8-4D2C-953A-554ADFD25F99), version(1.0), - helpstring("Comtypes Test COM Server 1.0 Type Library") + helpstring("Comtypes C++ Test COM Server 1.0 Type Library.") ] -library ComtypesTestLib +library ComtypesCppTestSrvLib { // TLib : OLE Automation : {00020430-0000-0000-C000-000000000046} importlib("stdole2.tlb") ; - // Component + // CoComtypesDispIfcParamTests [ uuid(06571915-2431-4CA3-9C01-53002B060DAB), - helpstring("Component Class") + helpstring("Comtypes component for dispinterface parameter tests.") ] - coclass Component + coclass CoComtypesDispIfcParamTests { - [default] interface IDualComtypesTest ; - dispinterface IComtypesTest ; + interface IDualRecordParamTest ; + dispinterface IDispRecordParamTest ; } ; } ; diff --git a/source/OutProcSrv/UTIL.CPP b/source/CppTestSrv/UTIL.CPP similarity index 100% rename from source/OutProcSrv/UTIL.CPP rename to source/CppTestSrv/UTIL.CPP diff --git a/source/OutProcSrv/UTIL.H b/source/CppTestSrv/UTIL.H similarity index 100% rename from source/OutProcSrv/UTIL.H rename to source/CppTestSrv/UTIL.H From f373411094168e8f3b3d912e468e5b1737c22eef Mon Sep 17 00:00:00 2001 From: geppi Date: Tue, 28 May 2024 15:35:10 +0200 Subject: [PATCH 09/11] Renamed the structure used for record parameter testing. --- comtypes/test/test_dispifc_records.py | 8 ++++---- source/CppTestSrv/CoComtypesDispIfcParamTests.cpp | 12 ++++++------ source/CppTestSrv/CoComtypesDispIfcParamTests.h | 6 +++--- source/CppTestSrv/SERVER.IDL | 9 +++++---- 4 files changed, 18 insertions(+), 17 deletions(-) diff --git a/comtypes/test/test_dispifc_records.py b/comtypes/test/test_dispifc_records.py index 0588351f..8c289fa1 100644 --- a/comtypes/test/test_dispifc_records.py +++ b/comtypes/test/test_dispifc_records.py @@ -36,7 +36,7 @@ def test_byref(self): # Passing a record by reference to a method that has declared the parameter # as [in, out] we expect modifications of the record on the server side to # also change the record on the client side. - test_record = ComtypesCppTestSrvLib.T_TEST_RECORD() + test_record = ComtypesCppTestSrvLib.StructRecordParamTest() self.assertEqual(test_record.question, None) self.assertEqual(test_record.answer, 0) self.assertEqual(test_record.needs_clarification, False) @@ -50,7 +50,7 @@ def test_pointer(self): # Passing a record pointer to a method that has declared the parameter # as [in, out] we expect modifications of the record on the server side to # also change the record on the client side. - test_record = ComtypesCppTestSrvLib.T_TEST_RECORD() + test_record = ComtypesCppTestSrvLib.StructRecordParamTest() self.assertEqual(test_record.question, None) self.assertEqual(test_record.answer, 0) self.assertEqual(test_record.needs_clarification, False) @@ -67,14 +67,14 @@ def test_record(self): # the server side. For this, the 'VerifyRecord' method returns 'True' if # all record fields have values equivalent to the initialization values # provided by 'InitRecord'. - inited_record = ComtypesCppTestSrvLib.T_TEST_RECORD() + inited_record = ComtypesCppTestSrvLib.StructRecordParamTest() inited_record.question = self.EXPECTED_INITED_QUESTIONS inited_record.answer = 42 inited_record.needs_clarification = True for rec, expected, (q, a, nc) in [ (inited_record, True, (self.EXPECTED_INITED_QUESTIONS, 42, True)), # Also perform the inverted test. For this, create a blank record. - (ComtypesCppTestSrvLib.T_TEST_RECORD(), False, (None, 0, False)), + (ComtypesCppTestSrvLib.StructRecordParamTest(), False, (None, 0, False)), ]: with self.subTest(expected=expected, q=q, a=a, nc=nc): # Perform the check on initialization values. diff --git a/source/CppTestSrv/CoComtypesDispIfcParamTests.cpp b/source/CppTestSrv/CoComtypesDispIfcParamTests.cpp index bbfad10f..846bab8a 100644 --- a/source/CppTestSrv/CoComtypesDispIfcParamTests.cpp +++ b/source/CppTestSrv/CoComtypesDispIfcParamTests.cpp @@ -35,14 +35,14 @@ static inline void trace(const char* msg, HRESULT hr) // Interface IDualRecordParamTest - Implementation // -HRESULT __stdcall CA::InitRecord(T_TEST_RECORD* test_record) +HRESULT __stdcall CA::InitRecord(StructRecordParamTest* test_record) { - // Display the received T_TEST_RECORD structure. + // Display the received StructRecordParamTest structure. if (test_record->question == NULL){ test_record->question = ::SysAllocString(L"") ; } std::ostringstream sout ; - sout << "Received T_TEST_RECORD structure contains: " << std::ends ; + sout << "Received StructRecordParamTest structure contains: " << std::ends ; trace(sout.str().c_str()) ; sout.str("") ; sout << "\n\t\t" << "question: " << test_record->question << std::ends ; @@ -65,15 +65,15 @@ HRESULT __stdcall CA::InitRecord(T_TEST_RECORD* test_record) return S_OK ; } -HRESULT __stdcall CA::VerifyRecord(T_TEST_RECORD* test_record, +HRESULT __stdcall CA::VerifyRecord(StructRecordParamTest* test_record, VARIANT_BOOL* result) { - // Display the received T_TEST_RECORD structure. + // Display the received StructRecordParamTest structure. if (test_record->question == NULL){ test_record->question = ::SysAllocString(L"") ; } std::ostringstream sout ; - sout << "Received T_TEST_RECORD structure contains: " << std::ends ; + sout << "Received StructRecordParamTest structure contains: " << std::ends ; trace(sout.str().c_str()) ; sout.str("") ; sout << "\n\t\t" << "question: " << test_record->question << std::ends ; diff --git a/source/CppTestSrv/CoComtypesDispIfcParamTests.h b/source/CppTestSrv/CoComtypesDispIfcParamTests.h index d082068f..1dfe95fa 100644 --- a/source/CppTestSrv/CoComtypesDispIfcParamTests.h +++ b/source/CppTestSrv/CoComtypesDispIfcParamTests.h @@ -59,10 +59,10 @@ class CA : public CUnknown, UINT* pArgErr) ; // Interface IDualRecordParamTest - virtual HRESULT __stdcall InitRecord(T_TEST_RECORD* test_record) ; + virtual HRESULT __stdcall InitRecord(StructRecordParamTest* test_record) ; virtual HRESULT __stdcall VerifyRecord( - T_TEST_RECORD* test_record, - VARIANT_BOOL* result) ; + StructRecordParamTest* test_record, + VARIANT_BOOL* result) ; // Initialization virtual HRESULT Init() ; diff --git a/source/CppTestSrv/SERVER.IDL b/source/CppTestSrv/SERVER.IDL index 7b7702c3..7ef84836 100644 --- a/source/CppTestSrv/SERVER.IDL +++ b/source/CppTestSrv/SERVER.IDL @@ -8,11 +8,11 @@ import "oaidl.idl" ; typedef [uuid(00FABB0F-5691-41A6-B7C1-11606671F8E5)] -struct T_TEST_RECORD { +struct StructRecordParamTest { BSTR question ; long answer ; VARIANT_BOOL needs_clarification ; -} T_TEST_RECORD ; +} StructRecordParamTest ; // Interface IDualRecordParamTest @@ -27,10 +27,10 @@ struct T_TEST_RECORD { interface IDualRecordParamTest : IDispatch { [id(0x00000001)] - HRESULT InitRecord([in, out] T_TEST_RECORD* test_record) ; + HRESULT InitRecord([in, out] StructRecordParamTest* test_record) ; [id(0x00000002)] HRESULT VerifyRecord( - [in] T_TEST_RECORD* test_record, + [in] StructRecordParamTest* test_record, [out, retval] VARIANT_BOOL* result); } ; @@ -60,6 +60,7 @@ library ComtypesCppTestSrvLib importlib("stdole2.tlb") ; // CoComtypesDispIfcParamTests + // Component that implements interfaces used for dispinterface parameter tests. [ uuid(06571915-2431-4CA3-9C01-53002B060DAB), helpstring("Comtypes component for dispinterface parameter tests.") From 4eee7320b40f696212d7938ab82d6d0069e36e90 Mon Sep 17 00:00:00 2001 From: geppi Date: Tue, 28 May 2024 17:11:26 +0200 Subject: [PATCH 10/11] Fixed typo that rendered a subtest useless. --- source/CppTestSrv/CoComtypesDispIfcParamTests.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/CppTestSrv/CoComtypesDispIfcParamTests.cpp b/source/CppTestSrv/CoComtypesDispIfcParamTests.cpp index 846bab8a..54ac8ecf 100644 --- a/source/CppTestSrv/CoComtypesDispIfcParamTests.cpp +++ b/source/CppTestSrv/CoComtypesDispIfcParamTests.cpp @@ -99,7 +99,7 @@ HRESULT __stdcall CA::VerifyRecord(StructRecordParamTest* test_record, // Modify the received record. // This modification should not change the record on the client side // because it is just an [in] parameter and not passed with VT_BYREF. - test_record->answer == 12 ; + test_record->answer = 12 ; return S_OK ; } From 9d8163905069b084e4fdaf9c94b276a7519b7727 Mon Sep 17 00:00:00 2001 From: Jun Komoda <45822440+junkmd@users.noreply.github.com> Date: Wed, 29 May 2024 14:06:01 +0900 Subject: [PATCH 11/11] Apply suggestions from code review --- comtypes/test/test_dispifc_records.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/comtypes/test/test_dispifc_records.py b/comtypes/test/test_dispifc_records.py index 8c289fa1..ec797d61 100644 --- a/comtypes/test/test_dispifc_records.py +++ b/comtypes/test/test_dispifc_records.py @@ -18,7 +18,7 @@ @unittest.skipIf(IMPORT_FAILED, "This depends on the out of process COM-server.") -class Test(unittest.TestCase): +class Test_DispMethods(unittest.TestCase): """Test dispmethods with record and record pointer parameters.""" EXPECTED_INITED_QUESTIONS = "The meaning of life, the universe and everything?" @@ -31,7 +31,7 @@ def _create_dispifc(self) -> "ComtypesCppTestSrvLib.IDispRecordParamTest": interface=ComtypesCppTestSrvLib.IDispRecordParamTest, ) - def test_byref(self): + def test_inout_byref(self): dispifc = self._create_dispifc() # Passing a record by reference to a method that has declared the parameter # as [in, out] we expect modifications of the record on the server side to @@ -45,7 +45,7 @@ def test_byref(self): self.assertEqual(test_record.answer, 42) self.assertEqual(test_record.needs_clarification, True) - def test_pointer(self): + def test_inout_pointer(self): dispifc = self._create_dispifc() # Passing a record pointer to a method that has declared the parameter # as [in, out] we expect modifications of the record on the server side to @@ -59,7 +59,7 @@ def test_pointer(self): self.assertEqual(test_record.answer, 42) self.assertEqual(test_record.needs_clarification, True) - def test_record(self): + def test_in_record(self): # Passing a record to a method that has declared the parameter just as [in] # we expect modifications of the record on the server side NOT to change # the record on the client side.