Create your own CLR Profiler

A CLR profiler is a very powerful way to examine your managed code. Let’s create your own CLR Profiler.

This simple example will

1. Start the specified managed application that we will profile

2. Intercept the creation of all managed objects

3. Calculate the class name for the object (Like “System.String” or “WindowsFormsApplication1.Paddle”)

4. Accumulate per class the number of instances and the total size.

5. Upon exit of the profilee, the data is written to a file on your Desktop called “LogOutputClassMap.csv”

6. An attempt to start the file will be made. If you have Excel on the machine, it will be opened with Excel.

7. In Excel, hit AltN->T (Insert Table), then Enter to accept the default table size.

8. You can sort, filter and query the data in Excel.

I see output like so:

clip_image001

We’ll create a solution with 2 C++ projects. The first is ClrLauncher, which will produce ClrLauncher.exe.

It’s almost the same as Use reflection from native C++ code to run managed code

Start Visual Studio

File->New Project->C++->Win32->Win32 Project.

Name it "ClrLauncher"

Application Type: Windows Application

Choose Additional options: Empty Project

Project->Add New Item->C++ File "ClrLauncher.cpp"

Paste in the code below marked “ClrLauncher” (Note : there are 2 different sections of code below. Use the one marked “ClrLauncher”)

In the command line arguments, paste in 3 parameters

       Full Path to exe (in quotes if there's a space embedded)

       Name of type to instantiate (Like "WindowsFormsApplication1.Form1")

       Name of method on type to invoke (like "ShowDialog")

I used: "C:\Users\calvinh\Documents\Visual Studio 2013\Projects\Paddle\Paddle\bin\Debug\Paddle.exe"  WindowsFormsApplication1.Form1 ShowDialog

Now before we add the 2nd project, we can just hit F5 to run the program, which will start the CLR and launch the target Breakout game.

See Create and play your own Breakout game

Now we’ll add the 2nd project to the solution:

File->Add New project->C++ Win32 Project “ClrProfiler”

Application Type: Dll

Choose Additional options: Empty Project

Now right click on the Source Files node of the ClrProfiler project in Solution Explorer, and add a new C++ Item: ClrProfiler.cpp

Paste in the “ClrProfiler” code below.

Build the project.

Make sure the code in ClrLauncher: EnableMyProfiler points to the ClrProfiler.dll (the full path is shown in the Output window on a full build)

Run the project again. This time, because ClrProfiler.dll exists, the CLR will instantiate it and calls to Object Allocations are intercepted.

The ClrProfiler code can be run separately: when the CLR starts, it looks for environment variables specifying a profiler.

See Setting Up a Profiling Environment

Try setting the environment variables, then start Visual Studio! I get thousands of classes.

<Code for ClrLauncher>

 //
/*
ClrLauncher: will launch Managed code
Pass in 3 command line parameters:
    Full Path to exe (in quotes if there's a space embedded)
    Name of type to instantiate (Like "WindowsFormsApplication1.Form1")
    Name of method on type to invoke (like "ShowDialog")

This sample also sets 3 environment variables specifying a Clr Profiler, 
  which are ignored if the profiler is not found.

Start Visual Studio
File->New Project->C++->Win32->Win32 Project.
Name it "ClrLauncher"
Application Type: Windows Application
Choose Additional options: Empty Project

Project->Add New Item->C++ File "ClrLauncher.cpp"

paste in the code below:

Pass the asmFileName and typeNameToInstantiate in the cmd line args
Project->Properties->Configuration Properties->Debugging->Command Arguments
For mine, I used: ("Create and play your own Breakout game": https://blogs.msdn.com/b/calvin_hsia/archive/2013/10/30/10461927.aspx )
"C:\Users\calvinh\Documents\Visual Studio 2013\Projects\Paddle\Paddle\bin\Debug\Paddle.exe"  WindowsFormsApplication1.Form1 ShowDialog

You can just hit F5 to run have this code start your application.


Note: the default debugger is Native for this project, but because we start the CLR
managed debugging is useful too.
Project->Properties->Configuration Properties->Debugging->Debugger Type: Mixed
*/

#include "atlsafe.h"
#include "atlcom.h"

#import <mscorlib.tlb> raw_interfaces_only rename("ReportEvent","ReportEventManaged")

#include <metahost.h>
#pragma comment(lib,"mscoree.lib")
#include <mscoree.h>

#include "shellapi.h"


using namespace mscorlib;

void EnableMyProfiler()
{
#if _DEBUG
    WCHAR filename[] = L"c:\\users\\calvinh\\documents\\visual studio 2013\\Projects\\ClrLauncher\\Debug\\ClrProfiler.dll";
#else
    WCHAR filename[] = L"c:\\users\\calvinh\\documents\\visual studio 2013\\Projects\\ClrLauncher\\Release\\ClrProfiler.dll";
#endif
    /*
    // set these 3 env variables to enable profiling
    // indicate that the profiler object to be created lives in this module
set COR_ENABLE_PROFILING=1
set COR_PROFILER={EA6ED0D7-6196-457D-9A2D-B00F1CC3EA8E}
set COR_PROFILER_PATH=c:\users\calvinh\documents\visual studio 2013\Projects\ClrLauncher\Debug\ClrProfiler.dll
    (COR_PROFILER_PATH should have no quotes)
    Any program that uses the CLR will loook for these environment variables
    Try setting them, then start VS
    */
    SetEnvironmentVariable(L"COR_ENABLE_PROFILING", L"1");
    SetEnvironmentVariable(L"COR_PROFILER", L"{EA6ED0D7-6196-457D-9A2D-B00F1CC3EA8E}");
    SetEnvironmentVariable(L"COR_PROFILER_PATH", filename);
}

// find a specified assembly from an AppDomain by enumerating assemblies
HRESULT GetAssemblyFromAppDomain(
    _AppDomain* pAppDomain,
    LPCWSTR wszAssemblyName,
    _Deref_out_opt_ _Assembly **ppAssembly)
{
    *ppAssembly = NULL;
    // get the assemblies into a safearray
    SAFEARRAY *pAssemblyArray = NULL;
    HRESULT hr = pAppDomain->GetAssemblies(&pAssemblyArray);
    if (FAILED(hr))
    {
        return hr;
    }
    // put the safearray into a smart ptr, so it gets released
    CComSafeArray<IUnknown*>    csaAssemblies;
    csaAssemblies.Attach(pAssemblyArray);

    size_t cchAssemblyName = wcslen(wszAssemblyName);

    long cAssemblies = csaAssemblies.GetCount();
    for (long i = 0; i<cAssemblies; i++)
    {
        CComPtr<_Assembly> spAssembly;
        spAssembly = csaAssemblies[i];
        if (spAssembly == NULL)
            continue;
        CComBSTR cbstrAssemblyFullName;
        hr = spAssembly->get_FullName(&cbstrAssemblyFullName);
        if (FAILED(hr))
            continue;
        // is it the one we want?
        if (cbstrAssemblyFullName != NULL &&
            _wcsnicmp(cbstrAssemblyFullName,
            wszAssemblyName,
            cchAssemblyName) == 0)
        {
            *ppAssembly = spAssembly.Detach();
            hr = S_OK;
            break;
        }
    }
    if (*ppAssembly == 0)
    {
        hr = E_FAIL;
    }
    return hr;
}


void StartClrCode(CComBSTR asmFileName, CComBSTR typeNameToInstantiate, CComBSTR typeMemberToCall)
{
    CComPtr<ICLRMetaHost> spClrMetaHost;
    // get a MetaHost
    HRESULT hr = CLRCreateInstance(
        CLSID_CLRMetaHost,
        IID_PPV_ARGS(&spClrMetaHost)
        );
    _ASSERT(hr == S_OK);

    // get a particular runtime version
    CComPtr<ICLRRuntimeInfo> spCLRRuntimeInfo;
    hr = spClrMetaHost->GetRuntime(L"v4.0.30319",
        IID_PPV_ARGS(&spCLRRuntimeInfo)
        );
    _ASSERT(hr == S_OK);

    // get the CorRuntimeHost
    CComPtr<ICorRuntimeHost> spCorRuntimeHost;
    hr = spCLRRuntimeInfo->GetInterface(
        CLSID_CorRuntimeHost,
        IID_PPV_ARGS(&spCorRuntimeHost)
        );
    _ASSERT(hr == S_OK);

    // Start the CLR
    hr = spCorRuntimeHost->Start();
    _ASSERT(hr == S_OK);

    // get the Default app domain as an IUnknown
    CComPtr<IUnknown> spAppDomainThunk;
    hr = spCorRuntimeHost->GetDefaultDomain(&spAppDomainThunk);
    _ASSERT(hr == S_OK);

    // convert the Appdomain IUnknown to a _AppDomain
    CComPtr<_AppDomain> spAppDomain;
    hr = spAppDomainThunk->QueryInterface(IID_PPV_ARGS(&spAppDomain));
    _ASSERT(hr == S_OK);

    // Get the mscorlib assembly
    CComPtr<_Assembly> sp_mscorlib;
    hr = GetAssemblyFromAppDomain(spAppDomain, L"mscorlib", &sp_mscorlib);
    _ASSERT(hr == S_OK);


    // get the Type of "System.Reflection.Assembly"
    CComPtr<_Type> _typeReflectionAssembly;
    hr = sp_mscorlib->GetType_2(
        CComBSTR(L"System.Reflection.Assembly"),
        &_typeReflectionAssembly);
    _ASSERT(hr == S_OK);

    // create the array of args. only need 1 argument, array 
    auto psaLoadFromArgs = SafeArrayCreateVector(
        VT_VARIANT,
        0, //start array at 0
        1); //# elems = 1
    long index = 0;
    // set the array element
    CComVariant arg1(asmFileName); // the argument: the asm to load
    SafeArrayPutElement(psaLoadFromArgs, &index, &arg1);

    //invoke the "Assembly.LoadFrom" public static member to load the paddle.exe
    CComVariant cvtEmptyTarget;
    CComVariant cvtLoadFromReturnValue;
    hr = _typeReflectionAssembly->InvokeMember_3(
        CComBSTR(L"LoadFrom"),
        static_cast<BindingFlags>(BindingFlags_InvokeMethod |
        BindingFlags_Public |
        BindingFlags_Static),
        nullptr, //Binder
        cvtEmptyTarget, // target. Since the method is static, an empty variant
        psaLoadFromArgs, //args
        &cvtLoadFromReturnValue);
    _ASSERT(hr == S_OK);
    SafeArrayDestroy(psaLoadFromArgs); // don't need args any more
    _ASSERT(cvtLoadFromReturnValue.vt == VT_DISPATCH);

    // get the assembly from the return value
    CComPtr<_Assembly> srpAssemblyTarget;
    srpAssemblyTarget.Attach(
        static_cast<_Assembly *>(cvtLoadFromReturnValue.pdispVal
        ));

    // get the desired type from the assembly
    CComPtr<_Type> _typeForm;
    hr = srpAssemblyTarget->GetType_2(
        typeNameToInstantiate,
        &_typeForm);
    _ASSERT(hr == S_OK && _typeForm != nullptr);

    // create an instance of the target type
    CComVariant resTargetInstance;
    hr = srpAssemblyTarget->CreateInstance(
        typeNameToInstantiate,
        &resTargetInstance);
    _ASSERT(hr == S_OK);

    // create an array var for return value of Type->GetMember
    SAFEARRAY *psaMember = nullptr;
    hr = _typeForm->GetMember(typeMemberToCall,
        MemberTypes_Method,
        (BindingFlags)(BindingFlags_Instance + BindingFlags_Public),
        &psaMember);
    _ASSERT(hr == S_OK);

    // put into SafeArray so it gets released
    CComSafeArray<IUnknown *> psaMem;
    psaMem.Attach(psaMember);

    // Get the Method Info for "ShowDialog" from the 1st type in the array
    CComPtr<_MethodInfo> methodInfo;
    hr = psaMem[0]->QueryInterface(
        IID_PPV_ARGS(&methodInfo)
        );
    _ASSERT(hr == S_OK);

    // invoke the ShowDialog method on the Form(WinForm) or Window (wpf)
    hr = methodInfo->Invoke_3(
        resTargetInstance,
        nullptr, //parameters
        nullptr // return value
        );
    _ASSERT(hr == S_OK);

    // stop the runtime
    hr = spCorRuntimeHost->Stop();
    _ASSERT(hr == S_OK);
}

int APIENTRY _tWinMain(_In_ HINSTANCE hInstance,
    _In_opt_ HINSTANCE hPrevInstance,
    _In_ LPTSTR    lpCmdLine,
    _In_ int       nCmdShow)
{
    CoInitializeEx(0, COINIT_APARTMENTTHREADED);
    EnableMyProfiler();
    // convert the single cmd line string to multiple args (handles quotes too)
    // "C:\Users\calvinh\Documents\Visual Studio 2012\Projects\Cartoon\Cartoon\bin\Debug\Cartoon.exe"  Cartoon.MainWindow ShowDialog
    // "C:\Users\calvinh\Documents\Visual Studio 2012\Projects\Paddle\Paddle\bin\Debug\Paddle.exe"  WindowsFormsApplication1.Form1 ShowDialog
    int nArgs = 0;
    LPWSTR *pCmdLineArgs = CommandLineToArgvW(lpCmdLine, &nArgs);
    if (nArgs == 3)
    {
        // the full file name on disk for the assembly. Can be WPF or Winform (both have ShowDialog methods)
        CComBSTR asmFileName = pCmdLineArgs[0];
        // the name of the type to instantiate
        CComBSTR typeNameToInstantiate = pCmdLineArgs[1];
        // the static public member of the type to call
        CComBSTR typeMemberToCall = pCmdLineArgs[2];
        StartClrCode(asmFileName, typeNameToInstantiate, typeMemberToCall);
    }
    else
    {
        MessageBox(0, L"Enter 3 parameters: full path to assembly to load (perhaps quoted), name of Type to create (like \"WindowsFormsApplication1.Form1\", Member Name to call", L"", 0);
    }
    return 0;
}

</Code for ClrLauncher>

<Code for ClrProfiler>

 // ClrProfiler.cpp : Defines the exported functions for the DLL application.
// File->New Project->VC++->Win32 Project "ClrProfiler"
// in the Win32 Application Wizard, choose application type: Dll, Empty Project.
// Right click on Solution Explroer->Source Files->Add New Item->C++ File. "ClrProfiler.cpp"
//


#include "atlsafe.h"
#include "atlcom.h"

#include <cor.h>
#include <corprof.h>

#include "string"
#include "unordered_map"
#include "map"
#include "iostream"
#include "fstream"

#include "shlobj.h"
#include "shellapi.h"

using namespace std;

//using namespace mscorlib;

#define MAXCLASSNAMELEN 900

#define PASTE2(x,y) x##y
#define PASTE(x,y) PASTE2(x,y)
#define __WFUNCDNAME__ PASTE(L, __FUNCDNAME__)

#define PROF_NOT_IMP(methodName, ...) \
    STDMETHOD(methodName) (__VA_ARGS__) \
{ \
if (m_fLoggingOn)  LogOutput(__WFUNCDNAME__);  \
    return S_OK;  \
} \

// let's define a Guid that represents our profiler
// VS->Tools->Create GUID
#include "initguid.h"
// {EA6ED0D7-6196-457D-9A2D-B00F1CC3EA8E}
DEFINE_GUID(CLSID_MyProfiler,
    0xea6ed0d7, 0x6196, 0x457d, 0x9a, 0x2d, 0xb0, 0xf, 0x1c, 0xc3, 0xea, 0x8e);


// a class that holds information about CLR objects
class ClrClassStats {
public:
    wstring className; // name of class
    int nInstances; // # of instances ever created
    long nSize; // total size (some instances vary in size, like arrays and string)
};
CComQIPtr<ICorProfilerInfo2 > g_pCorProfilerInfo; // can be null

// declare a COM profiler that implements an interface ICorProfilerCallback3
class MyProfiler :
    public ICorProfilerCallback3,
    public CComObjectRootEx<CComSingleThreadModel>,
    public CComCoClass<MyProfiler, &CLSID_MyProfiler>
{
public:
    BEGIN_COM_MAP(MyProfiler)
        COM_INTERFACE_ENTRY_IID(CLSID_MyProfiler, MyProfiler)
        COM_INTERFACE_ENTRY(ICorProfilerCallback2)
        COM_INTERFACE_ENTRY(ICorProfilerCallback3)
    END_COM_MAP()
    DECLARE_NOT_AGGREGATABLE(MyProfiler)
    DECLARE_NO_REGISTRY()

    MyProfiler()
    {
        LogOutput(L"MyProfiler Constructor");
    }
    bool m_fLoggingOn;
    void LogOutput(LPCWSTR wszFormat, ...)
    {
        if (IsDebuggerPresent())
        {
            SYSTEMTIME st;
            GetLocalTime(&st);
            WCHAR buf[1000];
            swprintf_s(buf, L"%2d/%02d/%02d %2d:%02d:%02d:%03d thrd=%d ", st.wMonth, st.wDay, st.wYear - 2000, st.wHour,
                st.wMinute, st.wSecond, st.wMilliseconds, GetCurrentThreadId());
            OutputDebugStringW(buf);
            va_list insertionArgs;
            va_start(insertionArgs, wszFormat);
            _vsnwprintf_s(buf, _countof(buf), wszFormat, insertionArgs);
            va_end(insertionArgs);
            OutputDebugStringW(buf);
            OutputDebugStringW(L"\r\n");
        }
    }

    // ICorProfilerCallback2 >>

    // STARTUP/SHUTDOWN EVENTS
    STDMETHOD(Initialize)(IUnknown *pICorProfilerInfoUnk)
    {
        m_fLoggingOn = true;
        // tell clr which events we want to be called for
        DWORD dwEventMask = COR_PRF_ENABLE_STACK_SNAPSHOT
            | COR_PRF_ENABLE_OBJECT_ALLOCATED
            | COR_PRF_MONITOR_GC //GarbageCollectionStarted, GarbageCollectionFinished, MovedReferences, SurvivingReferences, ObjectReferences, ObjectsAllocatedByClass, RootReferences, HandleCreated, HandleDestroyed, and FinalizeableObjectQueued callbacks.
            | COR_PRF_MONITOR_OBJECT_ALLOCATED // Object
            //      | COR_PRF_MONITOR_ENTERLEAVE // method enter/leave
            | COR_PRF_MONITOR_CLASS_LOADS // ClassLoad and ClassUnload 
            | COR_PRF_MONITOR_MODULE_LOADS // ModuleLoad, ModuleUnload, and ModuleAttachedToAssembly callbacks.
            | COR_PRF_MONITOR_ASSEMBLY_LOADS // AssemblyLoad and AssemblyUnload callbacks
            | COR_PRF_MONITOR_APPDOMAIN_LOADS // ModuleLoad, ModuleUnload, and ModuleAttachedToAssembly callbacks.
            | COR_PRF_MONITOR_SUSPENDS //Controls the RuntimeSuspend, RuntimeResume, RuntimeThreadSuspended, and RuntimeThreadResumed callbacks.
            | COR_PRF_MONITOR_THREADS // Controls the ThreadCreated, ThreadDestroyed, ThreadAssignedToOSThread, and ThreadNameChanged callbacks
            ;
        g_pCorProfilerInfo = pICorProfilerInfoUnk;
        g_pCorProfilerInfo->SetEventMask(dwEventMask);
        return S_OK;
    }
    STDMETHOD(ObjectAllocated)(ObjectID objectID, ClassID classID)
    { // gets called whenever a managed obj is created
        HRESULT hr = S_OK;
        _csectClassMap.Lock();
        ClrClassStats *pClrObjStats = nullptr;
        // beware of threading!
        auto res = _ClassDictionary.find(classID);
        if (res != _ClassDictionary.end())
        {// already exists
            pClrObjStats = &res->second;
        }
        else
        { // not found. figure out the name of the obj
            bool fIsArrayClass = false;
            CorElementType elemType;
            ClassID arrayClassId;
            ULONG arrayRank;
            hr = g_pCorProfilerInfo->IsArrayClass(
                classID,
                &elemType,
                &arrayClassId,
                &arrayRank);
            if (hr == S_OK)
            {
                if (elemType == ELEMENT_TYPE_CLASS)
                {
                    fIsArrayClass = true;
                    classID = arrayClassId;
                }
                else
                {
                    hr = E_FAIL;  // todo
                }
            }
            if (hr != E_FAIL)// (S_FALSE if not array)
            {
                ModuleID moduleID = 0;
                mdTypeDef tdToken;
                ClassID classIdParent;
                ClassID typeArgs[20]; //generics
                ULONG32 cNumTypeArgs;
                cNumTypeArgs = _countof(typeArgs);
                hr = g_pCorProfilerInfo->GetClassIDInfo2(
                    classID,
                    &moduleID,
                    &tdToken,
                    &classIdParent,
                    cNumTypeArgs,
                    &cNumTypeArgs,
                    typeArgs);
                if (hr == S_OK && moduleID != 0)
                {
                    CComPtr<IMetaDataImport> pIMetaDataImport;
                    if (g_pCorProfilerInfo->GetModuleMetaData(
                        moduleID,
                        ofRead,
                        IID_IMetaDataImport,
                        (LPUNKNOWN *)&pIMetaDataImport) == S_OK)
                    {
                        WCHAR wszClassName[MAXCLASSNAMELEN] = { 0 };
                        mdToken tkExtends = NULL;
                        hr = pIMetaDataImport->GetTypeDefProps(
                            tdToken,
                            wszClassName,
                            _countof(wszClassName),
                            0,
                            0,
                            &tkExtends);
                        if (hr == S_OK)
                        {
                            ClrClassStats classStat =
                            {
                                wszClassName //assign WCHAR to wstring
                            };
                            if (fIsArrayClass)
                            {
                                classStat.className.append(L"[]");
                            }
                            if (m_fLoggingOn)
                            {
                                LogOutput(classStat.className.c_str());
                            }
                            auto res = _ClassDictionary.insert(
                                pair<ClassID, ClrClassStats>(
                                classID,
                                classStat)
                                );
                            pClrObjStats = &res.first->second;
                        }
                    }
                }
            }
        }
        if (pClrObjStats != nullptr)
        {
            ULONG ulObjSize = 0;
            g_pCorProfilerInfo->GetObjectSize(objectID, &ulObjSize);
            pClrObjStats->nSize += ulObjSize;
            pClrObjStats->nInstances += 1;
            if (pClrObjStats->className == L"WindowsFormsApplication1.Paddle")
            {
                auto x = "Put a breakpoint here";
            }
        }
        _csectClassMap.Unlock();
        return S_OK;
    }

    STDMETHOD(Shutdown)()
    {
        wchar_t desktopFolderName[MAX_PATH];
        SHGetFolderPath(
            NULL, //hWnd, 
            CSIDL_DESKTOP,
            NULL, //token
            0, //dwFlags
            desktopFolderName
            ); // C:\Users\calvinh\Desktop
        wstring outFileNameClassMap(desktopFolderName);
        outFileNameClassMap.append(L"\\LogOutputClassMap.csv");
        {
            // sort by name
            map<wstring, ClrClassStats> mapClassbyName;
            for (const auto &obj : _ClassDictionary)
            {
                mapClassbyName.insert(pair<wstring, ClrClassStats>(obj.second.className, obj.second));
            }
            wofstream outFile(outFileNameClassMap); //dtor will close file
            outFile << "Class Name, NumInstances, TotSize" << endl;
            for (const auto &obj : mapClassbyName)
            {
                LogOutput(L"%s,   %d,  %d", obj.first.c_str(), obj.second.nInstances, obj.second.nSize);
                outFile << obj.first.c_str()
                    << L","
                    << obj.second.nInstances
                    << L","
                    << obj.second.nSize
                    << endl;
            }
        }
        // now start file with Excel
        ShellExecute(
            0,  // hwnd
            0, // lpOperation
            outFileNameClassMap.c_str(),
            nullptr, //lpParameters
            nullptr, // lpDirectory
            0 // nShowCmd
            );
        return S_OK;
    }

    // APPLICATION DOMAIN EVENTS
    PROF_NOT_IMP(AppDomainCreationStarted, AppDomainID appDomainId);
    PROF_NOT_IMP(AppDomainCreationFinished, AppDomainID appDomainId, HRESULT hr);
    PROF_NOT_IMP(AppDomainShutdownStarted, AppDomainID);
    PROF_NOT_IMP(AppDomainShutdownFinished, AppDomainID appDomainId, HRESULT hr);

    // ASSEMBLY EVENTS
    PROF_NOT_IMP(AssemblyLoadStarted, AssemblyID);
    PROF_NOT_IMP(AssemblyLoadFinished, AssemblyID, HRESULT);
    PROF_NOT_IMP(AssemblyUnloadStarted, AssemblyID);
    PROF_NOT_IMP(AssemblyUnloadFinished, AssemblyID assemblyID, HRESULT hr);

    // MODULE EVENTS
    PROF_NOT_IMP(ModuleLoadStarted, ModuleID);
    PROF_NOT_IMP(ModuleLoadFinished, ModuleID moduleID, HRESULT hr);
    PROF_NOT_IMP(ModuleUnloadStarted, ModuleID moduleId);
    PROF_NOT_IMP(ModuleUnloadFinished, ModuleID, HRESULT);
    PROF_NOT_IMP(ModuleAttachedToAssembly, ModuleID moduleID, AssemblyID assemblyID);

    // CLASS EVENTS
    PROF_NOT_IMP(ClassLoadStarted, ClassID classId);
    PROF_NOT_IMP(ClassLoadFinished, ClassID classId, HRESULT hr);
    PROF_NOT_IMP(ClassUnloadStarted, ClassID classId);
    PROF_NOT_IMP(ClassUnloadFinished, ClassID, HRESULT);
    PROF_NOT_IMP(FunctionUnloadStarted, FunctionID);

    // JIT EVENTS
    PROF_NOT_IMP(JITCompilationStarted, FunctionID functionID, BOOL fIsSafeToBlock);
    PROF_NOT_IMP(JITCompilationFinished, FunctionID functionID, HRESULT hrStatus, BOOL fIsSafeToBlock);
    PROF_NOT_IMP(JITCachedFunctionSearchStarted, FunctionID functionId, BOOL *pbUseCachedFunction);
    PROF_NOT_IMP(JITCachedFunctionSearchFinished, FunctionID, COR_PRF_JIT_CACHE);
    PROF_NOT_IMP(JITFunctionPitched, FunctionID);
    PROF_NOT_IMP(JITInlining, FunctionID, FunctionID, BOOL*);

    // THREAD EVENTS
    PROF_NOT_IMP(ThreadCreated, ThreadID);
    PROF_NOT_IMP(ThreadDestroyed, ThreadID);
    PROF_NOT_IMP(ThreadAssignedToOSThread, ThreadID, DWORD);

    // REMOTING EVENTS
    // Client-side events
    PROF_NOT_IMP(RemotingClientInvocationStarted);
    PROF_NOT_IMP(RemotingClientSendingMessage, GUID*, BOOL);
    PROF_NOT_IMP(RemotingClientReceivingReply, GUID*, BOOL);
    PROF_NOT_IMP(RemotingClientInvocationFinished);
    // Server-side events
    PROF_NOT_IMP(RemotingServerReceivingMessage, GUID*, BOOL);
    PROF_NOT_IMP(RemotingServerInvocationStarted);
    PROF_NOT_IMP(RemotingServerInvocationReturned);
    PROF_NOT_IMP(RemotingServerSendingReply, GUID*, BOOL);

    // CONTEXT EVENTS
    PROF_NOT_IMP(UnmanagedToManagedTransition, FunctionID, COR_PRF_TRANSITION_REASON);
    PROF_NOT_IMP(ManagedToUnmanagedTransition, FunctionID, COR_PRF_TRANSITION_REASON);

    // SUSPENSION EVENTS
    PROF_NOT_IMP(RuntimeSuspendStarted, COR_PRF_SUSPEND_REASON);
    PROF_NOT_IMP(RuntimeSuspendFinished);
    PROF_NOT_IMP(RuntimeSuspendAborted);
    PROF_NOT_IMP(RuntimeResumeStarted);
    PROF_NOT_IMP(RuntimeResumeFinished);
    PROF_NOT_IMP(RuntimeThreadSuspended, ThreadID);
    PROF_NOT_IMP(RuntimeThreadResumed, ThreadID);

    // GC EVENTS
    PROF_NOT_IMP(MovedReferences, ULONG cmovedObjectIDRanges, ObjectID oldObjectIDRangeStart[], ObjectID newObjectIDRangeStart[], ULONG cObjectIDRangeLength[]);

    PROF_NOT_IMP(ObjectsAllocatedByClass, ULONG classCount, ClassID classIDs[], ULONG objects[]);
    PROF_NOT_IMP(ObjectReferences, ObjectID objectID, ClassID classID, ULONG cObjectRefs, ObjectID objectRefIDs[]);
    PROF_NOT_IMP(RootReferences, ULONG cRootRefs, ObjectID rootRefIDs[]);

    // Exception creation
    PROF_NOT_IMP(ExceptionThrown, ObjectID);

    // Exception Caught
    PROF_NOT_IMP(ExceptionCatcherEnter, FunctionID, ObjectID);
    PROF_NOT_IMP(ExceptionCatcherLeave);

    // Search phase
    PROF_NOT_IMP(ExceptionSearchFunctionEnter, FunctionID);
    PROF_NOT_IMP(ExceptionSearchFunctionLeave);
    PROF_NOT_IMP(ExceptionSearchFilterEnter, FunctionID);
    PROF_NOT_IMP(ExceptionSearchFilterLeave);
    PROF_NOT_IMP(ExceptionSearchCatcherFound, FunctionID);

    // Unwind phase
    PROF_NOT_IMP(ExceptionUnwindFunctionEnter, FunctionID);
    PROF_NOT_IMP(ExceptionUnwindFunctionLeave);
    PROF_NOT_IMP(ExceptionUnwindFinallyEnter, FunctionID);
    PROF_NOT_IMP(ExceptionUnwindFinallyLeave);

    PROF_NOT_IMP(ExceptionCLRCatcherFound); // Deprecated in .Net 2.0
    PROF_NOT_IMP(ExceptionCLRCatcherExecute); // Deprecated in .Net 2.0

    PROF_NOT_IMP(ExceptionOSHandlerEnter, FunctionID); // Not implemented
    PROF_NOT_IMP(ExceptionOSHandlerLeave, FunctionID); // Not implemented

    // IID_ICorProfilerCallback2 EVENTS
    PROF_NOT_IMP(ThreadNameChanged, ThreadID threadId, ULONG cchName, __in WCHAR name[]);
    PROF_NOT_IMP(GarbageCollectionStarted, int cGenerations, BOOL generationCollected[], COR_PRF_GC_REASON reason);
    PROF_NOT_IMP(SurvivingReferences, ULONG cSurvivingObjectIDRanges, ObjectID objectIDRangeStart[], ULONG cObjectIDRangeLength[]);
    PROF_NOT_IMP(GarbageCollectionFinished);
    PROF_NOT_IMP(FinalizeableObjectQueued, DWORD finalizerFlags, ObjectID objectID);
    PROF_NOT_IMP(RootReferences2, ULONG cRootRefs, ObjectID rootRefIds[], COR_PRF_GC_ROOT_KIND rootKinds[], COR_PRF_GC_ROOT_FLAGS rootFlags[], UINT_PTR rootIds[]);
    PROF_NOT_IMP(HandleCreated, GCHandleID handleId, ObjectID initialObjectId);
    PROF_NOT_IMP(HandleDestroyed, GCHandleID handleId);

    // COM CLASSIC VTable
    PROF_NOT_IMP(COMClassicVTableCreated, ClassID wrappedClassID, REFGUID implementedIID, void *pVTable, ULONG cSlots);

    PROF_NOT_IMP(COMClassicVTableDestroyed, ClassID wrappedClassID, REFGUID implementedIID, void *pVTable);
    // ICorProfilerCallback2 <<

    PROF_NOT_IMP(InitializeForAttach, IUnknown* punk, void* data, UINT datasize);
    PROF_NOT_IMP(ProfilerAttachComplete);
    PROF_NOT_IMP(ProfilerDetachSucceeded);
protected:
    //key = ClassId: one entry per unique ClassId
    unordered_map<ClassID, ClrClassStats> _ClassDictionary;
    CComAutoCriticalSection _csectClassMap;
};

OBJECT_ENTRY_AUTO(CLSID_MyProfiler, MyProfiler)

// define a class that represents this module
class CClrProfModule : public ATL::CAtlDllModuleT< CClrProfModule >
{
#if _DEBUG
public:
    CClrProfModule()
    {
        int x = 0; // set a bpt here
    }
    ~CClrProfModule()
    {
        int x = 0; // set a bpt here
    }
#endif _DEBUG
};

// instantiate a static instance of this class on module load
CClrProfModule _AtlModule;
// this gets called by CLR due to env var settings
STDAPI DllGetClassObject(__in REFCLSID rclsid, __in REFIID riid, __deref_out LPVOID FAR* ppv)
{
    HRESULT hr = E_FAIL;
    hr = AtlComModuleGetClassObject(&_AtlComModule, rclsid, riid, ppv);
    //  hr= CComModule::GetClassObject();
    return hr;
}
//tell the linker to export the function
#pragma comment(linker, "/EXPORT:DllGetClassObject=_DllGetClassObject@12,PRIVATE")

</Code for ClrProfiler>