IDeviceTopology how to activate interfaces of devicetopology.h? - c++

Context: I am trying to activate some of the Core Audio Interfaces of "devicetopology.h" such as IAudioLoudness and IAudioPeakMeter using Component Object Model (COM), I need to get the current volume peak of the system's audio and I believe to have written most of the code necessary to make this work, but I am having trouble when trying to activate these interfaces using IPart::Activate.
Issue: When trying to access the IAudioPeakMeter Interface after the supposed activation, my console application throws an error 'Debug Assertion Failed!' and the description just says 'Expression: p!=0'. In Visual Studio an exception is thrown stating: 'Access violation reading location 0x00000000', and a deeper look inside the memory tells me that the variable audio has a value of 0x00000000 <NULL>
Additional Info: While attempting to fix the issue myself, I rewrote the entire code in a separate environment without the use of the CComPtr (Smart Pointer Class for Interfaces) and it threw me a similar error : 'audio is nullptr'
#include <iostream>
#include <devicetopology.h>
#include <mmdeviceapi.h>
#include "atlbase.h"
#include "Functiondiscoverykeys_devpkey.h"
#include "main.h"
int main()
{
HRESULT hr = S_OK;
CoInitialize(NULL);
CComPtr<IMMDeviceEnumerator> enumerator;
hr = enumerator.CoCreateInstance(__uuidof(MMDeviceEnumerator));
if (SUCCEEDED(hr) == true)
{
CComPtr<IMMDeviceCollection> devices;
hr = enumerator->EnumAudioEndpoints(EDataFlow::eRender, DEVICE_STATEMASK_ALL, &devices);
if (SUCCEEDED(hr) == true)
{
UINT count = 0;
devices->GetCount(&count);
for (int i = 0; i < count; i++)
{
CComPtr<IMMDevice> device;
hr = devices->Item(i, &device);
if (SUCCEEDED(hr) == true)
{
CComPtr<IDeviceTopology> topology;
hr = device->Activate(__uuidof(IDeviceTopology), CLSCTX_ALL, NULL, (void**)&topology);
if (SUCCEEDED(hr) == true)
{
CComPtr<IConnector> connector;
hr = topology->GetConnector(0, &connector);
if (SUCCEEDED(hr) == true)
{
CComPtr<IConnector> connectedTo;
hr = connector->GetConnectedTo(&connectedTo);
if (SUCCEEDED(hr) == true)
{
CComPtr<IPart> part;
hr = connectedTo->QueryInterface(&part);
if (SUCCEEDED(hr) == true)
{
CComPtr<IAudioPeakMeter> audio;
hr = part->Activate(CLSCTX_INPROC_SERVER, __uuidof(IAudioPeakMeter), (void**)&audio);
UINT channels;
audio->GetChannelCount(&channels); // Causes reading violation
}
}
}
}
}
}
}
}
I am new to programming with COM and haven't been able to figure out how to move forward from this point, I have tried googling the issue and also looking at some examples using other interfaces from DeviceTopology but haven't been able to fix the issue as of yet.
Thanks in advance.

Related

CoCreateInstance creates empty IBaseFilter for CLSID_WavDest

I am trying to record .wav by using directshow framework in C++ Visual Studio 2010 project. I am following WAV file section of this guide: https://msdn.microsoft.com/en-us/library/windows/desktop/dd375005(v=vs.85).aspx
I have built WavDest.dll, added it to registry, found it in the registry, it can be added as filter in Graphedit. I had unresolved external symbol error for _CLSID_WavDest but had fixed it by including InitGuid.h in my StdAfx.h and by linking the WavDest.lib.
Now I get no errors, program doesn't crash, but I get 0 byte wav file.
Section of code:
res = AddFilterByCLSID(dshow_dev->m_pGraph, CLSID_WavDest, (IBaseFilter **)&dshow_dev->m_pWaveDest, L"WavDest");
res = AddFilterByCLSID(dshow_dev->m_pGraph, CLSID_FileWriter, (IBaseFilter **)&dshow_dev->m_pWaveWriter, L"File Writer");
res = dshow_dev->m_pWaveWriter->QueryInterface(IID_IFileSinkFilter, (void**)&dshow_dev->m_pFileSink);
res = dshow_dev->m_pFileSink->SetFileName(L"D:\\test.wav", NULL);
res = ConnectFilters(dshow_dev->m_pGraph, dshow_dev->m_pCaptureSourceAudio, dshow_dev->m_pWaveDest);
res = ConnectFilters(dshow_dev->m_pGraph, dshow_dev->m_pWaveDest, dshow_dev->m_pWaveWriter);
AddFilterByCLSID for CLSID_WavDest returns S_OK but dshow_dev->m_pWaveDest has following values: -
[CWavDestFilter] {m_cbWavData=0x00000000 m_cbHeader=0x00000000 } CWavDestFilter
.
Therefore, ConnectFilters for m_pWaveDest returns E_Fail and no audio is recorded.
I have tried this with both Debug and Release versions of WavDest.dll registered (first Debug, then unreg Debug and reg Release).
I have checked everything other in code, graph (dshow_dev->m_pGraph) runs fine for video preview and writing AVI file (with audio).
I am sure that that I did something wrong with WavDest integration but I don't know what.
Any help is appreciated.
It was my mistake after all. I have replaced
assert(pResult != NULL);
in this function
// Match a pin by pin direction and connection state.
HRESULT MatchPin(IPin *pPin, PIN_DIRECTION direction, BOOL bShouldBeConnected, BOOL *pResult)
{
assert(pResult != NULL);
BOOL bMatch = FALSE;
BOOL bIsConnected = FALSE;
HRESULT hr = IsPinConnected(pPin, &bIsConnected);
if (SUCCEEDED(hr))
{
if (bIsConnected == bShouldBeConnected)
{
hr = IsPinDirection(pPin, direction, &bMatch);
}
}
if (SUCCEEDED(hr))
{
*pResult = bMatch;
}
return hr;
}
with
if(pResult == NULL);
{
HRESULT hr = E_FAIL;
return hr;
}

ImpersonateLoggedOnUser is successful but secondary process is still run in the initial context

I have an installer that tries to (re)start my application in the current user context after the installation is done.
The installer runs in the SYSTEM context and before launching the application it attempts (and theoretically succeeds) to impersonate the current user. However, when I look in the task manager, I see that my application is running in the SYSTEM context.
This is (a snippet from) my code:
TCHAR szUsername[128] = _T("");
DWORD dwUsernameSize = 128;
GetUserName(szUsername, &dwUsernameSize);
// Lets the calling process impersonate the security context of a logged-on user.
if (!ImpersonateLoggedOnUser(hToken))
{
throw Win32Exception(GetLastError(), _T("Failed to impersonate current user"));
}
TCHAR szUsername2[128] = _T("");
DWORD dwUsernameSize2 = 128;
GetUserName(szUsername2, &dwUsernameSize2);
MLOGD(_T("ProcessUtils::StartProcessInCurrentUserContext: Successfully impersonated %s"), szUsername2);
ProcessUtils::StartProcess(sExeName, lstParams, sWorkingDir, bWaitToFinish, errCode);
ProcessUtils::StartProcess is a wrapper around CreateProcess.
szUsername contains SYSTEM and szUsername2 contains the current user. So ImpersonateLoggedOnUser is successful.
However, as mentioned above, the process is started in the SYSTEM context, not the current user one.
I'm not sure how helpful this might be, but my installer is written in NSIS and it's calling the function that contains the code from above via a plugin written in C/C++.
Does anyone know why my application doesn't start in the current user context?
Win32 CreateProcess creates a process in the same security context as the caller which is SYSTEM (even though you are impersonating).
Think you need to be calling CreateProcessAsUser.
I had a very similar problem a couple of years ago when I was also
working on an installer application. After A LOT of frustration, caused
by failed attempts to start an application in the context of the current
user using CreateProcessAsUser, I've finally given up. After a thorough
search on the web, I've found a briliant implementation that uses
IShellDispatch2 interface. Here is an example:
#include <Windows.h>
#include <exdisp.h>
#include <Shobjidl.h>
#include <Shlwapi.h>
#include <comutil.h>
#include <SHLGUID.h>
#include <cstdlib>
#include <iostream>
#pragma comment(lib, "Shlwapi.lib")
#pragma comment(lib, "comsuppw.lib")
bool ShellExecuteAsCurrentUser(const TCHAR *pcOperation, const TCHAR *pcFileName, const TCHAR *pcParameters,
const TCHAR *pcsDirectory, const DWORD dwShow)
{
bool bSuccess = false;
IShellWindows *psw = NULL;
HRESULT hr = CoCreateInstance(CLSID_ShellWindows, NULL, CLSCTX_LOCAL_SERVER, IID_PPV_ARGS(&psw));
if(SUCCEEDED(hr))
{
HWND hwnd = 0;
IDispatch* pdisp = NULL;
_variant_t vEmpty;
if(S_OK == psw->FindWindowSW(&vEmpty, &vEmpty, SWC_DESKTOP, reinterpret_cast<long*>(&hwnd), SWFO_NEEDDISPATCH, &pdisp))
{
if((hwnd != NULL) && (hwnd != INVALID_HANDLE_VALUE))
{
IShellBrowser *psb;
hr = IUnknown_QueryService(pdisp, SID_STopLevelBrowser, IID_PPV_ARGS(&psb));
if(SUCCEEDED(hr))
{
IShellView *psv = NULL;
hr = psb->QueryActiveShellView(&psv);
if(SUCCEEDED(hr))
{
IDispatch *pdispBackground = NULL;
HRESULT hr = psv->GetItemObject(SVGIO_BACKGROUND, IID_PPV_ARGS(&pdispBackground));
if(SUCCEEDED(hr))
{
IShellFolderViewDual *psfvd = NULL;
hr = pdispBackground->QueryInterface(IID_PPV_ARGS(&psfvd));
if(SUCCEEDED(hr))
{
IDispatch *pdisp = NULL;
hr = psfvd->get_Application(&pdisp);
if(SUCCEEDED(hr))
{
IShellDispatch2 *psd;
hr = pdisp->QueryInterface(IID_PPV_ARGS(&psd));
if(SUCCEEDED(hr))
{
_variant_t verb(pcOperation);
_variant_t file(pcFileName);
_variant_t para(pcParameters);
_variant_t dir(pcsDirectory);
_variant_t show(dwShow);
if(SUCCEEDED(psd->ShellExecute(file.bstrVal, para, vEmpty, verb, show)))
bSuccess = true;
psd->Release();
psd = NULL;
}
pdisp->Release();
pdisp = NULL;
}
}
pdispBackground->Release();
pdispBackground = NULL;
}
psv->Release();
psv = NULL;
}
psb->Release();
psb = NULL;
}
}
pdisp->Release();
pdisp = NULL;
}
psw->Release();
psw = NULL;
}
return bSuccess;
}
int main(int argc, char *argv[])
{
CoInitialize(NULL);
if(ShellExecuteAsCurrentUser(L"open", L"notepad", nullptr, nullptr, SW_SHOWNORMAL))
std::cout << "SUCCESS" << std::endl;
CoUninitialize();
return 0;
}
This is just a quick demo, the implementation of ShellExecuteAsCurrentUser can be
improved by using smart pointers for COM interfaces and some refactoring. This method
worked for me on versions WinXP SP3 - Win 8.1, not sure if it works on Windows 10. For
more details, check the authors github page:
https://github.com/lordmulder/stdutils/tree/master/Contrib/StdUtils
If you had read the documentation for CreateProcess, you would have found the answer to your question in the first three sentences:
Creates a new process and its primary thread. The new process runs in the security context of the calling process.
If the calling process is impersonating another user, the new process uses the token for the calling process, not the impersonation token.
There really isn't much else to say; the behaviour you describe is as documented. If you want to create a process as another user, you must use CreateProcessAsUser or one of the related functions.

ClrRuntimeHost->Start not functioning as expected

So I've create a project that uses a CLRRunTimeHost to suprise suprise host a managed library.
However I've run into a problem. When I run this it works absolutely fine with no hiccups, however after distributing it to a few people the error reports started to flow in. After around 11 hours just today I've figured out the source to be this. The entire function passes through fine, however, upon the last check or
hr = pClrRuntimeHost->Start();
It fails and doesn't start. I've talked and guided all of the people that are using it through library installs etc. and none can get it to work, I even gave the source to a close friend that has VS2015 installed and he fell victim to the same error.
Here's the full function:
ICLRRuntimeHost* StartCLR(LPCWSTR dotNetVersion)
{
HRESULT hr;
ICLRMetaHost* pClrMetaHost = NULL;
ICLRRuntimeInfo* pClrRuntimeInfo = NULL;
ICLRRuntimeHost* pClrRuntimeHost = NULL;
// Get the CLRMetaHost that tells us about .NET on this machine
hr = CLRCreateInstance(CLSID_CLRMetaHost, IID_ICLRMetaHost, (LPVOID*)&pClrMetaHost);
if (hr == S_OK)
{
// Get the runtime information for the particular version of .NET
hr = pClrMetaHost->GetRuntime(dotNetVersion, IID_PPV_ARGS(&pClrRuntimeInfo));
if (hr == S_OK)
{
// Check if the specified runtime can be loaded into the process. This
// method will take into account other runtimes that may already be
// loaded into the process and set pbLoadable to TRUE if this runtime can
// be loaded in an in-process side-by-side fashion.
BOOL fLoadable;
hr = pClrRuntimeInfo->IsLoadable(&fLoadable);
if ((hr == S_OK) && fLoadable)
{
// Load the CLR into the current process and return a runtime interface
// pointer.
hr = pClrRuntimeInfo->GetInterface(CLSID_CLRRuntimeHost,
IID_PPV_ARGS(&pClrRuntimeHost));
if (hr == S_OK)
{
// Start it. This is okay to call even if the CLR is already running
hr = pClrRuntimeHost->Start();
if (hr == S_OK)
{
// Success!
return pClrRuntimeHost;
}
}
}
}
}
// Cleanup if failed
if (pClrRuntimeHost)
{
pClrRuntimeHost->Release();
pClrRuntimeHost = NULL;
}
if (pClrRuntimeInfo)
{
pClrRuntimeInfo->Release();
pClrRuntimeInfo = NULL;
}
if (pClrMetaHost)
{
pClrMetaHost->Release();
pClrMetaHost = NULL;
}
return NULL;
}
And I'm calling it like this:
ICLRRuntimeHost* pClr = StartCLR(L"v4.0.30319");

Retrieve HTML source from CHtmlView (visual studio 6)

Im working on an application that uses a CHtmlView. New requirements mean I would like to be able to get the HTML source from the class to parse for a specific tag (or if possible just get the information in the tag). This would be fine if we were using a newer system and I could use CHtmlView::GetSource but it doesn't exist.
I've had a pretty extensive search online but am pretty new to most of Windows programming and haven't been able to achieve anything useful yet.
So if anyone has an example of how to extract the HTML from a CHtmlView without using GetSource I would appreciate seeing it. I've tried
BSTR bstr;
_bstr_t * bstrContainer;
HRESULT hr;
IHTMLDocument2 * pDoc;
IDispatch * pDocDisp = NULL;
pDocDisp = this->GetHtmlDocument();
if (pDocDisp != NULL) {
hr = pDocDisp->QueryInterface (IID_IHTMLDocument2, (void**)&pDoc);
if (SUCCEEDED(hr)) {
if (pDoc->toString(&bstr) != S_OK) {
//error...
} else {
bstrContainer = new _bstr_t(bstr);
size = (bstrContainer->length()+1)*2;
realString = new char[size];
strncpy(realString, (char*)(*bstrContainer), size);
}
} else {
//error
}
pDocDisp->Release();
}
but it mostly just gives me "[object]" in realString. Like I said, new to Windows.
Any help appreciated.
Add this helper function into your CHtmlView-derived class to retrieve the html source. Remember to check the returned boolean state from this function as com-interface can be quite unreliable when system resources are low.
/* ============================================== */
BOOL CTest1View::GetHtmlText(CString &strHtmlText)
{
BOOL bState = FALSE;
// get IDispatch interface of the active document object
IDispatch *pDisp = this->GetHtmlDocument();
if (pDisp != NULL)
{ // get the IHTMLDocument3 interface
IHTMLDocument3 *pDoc = NULL;
HRESULT hr = pDisp->QueryInterface(IID_IHTMLDocument3, (void**) &pDoc);
if (SUCCEEDED(hr))
{ // get root element
IHTMLElement *pRootElement = NULL;
hr = pDoc->get_documentElement(&pRootElement);
if (SUCCEEDED(hr))
{ // get html text into bstr
BSTR bstrHtmlText;
hr = pRootElement->get_outerHTML(&bstrHtmlText);
if (SUCCEEDED(hr))
{ // convert bstr to CString
strHtmlText = bstrHtmlText;
bState = TRUE;
SysFreeString(bstrHtmlText);
}
pRootElement->Release();
}
pDoc->Release();
}
pDisp->Release();
}
return bState;
}

How to globally mute and unmute sound in Vista and 7, and to get a mute state?

I'm using the old good Mixer API right now, but it does not work as expected on Windows Vista & 7 in the normal, not in XP compatibility mode. It mutes the sound for the current app only, but I need a global (hardware) mute. How to rearch the goal? Is there any way to code this w/o COM interfaces and strange calls, in pure C/C++?
The audio stack was significantly rewritten for Vista. Per-application volume and mute control was indeed one of the new features. Strange calls will be required to use the IAudioEndpointVolume interface.
I recently dealt with this same issue. We have a Windows application that uses the sound system for alarms. We cannot abide the user muting the sound system inadvertently. Here is how I was able to use the interface suggested above to address this issue:
During initialization I added a function to initialize a member of type IAudioEndpointVolume. It was a bit tricky and the help wasn't as helpful as it could be. Here's how to do it:
/****************************************************************************
** Initialize the Audio Endpoint (Only for post XP systems)
****************************************************************************/
void CMuteWatchdog::InitAudioEndPoint(void)
{
HRESULT hr;
IMMDeviceEnumerator * pDevEnum;
IMMDevice * pDev;
const CLSID CLSID_MMDeviceEnumerator = __uuidof(MMDeviceEnumerator);
const IID IID_IMMDeviceEnumerator = __uuidof(IMMDeviceEnumerator);
hr = CoCreateInstance(CLSID_MMDeviceEnumerator, NULL,
CLSCTX_ALL, IID_IMMDeviceEnumerator,
(void**)&pDevEnum);
m_pIaudEndPt = NULL;
if(hr == S_OK)
{
hr = pDevEnum->GetDefaultAudioEndpoint(eRender, eConsole, &pDev);
if(hr == S_OK)
{
DWORD dwClsCtx;
const IID iidAEV = __uuidof(IAudioEndpointVolume);
dwClsCtx = 0;
hr = pDev->Activate(iidAEV, dwClsCtx, NULL, (void**) &m_pIaudEndPt);
if(hr == S_OK)
{
// Everything is groovy.
}
else
{
m_pIaudEndPt = NULL; // Might mean it's running on XP or something. Don't use.
}
pDev->Release();
}
pDevEnum->Release();
}
}
...
About once per second I added a simple call to the following:
////////////////////////////////////////////////////////////////////////
// Watchdog function for mute.
void CMuteWatchdog::GuardMute(void)
{
if(m_pIaudEndPt)
{
BOOL bMute;
HRESULT hr;
bMute = FALSE;
hr = m_pIaudEndPt->GetMute(&bMute);
if(hr == S_OK)
{
if(bMute)
{
m_pIaudEndPt->SetMute(FALSE, NULL);
}
}
}
}
Finally, when the program exits just remember to release the allocated resource.
////////////////////////////////////////////////////////////////////////
// De-initialize the watchdog
void CMuteWatchdog::OnClose(void)
{
if(m_pIaudEndPt)
{
m_pIaudEndPt->Release();
m_pIaudEndPt = NULL;
}
}