Hungry Mind , Blog about everything in IT - C#, Java, C++, .NET, Windows, WinAPI, ...

Alpha testers

Есть самец, есть альфа-самец. Есть тестеры, есть альфа-тестеры. Разница понятна?

Casablanca refresh

А тем временем тихонько обновили Casablanca. Предыдущий refresh не работал с Visual Studion 2012 RTM.

DOTA 2 gifts

I have 2 DOTA 2 gifts. Probably you can ask me by email, probably someone will definitely get it. Go, gentlemen.

Realtime ETW consumer howto

Short howto for real time Event Tracing for Windows.

#pragma once

struct ITraceConsumer;

class TraceSession
{

public:
   TraceSession(LPCTSTR szSessionName);
   ~TraceSession();

public:
   bool Start();
   bool EnableProvider(const GUID& providerId, UCHAR level, ULONGLONG anyKeyword = 0, ULONGLONG allKeyword = 0);
   bool OpenTrace(ITraceConsumer *pConsumer);
   bool Process();
   bool CloseTrace();
   bool DisableProvider(const GUID& providerId);
   bool Stop();

   ULONG Status() const;
   LONGLONG PerfFreq() const;

private:
   LPTSTR _szSessionName;
   ULONG _status;
   EVENT_TRACE_PROPERTIES* _pSessionProperties;
   TRACEHANDLE hSession;
   EVENT_TRACE_LOGFILE _logFile;
   TRACEHANDLE _hTrace;

};

Initialization and cleanup:

TraceSession::TraceSession(LPCTSTR szSessionName) : _szSessionName(_tcsdup(szSessionName))
{
}

TraceSession::~TraceSession(void)
{
   delete []_szSessionName;
   delete _pSessionProperties;
}

First step - lets start trace session:

bool TraceSession::Start()
{
   if (!_pSessionProperties) {
      const size_t buffSize = sizeof(EVENT_TRACE_PROPERTIES) + (_tcslen(_szSessionName) + 1) * sizeof(TCHAR);
      _pSessionProperties = reinterpret_cast<EVENT_TRACE_PROPERTIES *>(malloc(buffSize));
      ZeroMemory(_pSessionProperties, buffSize);
      _pSessionProperties->Wnode.BufferSize = buffSize;
      _pSessionProperties->Wnode.ClientContext = 1;
      _pSessionProperties->LogFileMode = EVENT_TRACE_REAL_TIME_MODE;
      _pSessionProperties->LoggerNameOffset = sizeof(EVENT_TRACE_PROPERTIES);
   }

   // Create the trace session.
   _status = StartTrace(&hSession, _szSessionName, _pSessionProperties);

   return (_status == ERROR_SUCCESS);
}

Then lets enable a provider with some filters:

bool TraceSession::EnableProvider(const GUID& providerId, UCHAR level, ULONGLONG anyKeyword, ULONGLONG allKeyword)
{
   _status = EnableTraceEx2(hSession, &providerId, EVENT_CONTROL_CODE_ENABLE_PROVIDER, level, anyKeyword, allKeyword, 0, NULL);
   return (_status == ERROR_SUCCESS);
}

BTW, you can have useful ETW metadata generated by mc.exe utility, just find your specific man manifest file for your ETW provider.

Now we open the trace:

bool TraceSession::OpenTrace(ITraceConsumer *pConsumer)
{   
   if (!pConsumer)
      return false;

   ZeroMemory(&_logFile, sizeof(EVENT_TRACE_LOGFILE));
   _logFile.LoggerName = _szSessionName;
   _logFile.ProcessTraceMode = PROCESS_TRACE_MODE_REAL_TIME | PROCESS_TRACE_MODE_EVENT_RECORD;
   _logFile.EventRecordCallback = &EventRecordCallback;
   _logFile.Context = pConsumer;

   _hTrace = ::OpenTrace(&_logFile);
   return (_hTrace != 0);
}

EventRecordCallback looks like this:

namespace
{

VOID WINAPI EventRecordCallback(_In_ PEVENT_RECORD pEventRecord)
{
   reinterpret_cast<ITraceConsumer *>(pEventRecord->UserContext)->OnEventRecord(pEventRecord);
}

}

But this is just for convenience and OOP, you can have just static function. Up to you.

Finally, call the following method:

bool TraceSession::Process()
{
   _status = ProcessTrace(&_hTrace, 1, NULL, NULL);
   return (_status == ERROR_SUCCESS);
}

Ensure you are running under elevated privileges.

When you decide its over - close the trace, disable any providers and stop the session:

bool TraceSession::CloseTrace()
{
   _status = ::CloseTrace(_hTrace);
   return (_status == ERROR_SUCCESS);
}

bool TraceSession::DisableProvider(const GUID& providerId)
{
   _status = EnableTraceEx2(hSession, &providerId, EVENT_CONTROL_CODE_DISABLE_PROVIDER, 0, 0, 0, 0, NULL);
   return (_status == ERROR_SUCCESS);
}

bool TraceSession::Stop()
{
   _status = ControlTrace(hSession, _szSessionName, _pSessionProperties, EVENT_TRACE_CONTROL_STOP);
   delete _pSessionProperties;
   _pSessionProperties = NULL;
   
   return (_status == ERROR_SUCCESS);
}

Some useful stuff:

ULONG TraceSession::Status() const
{
   return _status;
}

LONGLONG TraceSession::PerfFreq() const
{
   return _logFile.LogfileHeader.PerfFreq.QuadPart;
}

Remember: sessions are global, not tied to your executable. So be sure to write something like this:

if (!traceSession.Start()) {
      if (traceSession.Status() == ERROR_ALREADY_EXISTS) {
         if (!traceSession.Stop() || !traceSession.Start()) {
            // Handle an error
         }
      }
   }

Well, thats it.

Marshall function call to another thread

Short tutorial on how to make cross apartment (cross thread) COM call without interfaces, proxies, stubs and tlbs.

Each apartment (thread) has its own accesible context object. It can be obtained via CoGetObjectContext API function:

CComPtr<IContext> pContext;   
CComPtr<IContextCallback> pContextCallback;
hr = CoGetObjectContext(IID_PPV_ARGS(&pContext));
hr = CoGetObjectContext(IID_PPV_ARGS(&pContextCallback));

One of interfaces you can get is IContextCallback with only one method - ContextCallback. As you might already guessed, we just need to get IContextCallback in target execution context and invoke ContextCallback method in any other context:

pContextCallback.p->AddRef();
_beginthread(&myThread, 65536, pContextCallback.p);
HANDLE h[] = { CreateEvent(NULL, TRUE, FALSE, NULL) };
DWORD dwIndex;
CoWaitForMultipleHandles(0, INFINITE, _countof(h), h, &dwIndex);
[...]
HRESULT _stdcall contextCall(ComCallData *pParam)
{
   OutputDebugString(_T("contextCall\n"));
   return S_OK;
}

void __cdecl myThread(LPVOID p)
{
   CoInitialize(NULL);

   CComPtr<IContextCallback> pContextCallback;
   pContextCallback.Attach(reinterpret_cast<IContextCallback *>(p));
   
   ComCallData cd = { 0, 0, NULL };
   pContextCallback->ContextCallback(&contextCall, &cd, IID_NULL, 0, NULL);

   CoUninitialize();
}

Here I'm starting a separate thread to make cross context call and using CoWaitForMultipleHandles OLE32 function to trigger message loop to accept cross thread COM calls (regular message loop is surely enough, I'm just reluctant to write one). Here are resulting call stacks:

Main thread:
> Win32ConsoleApplication.exe!contextCall(tagComCallData * pParam) Line 227 C++
  ole32.dll!CRemoteUnknown::DoCallback(struct tagXAptCallback *) Unknown
  rpcrt4.dll!_Invoke@12 () Unknown
  rpcrt4.dll!_NdrStubCall2@16 () Unknown
  ole32.dll!_CStdStubBuffer_Invoke@12 () Unknown
  ole32.dll!SyncStubInvoke(struct tagRPCOLEMESSAGE *,struct _GUID const &,class CIDObject *,void *,struct IRpcChannelBuffer *,struct IRpcStubBuffer *,unsigned long *) Unknown
  ole32.dll!StubInvoke(struct tagRPCOLEMESSAGE *,class CStdIdentity *,struct IRpcStubBuffer *,struct IRpcChannelBuffer *,struct tagIPIDEntry *,unsigned long *) Unknown
  ole32.dll!CCtxComChnl::ContextInvoke(struct tagRPCOLEMESSAGE *,struct IRpcStubBuffer *,struct tagIPIDEntry *,unsigned long *) Unknown
  ole32.dll!MTAInvoke(struct tagRPCOLEMESSAGE *,unsigned long,struct IRpcStubBuffer *,class IInternalChannelBuffer *,struct tagIPIDEntry *,unsigned long *) Unknown
  ole32.dll!STAInvoke(struct tagRPCOLEMESSAGE *,unsigned long,struct IRpcStubBuffer *,class IInternalChannelBuffer *,void *,struct tagIPIDEntry *,unsigned long *) Unknown
  ole32.dll!AppInvoke(class CMessageCall *,class CRpcChannelBuffer *,struct IRpcStubBuffer *,void *,void *,struct tagIPIDEntry *,struct LocalThis *) Unknown
  ole32.dll!ComInvokeWithLockAndIPID(class CMessageCall *,struct tagIPIDEntry *) Unknown
  ole32.dll!ComInvoke(class CMessageCall *) Unknown
  ole32.dll!ThreadDispatch(void *) Unknown
  ole32.dll!ThreadWndProc(struct HWND__ *,unsigned int,unsigned int,long) Unknown
  user32.dll!_InternalCallWinProc@20 () Unknown
  user32.dll!_UserCallWinProcCheckWow@32 () Unknown
  user32.dll!_DispatchMessageWorker@8 () Unknown
  user32.dll!_DispatchMessageW@4 () Unknown
  ole32.dll!CCliModalLoop::PeekRPCAndDDEMessage(void) Unknown
  ole32.dll!CCliModalLoop::FindMessage(unsigned long) Unknown
  ole32.dll!CCliModalLoop::HandleWakeForMsg(void) Unknown
  ole32.dll!CCliModalLoop::BlockFn(void * *,unsigned long,unsigned long *) Unknown
  ole32.dll!_CoWaitForMultipleHandles@20 () Unknown
  Win32ConsoleApplication.exe!wmain(int argc, wchar_t * * argv) Line 284 C++
  Win32ConsoleApplication.exe!__tmainCRTStartup() Line 533 C
  Win32ConsoleApplication.exe!wmainCRTStartup() Line 377 C
  kernel32.dll!@BaseThreadInitThunk@12 () Unknown
  ntdll.dll!___RtlUserThreadStart@8 () Unknown
  ntdll.dll!__RtlUserThreadStart@8 () Unknown
Worker thread:
  ntdll.dll!_NtWaitForMultipleObjects@20 () Unknown
  ntdll.dll!_NtWaitForMultipleObjects@20 () Unknown
  KernelBase.dll!_WaitForMultipleObjectsEx@20 () Unknown
  kernel32.dll!_WaitForMultipleObjectsExImplementation@20 () Unknown
  user32.dll!_RealMsgWaitForMultipleObjectsEx@20 () Unknown
  ole32.dll!CCliModalLoop::BlockFn(void * *,unsigned long,unsigned long *) Unknown
  ole32.dll!ModalLoop(class CMessageCall *) Unknown
  ole32.dll!SwitchSTA(class OXIDEntry *,class CMessageCall * *) Unknown
  ole32.dll!CRpcChannelBuffer::SwitchAptAndDispatchCall(class CMessageCall * *) Unknown
  ole32.dll!CRpcChannelBuffer::SendReceive2(struct tagRPCOLEMESSAGE *,unsigned long *) Unknown
  ole32.dll!CCliModalLoop::SendReceive(struct tagRPCOLEMESSAGE *,unsigned long *,class IInternalChannelBuffer *) Unknown
  ole32.dll!CAptRpcChnl::SendReceive(struct tagRPCOLEMESSAGE *,unsigned long *) Unknown
  ole32.dll!CCtxComChnl::SendReceive(struct tagRPCOLEMESSAGE *,unsigned long *) Unknown
  ole32.dll!NdrExtpProxySendReceive(void *,struct _MIDL_STUB_MESSAGE *) Unknown
  rpcrt4.dll!@NdrpProxySendReceive@4 () Unknown
  rpcrt4.dll!_NdrClientCall2 () Unknown
  ole32.dll!_ObjectStublessClient@8 () Unknown
  ole32.dll!_ObjectStubless@0 () Unknown
  ole32.dll!CObjectContext::InternalContextCallback(long (*)(void *),void *,struct _GUID const &,int,struct IUnknown *) Unknown
  ole32.dll!CObjectContext::ContextCallback(long (*)(struct tagComCallData *),struct tagComCallData *,struct _GUID const &,int,struct IUnknown *) Unknown
> Win32ConsoleApplication.exe!myThread(void * p) Line 239 C++
  msvcr110d.dll!_callthreadstart() Line 255 C
  msvcr110d.dll!_threadstart(void * ptd) Line 239 C
  kernel32.dll!@BaseThreadInitThunk@12 () Unknown
  ntdll.dll!___RtlUserThreadStart@8 () Unknown
 ntdll.dll!__RtlUserThreadStart@8 () Unknown

Windows OS has many hidden features, used by Microsoft products. Are we any worse?

Copyright 2007-2011 Chabster