/*++ Copyright (C) 1996-2001 Microsoft Corporation Module Name: MOFDATA.CPP Abstract: Entry points for the WBEM MOF compiler. History: a-davj 12-April-97 Added WMI support. --*/ #include "precomp.h" #include #include "mofout.h" #include "mofdata.h" #include "typehelp.h" #include "bmof.h" #include "cbmofout.h" #include "trace.h" #include "strings.h" #include "arrtempl.h" #include #define TEMP_BUF 128 WCHAR * Macro_CloneStr(LPCWSTR pFr) { if(pFr == NULL) return NULL; DWORD dwLen = wcslen(pFr) + 1; WCHAR * pTo = new WCHAR[dwLen]; if(pTo) { StringCchCopyW(pTo, dwLen, pFr); return pTo; } return NULL; } HRESULT MofdSetInterfaceSecurity(IUnknown * pInterface, LPWSTR pAuthority, LPWSTR pUser, LPWSTR pPassword) { SCODE sc; BOOL bUseAuthInfo = FALSE; DWORD dwQueryAuthnLevel, dwQueryImpLevel, dwQueryCapabilities; DWORD dwAuthnSvc = RPC_C_AUTHN_GSS_NEGOTIATE; WCHAR * pwCSPBPrincipal = NULL; HRESULT hr = CoQueryProxyBlanket( pInterface, //Location for the proxy to query &dwAuthnSvc, //Location for the current authentication service NULL, //Location for the current authorization service NULL, //Location for the current principal name &dwQueryAuthnLevel, //Location for the current authentication level &dwQueryImpLevel, //Location for the current impersonation level NULL, &dwQueryCapabilities //Location for flags indicating further capabilities of the proxy ); if(SUCCEEDED(hr) && dwAuthnSvc != RPC_C_AUTHN_WINNT) { pwCSPBPrincipal = COLE_DEFAULT_PRINCIPAL; } else { dwAuthnSvc = RPC_C_AUTHN_WINNT; pwCSPBPrincipal = NULL; } // If we are doing trivial case, just pass in a null authenication structure which is used // if the current logged in user's credentials are OK. if((pAuthority == NULL || wcslen(pAuthority) < 1) && (pUser == NULL || wcslen(pUser) < 1) && (pPassword == NULL || wcslen(pPassword) < 1)) { return WbemSetProxyBlanket(pInterface, dwAuthnSvc, RPC_C_AUTHZ_NONE, pwCSPBPrincipal, RPC_C_AUTHN_LEVEL_DEFAULT, RPC_C_IMP_LEVEL_IMPERSONATE, NULL, EOAC_STATIC_CLOAKING); } // If user, or Authority was passed in, the we need to create an authority argument for the login COAUTHIDENTITY authident; BSTR AuthArg = NULL, UserArg = NULL, PrincipalArg = NULL; sc = DetermineLoginTypeEx(AuthArg, UserArg, PrincipalArg, pAuthority, pUser); if(sc != S_OK) return sc; CSysFreeMe fm1(UserArg), fm2(AuthArg), fm3(PrincipalArg); memset((void *)&authident,0,sizeof(COAUTHIDENTITY)); if(UserArg) { authident.UserLength = wcslen(UserArg); authident.User = (LPWSTR)UserArg; bUseAuthInfo = TRUE; } if(AuthArg) { authident.DomainLength = wcslen(AuthArg); authident.Domain = (LPWSTR)AuthArg; bUseAuthInfo = TRUE; } if(pPassword) { authident.PasswordLength = wcslen(pPassword); authident.Password = (LPWSTR)pPassword; bUseAuthInfo = TRUE; } authident.Flags = SEC_WINNT_AUTH_IDENTITY_UNICODE; sc = WbemSetProxyBlanket(pInterface, (PrincipalArg) ? RPC_C_AUTHN_GSS_KERBEROS : dwAuthnSvc, RPC_C_AUTHZ_NONE, (PrincipalArg) ? PrincipalArg : pwCSPBPrincipal, RPC_C_AUTHN_LEVEL_DEFAULT, RPC_C_IMP_LEVEL_IMPERSONATE, (bUseAuthInfo) ? &authident : NULL, (bUseAuthInfo) ? 0 : EOAC_STATIC_CLOAKING ); return sc; } CNamespaceCache::CNamespaceRecord::CNamespaceRecord( COPY LPCWSTR wszName, ADDREF IWbemServices* pNamespace) { m_wszName = Macro_CloneStr(wszName); m_pNamespace = pNamespace; m_pNamespace->AddRef(); } CNamespaceCache::CNamespaceRecord::~CNamespaceRecord() { delete [] m_wszName; if(m_pNamespace) m_pNamespace->Release(); } //***************************************************************************** CNamespaceCache::CNamespaceCache(ADDREF IWbemLocator* pLocator) { if(pLocator) pLocator->AddRef(); m_pLocator = pLocator; } CNamespaceCache::~CNamespaceCache() { if(m_pLocator) m_pLocator->Release(); for(int i = 0; i < m_aRecords.GetSize(); i++) { delete (CNamespaceRecord*)m_aRecords[i]; } } RELEASE_ME IWbemServices* CNamespaceCache::GetNamespace(COPY LPCWSTR wszName, SCODE & scRet, WCHAR * pUserName, WCHAR * pPassword , WCHAR * pAuthority, IWbemContext * pCtx, GUID LocatorGUID, LONG fConnectFlags) { // Check if it is the cache // ======================== scRet = S_OK; for(int i = 0; i < m_aRecords.GetSize(); i++) { CNamespaceRecord* pRecord = (CNamespaceRecord*)m_aRecords[i]; if(!wbem_wcsicmp(pRecord->m_wszName, wszName)) { // Found it // ======== pRecord->m_pNamespace->AddRef(); return pRecord->m_pNamespace; } } // Not found --- open it // ===================== IWbemServices* pNamespace; if(wszName == NULL) return NULL; LPOLESTR pwszName; pwszName = SysAllocString(wszName); if(pwszName == NULL) return NULL; CSysFreeMe fm0(pwszName); LPOLESTR bstrPassword = NULL; LPOLESTR bstrUserName = NULL; LPOLESTR bstrAuthority = NULL; if(pUserName && wcslen(pUserName) > 0) { bstrUserName = SysAllocString(pUserName); if(bstrUserName == NULL) return NULL; } CSysFreeMe fm1(bstrUserName); if(pPassword) { bstrPassword = SysAllocString(pPassword); if(bstrPassword == NULL) return NULL; } CSysFreeMe fm2(bstrPassword); if(pAuthority && wcslen(pAuthority) > 0) { bstrAuthority = SysAllocString(pAuthority); if(bstrAuthority == NULL) return NULL; } CSysFreeMe fm3(bstrAuthority); // Determine if the connection is to the regular locator, or to one of the special inproc ones // used for autocompile. If it is inproc, then remote connections are not valid. bool bInProc = false; if(LocatorGUID != CLSID_WbemLocator) bInProc = true; if(bInProc) { WCHAR * pMachine = ExtractMachineName(pwszName); if(pMachine) { BOOL bLocal = bAreWeLocal(pMachine); delete pMachine; if(!bLocal) { scRet = WBEM_E_INVALID_NAMESPACE; ERRORTRACE((LOG_MOFCOMP,"Error, tried to do a remote connect during autocomp\n")); } } } // Connect up to namespace. //TODO, PASS AUTHORITY IN THE CONTEXT if(scRet == S_OK) scRet = m_pLocator->ConnectServer((LPWSTR)pwszName, bstrUserName, bstrPassword, NULL, fConnectFlags, pAuthority, pCtx, &pNamespace); if(scRet == S_OK && !bInProc) { // Set the impersonation level up so that puts to providers can be done DWORD dwAuthLevel, dwImpLevel; SCODE sc = GetAuthImp( pNamespace, &dwAuthLevel, &dwImpLevel); if(sc != S_OK || dwAuthLevel != RPC_C_AUTHN_LEVEL_NONE) sc = MofdSetInterfaceSecurity(pNamespace, bstrAuthority, bstrUserName, bstrPassword); } if(FAILED(scRet)) return NULL; // Add it to the cache // =================== CNamespaceRecord * pNew = new CNamespaceRecord(wszName, pNamespace); if(pNew) m_aRecords.Add(pNew); // AddRef'ed return pNamespace; } //***************************************************************************** //***************************************************************************** void CMofData::SetQualifierDefault(ACQUIRE CMoQualifier* pDefault) { // Search for this qualifier in the defaults list // ============================================== for(int i = 0; i < m_aQualDefaults.GetSize(); i++) { CMoQualifier* pOrig = (CMoQualifier*)m_aQualDefaults[i]; if(wbem_wcsicmp(pOrig->GetName(), pDefault->GetName()) == 0) { // Found it. Replace // ================= delete pOrig; m_aQualDefaults[i] = (void*)pDefault; return; } } // Not found. Add // ============== m_aQualDefaults.Add((void*)pDefault); } HRESULT CMofData::SetDefaultFlavor(MODIFY CMoQualifier& Qual, bool bTopLevel, QUALSCOPE qs, PARSESTATE ps) { HRESULT hr; // Search for this qualifier in the defaults list // ============================================== for(int i = 0; i < m_aQualDefaults.GetSize(); i++) { CMoQualifier* pOrig = (CMoQualifier*)m_aQualDefaults[i]; if(wbem_wcsicmp(pOrig->GetName(), Qual.GetName()) == 0) { // Found it. SetFlavor // =================== if(pOrig->IsCimDefault()) { // dont bother if the parse state is the initial scan if(ps == INITIAL) continue; if(Qual.IsUsingDefaultValue()) { // see if the scope matches what we have here DWORD dwScope = pOrig->GetScope(); bool bInScope = false; if((dwScope & SCOPE_CLASS) || (dwScope & SCOPE_INSTANCE)) if(qs == CLASSINST_SCOPE) bInScope = true; if(dwScope & SCOPE_PROPERTY) if(qs == PROPMETH_SCOPE) bInScope = true; if(bInScope) { CMoValue& Src = pOrig->AccessValue(); CMoValue& Dest = Qual.AccessValue(); Dest.SetType(Src.GetType()); VARIANT & varSrc = Src.AccessVariant(); VARIANT & varDest = Dest.AccessVariant(); hr = VariantCopy(&varDest, &varSrc); if(FAILED(hr)) return hr; Qual.SetFlavor(pOrig->GetFlavor()); Qual.SetAmended(pOrig->IsAmended()); } } } else { Qual.SetFlavor(pOrig->GetFlavor()); Qual.SetAmended(pOrig->IsAmended()); } return S_OK; } } return S_OK; } BOOL CMofData::IsAliasInUse(READ_ONLY LPWSTR wszAlias) { for(int i = 0; i < m_aObjects.GetSize(); i++) { CMObject* pObject = (CMObject*)m_aObjects[i]; if(pObject->GetAlias() && !wbem_wcsicmp(pObject->GetAlias(), wszAlias)) { return TRUE; } } return FALSE; } BOOL IsGuid(LPWSTR pTest) { int i; int iSoFar = 0; #define HEXCHECK(n) \ for (i = 0; i < n; i++) \ if (!iswxdigit(*pTest++)) \ return FALSE; #define HYPHENCHECK() \ if (*pTest++ != L'-') \ return FALSE; if(*pTest++ != L'{') return FALSE; HEXCHECK(8); HYPHENCHECK(); HEXCHECK(4); HYPHENCHECK(); HEXCHECK(4); HYPHENCHECK(); HEXCHECK(4); HYPHENCHECK(); HEXCHECK(12); if(*pTest++ != L'}') return FALSE; return TRUE; } INTERNAL LPCWSTR CMofData::FindAliasee(READ_ONLY LPWSTR wszAlias) { for(int i = 0; i < m_aObjects.GetSize(); i++) { CMObject* pObject = (CMObject*)m_aObjects[i]; if(pObject->GetAlias() && !wbem_wcsicmp(pObject->GetAlias(), wszAlias)) { IWbemClassObject * pTemp; pTemp = pObject->GetWbemObject(); // check for unresolved aliases in keys if(pTemp && pObject->IsDone() == FALSE) { SCODE sc = pTemp->BeginEnumeration(WBEM_FLAG_KEYS_ONLY | WBEM_FLAG_REFS_ONLY); if(sc != S_OK) return NULL; VARIANT var; VariantInit(&var); while ((sc = pTemp->Next(0, NULL, &var, NULL, NULL)) == S_OK) { if(var.vt == VT_BSTR && IsGuid(var.bstrVal)) { VariantClear(&var); return NULL; } VariantClear(&var); } } return pObject->GetFullPath(); } } return NULL; } HRESULT CMofData::Store(CMofParser & Parser, OLE_MODIFY IWbemLocator* pLocator,IWbemServices *pOverride,BOOL bRollbackable, WCHAR * pUserName, WCHAR * pPassword, WCHAR * pAuthority, IWbemContext * pCtx, GUID LocatorGUID, WBEM_COMPILE_STATUS_INFO *pInfo, BOOL bClassOwnerUpdate, BOOL bInstanceOwnerUpdate, LONG fConnectFlags) { HRESULT hres = WBEM_E_FAILED; int i; CNamespaceCache Cache(pLocator); BOOL bMakingProgress = TRUE; long lClassFlags = 0; long lInstanceFlags = 0; while(bMakingProgress) { bMakingProgress = FALSE; for(i = 0; i< m_aObjects.GetSize(); i++) { CMObject* pObject = (CMObject*)m_aObjects[i]; if(pObject->IsDone()) continue; lClassFlags = pObject->GetClassFlags(); lInstanceFlags = pObject->GetInstanceFlags(); if(bClassOwnerUpdate) { lClassFlags |= WBEM_FLAG_OWNER_UPDATE; } if(bInstanceOwnerUpdate) { lInstanceFlags |= WBEM_FLAG_OWNER_UPDATE; } // Get a namespace pointer for this object. SCODE scRet; IWbemServices* pNamespace = NULL; if(pOverride && !wbem_wcsicmp(L"root\\default", pObject->GetNamespace())) { // AddRef() the namespace pointer, since we will be Releasing // it below pOverride->AddRef(); pNamespace = pOverride; } else { // This will return an AddRef'd pointer pNamespace = Cache.GetNamespace(pObject->GetNamespace(), scRet, pUserName, pPassword ,pAuthority, pCtx, LocatorGUID, fConnectFlags); } if(pNamespace == NULL) { int iMsg = (GotLineNumber(i)) ? ERROR_OPENING : ERROR_OPENING_NO_LINES; PrintError(i, iMsg, scRet, pInfo); return scRet; } // Ensures we release the namespace pointer when we go out of scope CReleaseMe rmns( pNamespace ); // If there isnt a wbem object, try to get one. This will fail if this is a // instance for which the class hasn't been saved just yet. if(pObject->GetWbemObject() == NULL) { IWbemClassObject* pWbemObject = NULL; hres = pObject->CreateWbemObject(pNamespace, &pWbemObject,pCtx); if(hres != S_OK) if(pObject->IsInstance()) continue; else { PrintError(i, (GotLineNumber(i)) ? ERROR_CREATING : ERROR_CREATING_NO_LINES, hres, pInfo); return WBEM_E_FAILED; } bMakingProgress = TRUE; pObject->Reflate(Parser); pObject->SetWbemObject(pWbemObject); if(!pObject->ApplyToWbemObject(pWbemObject, pNamespace,pCtx)) { hres = m_pDbg->hresError; PrintError(i, (GotLineNumber(i)) ? ERROR_CREATING : ERROR_CREATING_NO_LINES, hres, pInfo); return WBEM_E_FAILED; } } // If there are no unresolved aliases, save it! if(pObject->GetNumAliasedValues() == 0 || S_OK == pObject->ResolveAliasesInWbemObject(pObject->GetWbemObject(), (CMofAliasCollection*)this)) { // Save this into WinMgmt // ================== hres = pObject->StoreWbemObject(pObject->GetWbemObject(), lClassFlags, lInstanceFlags, pNamespace, pCtx, pUserName, pPassword ,pAuthority); if(hres != S_OK) { PrintError(i, (GotLineNumber(i)) ? ERROR_STORING : ERROR_STORING_NO_LINES, hres, pInfo); return WBEM_E_FAILED; } pObject->FreeWbemObjectIfPossible(); pObject->Deflate(false); pObject->SetDone(); bMakingProgress = TRUE; } } } // If there is one or more objects that cant be resolved, print and bail for(i = 0; i < m_aObjects.GetSize(); i++) { CMObject* pObject = (CMObject*)m_aObjects[i]; if(pObject && !pObject->IsDone()) { PrintError(i, (GotLineNumber(i)) ? ERROR_RESOLVING : ERROR_RESOLVING_NO_LINES, hres, pInfo); return WBEM_E_FAILED; } } return S_OK; } HRESULT CMofData::RollBack(int nObjects) { return WBEM_E_FAILED; } BOOL CMofData::GotLineNumber(int nIndex) { CMObject* pObject = (CMObject*)m_aObjects[nIndex]; if(pObject == NULL || (pObject->GetFirstLine() == 0 && pObject->GetLastLine() == 0)) return FALSE; else return TRUE; } void CMofData::PrintError(int nIndex, long lMsgNum, HRESULT hres, WBEM_COMPILE_STATUS_INFO *pInfo) { CMObject* pObject = (CMObject*)m_aObjects[nIndex]; TCHAR szMsg[500]; bool bErrorFound = false; if(pInfo) pInfo->ObjectNum = nIndex+1; if(!GotLineNumber(nIndex)) Trace(true, m_pDbg, lMsgNum, nIndex+1); else { Trace(true, m_pDbg, lMsgNum, nIndex+1, pObject->GetFirstLine(), pObject->GetLastLine(), pObject->GetFileName()); if(pInfo) { pInfo->FirstLine = pObject->GetFirstLine(); pInfo->LastLine = pObject->GetLastLine(); } } if(hres) { // A few error messages are retrived from the local resources. This is so that the name can be // injected into the name. if(hres == WBEM_E_NOT_FOUND || hres == WBEM_E_TYPE_MISMATCH || hres == WBEM_E_OVERRIDE_NOT_ALLOWED || hres == WBEM_E_PROPAGATED_QUALIFIER || hres == WBEM_E_VALUE_OUT_OF_RANGE) { Trace(true, m_pDbg, ERROR_FORMAT, hres); Trace(true, m_pDbg, hres, m_pDbg->GetString()); bErrorFound = true; } else { // Get the error from the standard error facility IWbemStatusCodeText * pStatus = NULL; SCODE sc = CoCreateInstance(CLSID_WbemStatusCodeText, 0, CLSCTX_INPROC_SERVER, IID_IWbemStatusCodeText, (LPVOID *) &pStatus); if(sc == S_OK) { BSTR bstrError = 0; BSTR bstrFacility = 0; sc = pStatus->GetErrorCodeText(hres, 0, 0, &bstrError); if(sc == S_OK) { sc = pStatus->GetFacilityCodeText(hres, 0, 0, &bstrFacility); if(sc == S_OK) { IntString is(ERROR_FORMAT_LONG); StringCchPrintfW(szMsg, 500, is, hres, bstrFacility, bstrError); bErrorFound = true; SysFreeString(bstrFacility); } SysFreeString(bstrError); } pStatus->Release(); } // if all else fails, just use the generic error message if(!bErrorFound) { IntString is(ERROR_FORMATEX); StringCchPrintfW(szMsg, 500 ,is, hres); } // Print the error message if(m_pDbg->m_bPrint) printf("%S", szMsg); ERRORTRACE((LOG_MOFCOMP,"%S", szMsg)); } // ELSE get error from standard facility } // IF hres } //*************************************************************************** // // GetFileNames // // DESCRIPTION: // // The amendment local, the localized and neutral file names are passed // in using the BMOF string. These values are separated by commas and // a single letter which indicates what follows. An example string // would be ",aMS_409,nNEUTRAL.MOF,lLocalMof" Notice that the amendment // substring starts with an 'a', the neutral starts with 'n', and the // locale starts with 'l'. // // While the neutral name is required, the locale version isnt. If not // supplied, it will be created. The two character inputs are ASSUMED to // point to preallocated buffers of MAX_PATH size! // //*************************************************************************** HRESULT GetFileNames(TCHAR * pcNeutral, TCHAR * pcLocale, LPWSTR pwszBMOF) { WCHAR * pNeutral=NULL; WCHAR * pLocale=NULL; if(pwszBMOF == NULL) return WBEM_E_INVALID_PARAMETER; // make a copy of the string DWORD dwLen = wcslen(pwszBMOF)+1; WCHAR *pTemp = new WCHAR[dwLen]; if(pTemp == NULL) return WBEM_E_OUT_OF_MEMORY; CDeleteMe dm1(pTemp); StringCchCopyW(pTemp, dwLen, pwszBMOF); // use wcstok to do a seach WCHAR * token = wcstok( pTemp, L"," ); while( token != NULL ) { if(token[0] == L'n') { pNeutral = token+1; CopyOrConvert(pcNeutral, pNeutral, MAX_PATH); } else if(token[0] == L'l') { pLocale = token+1; CopyOrConvert(pcLocale, pLocale, MAX_PATH); } token = wcstok( NULL, L"," ); } // If the neutral name was not specified, that is an error if(pNeutral == NULL) return WBEM_E_INVALID_PARAMETER; // If the local name was not specified, create it and make it the // same as the neutral name except for changing the mfl extension if(pLocale == NULL) { TCHAR * pFr = pcNeutral,* pTo = pcLocale; for(; *pFr && *pFr != '.'; pTo++, pFr++) *pTo = *pFr; *pTo=0; StringCchCatW(pcLocale, MAX_PATH, TEXT(".mfl")); } // make sure that the locale and neutral names are not the same if(!lstrcmpi(pcLocale, pcNeutral)) return WBEM_E_INVALID_PARAMETER; return S_OK; } //*************************************************************************** // // GetLocale // // DESCRIPTION: // // Converts the amendment string to a local number. An example string // would be "MS_409" // //*************************************************************************** HRESULT GetLocale(long * plLocale, WCHAR * pwszAmendment) { if(pwszAmendment == NULL || wcslen(pwszAmendment) != 6) return WBEM_E_INVALID_PARAMETER; *plLocale = 0; swscanf(pwszAmendment+3,L"%x", plLocale); return (*plLocale != 0) ? S_OK : WBEM_E_INVALID_PARAMETER; } //*************************************************************************** // // RecursiveSetAmended // // DESCRIPTION: // // Sets the boolean indicating that an object is to be amended and all of // its parents // //*************************************************************************** void CMofData::RecursiveSetAmended(CMObject * pObj) { // If the object is already amended, then its parents are already set. // In that case, our job is done here! if(pObj->IsAmended()) return; // If the object hasnt been set yet, set it and also set its parents pObj->SetAmended(true); // Look for the parent and do the same if(pObj->IsInstance() || pObj->IsDelete()) return; // run away now if this is a instance! CMoClass * pClass = (CMoClass *)pObj; const WCHAR *pClassName = pClass->GetParentName(); if(pClassName == NULL) return; // Find the parent and recursively set it! for(int i = 0; i< m_aObjects.GetSize(); i++) { CMObject* pObject = (CMObject*)m_aObjects[i]; if(pObject && pObject->GetClassName() && !wbem_wcsicmp(pClassName, pObject->GetClassName())) { RecursiveSetAmended(pObject); } } } //*************************************************************************** // // CMofData::Split // // DESCRIPTION: // // Creates a neutral and locale specific mof. // // Parameters: // pwszBMOF See the GetFileNames() comments // pInfo usual error info // bUnicode if true, then the orignal file was unicode and so the // new files will also be unicode // bAutoRecovery Need to add this pragma if true // pwszAmendment See the GetLocale() comments // //*************************************************************************** HRESULT CMofData::Split(CMofParser & Parser, LPWSTR pwszBMOF, WBEM_COMPILE_STATUS_INFO *pInfo, BOOL bUnicode, BOOL bAutoRecovery, LPWSTR pwszAmendment) { int i; TCHAR cNeutral[MAX_PATH]; TCHAR cLocale[MAX_PATH]; // Determine the file names and locale HRESULT hRes = GetFileNames(cNeutral, cLocale, pwszBMOF); if(hRes != S_OK) return S_OK; long lLocale; hRes = GetLocale(&lLocale, pwszAmendment); if(hRes != S_OK) return S_OK; // Create the output objects COutput Neutral(cNeutral, NEUTRAL, bUnicode, bAutoRecovery, lLocale); COutput Local(cLocale, LOCALIZED, bUnicode, bAutoRecovery, lLocale); if(!Neutral.IsOK() || !Local.IsOK()) return WBEM_E_INVALID_PARAMETER; // Start by determining what is amended for(i = 0; i< m_aObjects.GetSize(); i++) { CMObject* pObject = (CMObject*)m_aObjects[i]; pObject->Reflate(Parser); if(pObject->CheckIfAmended()) { RecursiveSetAmended(pObject); } } // Create the neutral output and the localized output. // These two loops could have been combined, but are // separate for debugging purposes for(i = 0; i< m_aObjects.GetSize(); i++) { CMObject* pObject = (CMObject*)m_aObjects[i]; pObject->Split(Neutral); } for(i = 0; i< m_aObjects.GetSize(); i++) { CMObject* pObject = (CMObject*)m_aObjects[i]; pObject->Split(Local); } return S_OK; }