Translation Matt Pietrek's Under The Hood Column Article The .Net Profiling API and The Dnprofiler Tool

xiaoxiao2021-03-06  46

The .Net Profiling API AND The DNPROFILER TOOLER TOOL

Http://msdn.microsoft.com/msdnmag/issues/01/12/HOOD/default.aspx

Microsoft's .NET Common Language Runtime (CLR) provides a number of mechanisms to create more easily, more object-oriented platforms. Including garbage collection, standard cross-language abnormal processing, extensive class libraries, metadata, and interoperability of Native code, remote processing. It also includes an instant compiler that across the CPU instruction format (intermediate language, IL) and the IL to be compiled into a code that can run on the target CPU.

As the development of the system becomes more complex, it can understand the internal working mechanism of the system becomes more and more valuable. In Windows®, investigating executable program loaders, the operating mechanism of the memory manager will show a lot of different techniques. In addition, some techniques can work normally under the Window 9x platform, but in Windows NT® and Windows 2000 cannot work, vice versa. Under Win32®, the best way to see the internal operation of the process is to use debug API, but it can only contain few parts.

It is completely different under .NET. Because the CLR runtime is the center of any .NET program, it provides a logical location to insert the hook to observe the internal .NET. It is expected that the tool developer, the system-level programmer needs, Microsoft uses .NET to provide a very detailed, consistent way to see the internal operation of the process. This month, I want to describe this mechanism and provide a program (DNPROFILE) to record the operation of the .NET runtime.

The .Net Profiling API

An analysis of the API when observing the .NET runtime action. An Analysis of the API is not good because it is useful for many things, not just analyzing. Consider the list of .NET action lists observed by the API, as follows:

Figure 1 functions of the profile supply API

CLR Startup and Shutdown

Application Domain Creation / Shutdown

AskSEMBLY LOADS / UNLOADS

Module Loads / Unloads

Class Loads / Unloads

COM Interop vTable Creation / Destruction

JIT-Compiles, Code Pitching, and Pre-Jited Method Searches

Thread Creation / Destruction / Suspends

Remoting Activity

Exception Handling

Managed / Unmanaged Code Transitions

Garbage Collection and Managed Heap Objects

Method Entry / Exit

Just as Keanu Reeves said WHOOA! With regard to how to install hooks for many years of hard exploration, Microsoft makes it easy to observe the CLR behavior. Each action in the list can be seen as a completely different Callback. In theory, it is possible to write only the code required in the Callback function.

Now, the best profiling API document is PROFILING.DOC under the .NET SDK, in the Tool Developers Guide / DOCS directory. For Beta2, this document and achievement are slightly unsynchronized. In this journal, I will not discuss every aspect of the API, but a focus on the analysis of the big aspects of the API.

A large selling point in .NET is that it no longer uses COM. This is not true, in fact, the analysis of the API is based on COM. Using an Analysis API includes creating an internal server that implements a single interface. Each interface method represents an event in Figure 1. Inside the Callback method, the code can use additional COM interfaces (provided by CLR) to obtain information about the event. Personally, I like the simpler API to register every event interested. Implement the API as a COM object to force you to write almost the same code to use a COM object. Since the analysis of the API is COM, the user profile API is likely to use C .

Despite the use of typical COM instead of using a hosted .NET code, it will be appreciated after it looks carefully. If the interface is implemented with a managed code, it will have a negative impact on all events of the monitored. For example, what should I do if the host is triggered? The abnormal events will not happen without activation in the API.

Once you have a COM server that implements the Profiling interface, the next step is to force the CLR to load the COM server, call it. Tips are setting environment variables. Compared with the registry, XML files, environment variables are quite ancient.

Why don't you use the registry (configuration file) telling the CLR to call the Profiling API? One reason I heard from Microsoft developers is that the required .NET program checks the registry when starting. The registry will have performance impact. Considering that there is a lot of things to do when .NET starts, this reason is also very cautious. Nonetheless, use environment variables can work for all programs (if in system environment variables) or separate processes. In the latter case, the debugger, parser, etc. can specify the environment variable when the program is started. I will discuss the required environment variables later.

The .Net Profiling API Interfaces

The main interface I checked is IcorprofilerCallback, defined in the corprof.idl file in the .NET Framework SDK's Include directory. The implementation of the interface requires an analysis of the API. Your job is to provide implementation. Despite many ways, don't be afraid, most of them say that you can return to S_OK or E_NOIMPL. For a clear event, write the appropriate code in a suitable Callback method.

Check the callback method and you will find that most of the parameters of additional information about that event is received. For example, the JitCompilationStarted method receives a parameter named FunctionID. What can you do with it? The answer lies in the interface icorprofilerinfo.

ICORPROFILERINFO can provide any profiling information. Consider the functionID mentioned just mentioned, you can call ICORPROFILERINFO :: GetTokenAndMetadataFromFunction, it returns to your metadata interface, the metadata TOKEN of the function. Using metadata interfaces and token, you can query the function name, its class, and any information you know.

Simply put, the profiling API contains two COM interfaces. Introducing the interface, is implemented by you, is IcorprofilerCallback. When the CLR event occurs, the CLR calls a method in the interface. The lead interface is IcorprofilerInfo, which is provided by the CLR to let you use it within Callback.

The universal pattern using the ICORPROFILERCALLBACK parameter is a wide range of IDs to represent functions, classes, modules, assemblies, and so on. ID is an opaque handle. About its meaningful information is obtained from the icorprofilerinfo interface. During the execution of the program, if the code is uninstalled, then loaded, and compiled, the specific ID value (such as functionID) may change. But there are callback lets you know this happens. The icorprofilerCallback method can be divided into several groups of logic. In most cases, the events have a Started, Finished method, and paired calls. Let us study it in depth, better understand what we can observe through CLR. All methods listed below are IcorprofilerCallback interfaces unless there are special instructions.

Initialize / Shutdown Methods

In the process of using an Analysis API, INITIALIZE is the first way to be called. Your code gets the ICORPROFILERINFO pointer from this method. The only parameter is lpunknown, which calls the QueryInterface method to get the icorprofilerInfo pointer. INITIALIZE is where you tell an analysis of the API you are interested in.

In order to point out the event you are interested, call the icorprofilerinfo :: SeEventmask method to pass a parameter for DWORD types that set the right bit. These markers come from the COR_PROF_MONITOR enumeration variable in the corprof.h file. The low-bit value flag is named COR_PRF_MONITOR_XXX tells the CLR to call the ICORPROFILERCALLBACK. For example, if you want the ClassLoadStarted method to be called, you must set the cor_prf_monitor_class_loads flag.

Another flag of the parameter of the ICORPROFILERINFO :: SeteventMask method In one way, or another way to change the CLR behavior. For example, if you want to monitor the object's assignment, you must set the cor_prf_enable_object_allocated flag. Similar, cor_prf_disable_inlining tells the CLR not to inline any function. If a method is inline, you will not get Enter, Leave notification.

You can call ICORPROFILERINFO :: Seteventmask at a moment of future, modify the event you are interested in. However, some events cannot be changed, meaning that once you set it in Initialize, they will not be modified.

The shutdown method is called when the CLR terminates the process. In some cases, it will not be called, but it should be called for a normally terminated .NET program.

Application Domain Creation / Shutdown

This type of method has AppdomainCreationStarted, AppDomainCreationFinished, AppDomainShutdownStarted, and AppdomainShutdownFinished. Their names are described. The main token of these methods is appdomainid. It should be noted that APPDOMAINID cannot be used in the AppDomainCreationStarted callback method, because Appdomain does not exist. However, once the AppDomainCreationFinished notification, you can call IcorprofilerInfo :: GetAppDomainInfo to get the information of new Appdomain as a parameter to call ICORPROFIERINFO :: GetAppDomainInfo as a parameter.

AskSEMBLY LOADS / UNLOADS

These methods are called when loading, uninstall assembly, assemblyloadstarted, assemblyloadstarted, assemblyunloadstarted, assemblyunloadfinished, assemblyunloadFinished The main TOKEN is Assemblyid. It should be noted that the Token AssemblyId cannot be used in the AssemblyLoadStarted method, because the assembly does not exist. However, once the AssemblyLoadFinished notification is received, IcorprofilerInfo :: GetassemblyInfo is called to get information on the new assembly as the AssemblyId as the parameter. Module Loads / Unloads

Loading modules related functions include ModuleLoadStarted, ModuleLoadStarted, ModuleunloadStarted, ModuleunloadFinished, and ModuleAttachedtoassembly. The name of the first four functions is described. The main token is ModuleID. Like the token when asserblyloadstarted, TOKEN that passes to the ModuleLoadStarted method is also unavailable. Because the module does not exist. However, when you receive the ModuleLoadFinished notification, you can call ICORPROFILERINFO :: getModuleInfo as the parameter to get information about the new module.

When the CLR associates a module and a set of assessments, the last function, ModuleAtTachedToAssembly, is called. Although modules and assemblies are often the same file, one assembly may have multiple modules.

Class Loads / Unloads

Load-class related functions include ClassLoadStarted, ClassLoadFinished, ClassunloadStarted, and ClassunloadFinished. The names of these functions are described. TOKEN that passes to the ClassLoadStarted method is also unavailable. Because the class does not exist. But after receiving the ClassLoadFinished notification, you can call ICORPROFILERINFO :: GetClassIDInfo as a parameter to get information about the new class.

Jit compilation

The primary token used by JIT compilation method (Figure 2) is FunctionID.

JitcompiLationStarted

JitcompilationFinished

JitcachedFunctionSearchstarted

JitcachedFunctionSearchFinished

JITFunctionPitched

JITINLINING

Here, the term function, method interaction is used. Call ICORPROFILERINFO :: GetFunctionInfo with FunctionID to get information about functions.

Method JitCompiLationStarted is very interesting because it allows you to view, modify IL before JiteD. View icrprofilerinfo :: getilFunctionBody to get details, don't think it's easy. Method JITCACHEDFunctionSTARTDARTED indicates that the CLR looks for functions that have been compiled into a native code by JITED. By setting the output parameter PBuseCachedFunction is false, you can force the pre-jitd status when you force the runtime, use the latest JITED version.

Method JITFunctionPitched means to delete a previous JITED method from memory. Only if there is very little memory, it will happen. The final method JITINLINING means JITER to be in the inner function. If you want to calculate the number of ENTER / Leave notifications of that method, you can set the output parameter pfSholdinline to False to disable inline. The inline within the process range can also be prohibited by passing a flag of ICORPROFILERINFO :: Seteventmask. Threading

Thread method is shown in Figure 3

Threadcreated

ThreadDestroyed

Threadassignedtoosthread

RuntimeSuspendStarted

RuntimeSuspendfinished

RuntimeSuspendaborted

RuntimeResumeStarted

RuntimeResuMefinished

Runtimethreadsuspendeded

RuntimetReadResumed

The threadcreated / destroyed method contains the life cycle of the thread. For example, conceptually, a CLR thread can run on multiple Win32 threads in its life cycle. The ThreadassignedToosthread method indicates which Win32 thread is running in the CLR.

When the CLR is executed, some or all threads must sometimes be suspended to perform garbage collection. RuntimeSuspend, the RuntimeResume series method indicates that the CLR thread is hanged (actually more complicated than this, I am in depth here). A parameter passed to RuntimeSuspendStarted indicates why threads hang. The last two runtimethread methods indicate that a thread is hanging, and that is always in the event runtimesuspend.

COM Interop

When the CLR and the normal COM object interoperate, the proxy interface is required. Two ways indicate that the agent is created and destroyed. Parameters passing to these two functions are .NET ClassID, corresponding COM interface IID, a virtual function table, virtual function table, a virtual function table.

Managed / Unmanaged Code Transitions

When the hosted code calls the unmanaged code, or when the non-hosting code calls the managed code, the function unmanagedtomanagedtransition, ManagedTounManagedTransition is called. Pass to each method FunctionID to represent the caller, you can use it to call icrprofilerinfo :: getFunctionInfo to get more information.

Garbage Collection and Managed Heap Objects

Although there is no practical garbage collection method, Objectallocated, Movecerence, ObjectSallocatedByClass, ObjectReferences, RootReferences, these events, these events indicate that the system is undergoing garbage collection. The information carried by these notifications is very complicated, I will not explain all of his, just point some key points.

When an object is assigned from the hosted heap, the ObjectAllocated method is called (you need to set the cor_prf_enable_object_allocated flag). Objects are labeled by an ObjectID and also provides ClassID. You can use ClassID to call to IcorprofilerInfo :: getClassIDInfo get more information.

The MoveDreferences method indicates that an object (marked by ObjectID) has been moved to memory. ObjectsallocatedByClass method indicates which class of instances have been created after the last garbage collection. The ObjectReferences method provides a list of objects referenced by a particular object. Finally, the rootReferences method indicates the list of objects referenced by all root objects. Remoting Activity

Remote method is shown in Figure 4

RemotingClientInvocationStarted

RemotingClientSendingMessage

RemotingClientReceivingReply

RemotingClientInvocationFinished

RemotingServerReceivingMessage

RemotingServerInvocationStarted

RemotingServerInvocationReturnedReturnedReturned

RemotingServersendingReply

It pointed out various execution points in the process of calling during the remote method. When the program is used as the client, the method is called; when the program is used as a remote server, the method is called. In both cases, the call of the remote method, the actual transmission message is distinguished.

Exception Handling

Analyzing the API is quite complicated to an abnormal treatment and is very complete. For details, please refer to the documentation. In essence, in every stage of exception handling, the API is before, then inform you. Figure 5 shows an abnormal method.

EXCEPTIONTHROWN

ExceptionSearchFunctionEnter

ExceptionSearchFunctionLeave

ExceptionSearchfilteener

ExceptionSearchfilterLeave

ExceptionSearchcatcherfound

Exceptionoshandleres

Exceptionoshandlerleave

ExceptionunWindFunctionEnter

ExceptionUnwindFunctionLeave

ExceptionUnwindFinalLinalLenter

ExceptionunWindFinalLyleave

Exceptioncatcheenter

ExceptionCatcherleave

ExceptionClRcatcherfound

ExceptionClRcatchereExecute

The ExceptionThrown method is the first instruction you receive. Call m_picorprofilerinfo :: getClassFromObject with ObjectId to get an exception type. For each hosted method in the stack, when the TRY block in the CLR lookup method, the Filter in the method is executed, or when you find a method of processing an exception, the CLR informs you. When the stack is expanded, the CLR will also notify you when performing a FINALLY block.

Receiving Method Entry and Exit Notifications

One of the coolest features of the API is that when an arbitrary hosted method begins to execute, you can notify you when you return to the caller. Just JITED is required before the .NET method is executed. If you are interested in methods, Leaves, you can inform JITER, JITER, call the specific method you provide at the beginning of the method, end, and call the specific method you provide. At the same time, there is a TAILCAL notification and conceptually speaking similar to the optimization function. However, I never see the event of the event under .NET.

Unlike all other events I described, ENTER / Leave callback method part belongs to the icorprofilerCallback interface. But use interface icorprofilerCallback, IcorprofilerInfo for settings so that JITED calls you. Interestingly, the callback function has a limit and cannot modify the value of the register. So the code you provide is likely to include a compilation language, use the __declspec naked function. Please refer to an example of Figure 6. Void __declspec (naked) enternate ()

{

__ASM

{

Push EAX

Push ECX

Push Edx

Push [ESP 16]

Call RealenterFunctionIncppp

POP EDX

POP ECX

POP EAX

Ret 4

}

}

There is only one parameter (functionID) being passed to the Enter / Leave callback function. Document warning, if the callback function is blocked or has not returned for a long time, it will lead to terrible consequences. However, as far as my experience, use icorprofilerinfo to query the name of FunctionID and write into the file and have problems.

Investing some efforts, you can use Enter / Leave to write a similar listener program to display the execution of each method. If you write such a program, you will find that there are many ways to start a small .NET program. Because there is ENTER, the Leave callback function, you can see a lot of nested methods calls.

It should be noted that the PRE-JITED function may be used when the .NET is run. This method does not generate callback unless you take special steps. This problem can sometimes correct by monitoring JITCACHEDFunctionSearchStarted and returning false.

Caveats with the profiling api

If you want to use an Analyzing API to implement some features, you need to know some important limitations. First, it assumes that the COM server is free-threaded. When calling the icrprofilerCallback method, the .NET is running without recurring any synchronization. If you use global data, you need to use critical area protection data if necessary. Or use the thread partial storage, please refer to the DNProfiler example.

Another limit is in the ICORPROFILERCALLBACK method, you cannot call any managed code, whether direct call or indirect call. Analysis of the API is designed to be re-enter, adjustable managed code, will be troublesome if it is violated.

An Analysis API provides a method of obtaining IcordeBug. This allows you to get a specific call stack detail. But when you debug within your process, not all the icordebug methods are available. Check the icordebug documentation to make sure that can be called when debugging within the process, which is not called.

Finally, analyzing the API is so cool, I want to use it to write a lot of tools at the same time. Unfortunately, at a moment, there can only be an analysis of the COM server. If you pursue time information, you may only want to have a parser at some point. However, taking into account the non-designer tool, if Microsoft provides a solution, allowing a tool to process these events, and you can continue the event to the next tool.

The Dnprofiler Sample

In order to display the cool nature of the API, I wrote a simple implementation to record each Callback method. For some ways, the code performs some additional work makes it more meaningful. For example, when ClassLoadFinished is triggered, Dnprofiler receives a classID and writes the name of the class. DNPROFILER does not display all the information of each Callback, but it makes a considerable job show what happened. In this journal, I didn't implement the Enter / Leave Callback, but the Microsoft example implemented these.

Dnprofiler writes the result in the text file of DNPROFILER.OUT in the same directory with the parent process. Part of the startup event observed by DNProfiler is as follows:

Figure 8 Sample OUTPUT

Initialize

Threadcreated

Threadassignedtoosthread

Threadcreated

Threadassignedtoosthread

AskSEMBLYLOADSTARTED

ModuleLoadStarted

ModuleLoadFinished: C: /Winnt/Microsoft.Net/framework/v1.0.2914/mscorlib.dll

ModuleAtTachedtoassembly: mscorlib

AssemblyLoadFinished: Mscorlib Status: 00000000

ClassLoadStarted: System.Object

ClassLoadFinished

ClassLoadStarted: System.ValeTed

ClassLoadFinished

ClassLoadStarted: system.icloneable

ClassLoadFinished

... // Lines Omitted

Objectallocated: System.outofMemoryException

Objectallocated: System.StackoverflowException

Note that DNProfiler is nesting by indentation. In this example, between AssemblyLoadStarted and AssemblyLoadFinished, DNProfiler received another three events and has been properly indent.

DNProfiler is just a DLL form of COM components, no EXE execution. You or compile it yourself or register it using Regsvr32. Once the registration is successful, use a console window to run the Profiling_on.bat file when the simpler usage is used.

Figure 9 Profiling_on.bat

SET COR_ENABLE_PROFILING = 0x1

SET COR_PROFILER = {9AB84088-18E7-42F0-8F8D-E022AE3C4517}

@Rem COR_PRF_MONITOR_FUNCTION_UNLOADS = 0x1,

@Rem COR_PRF_MONITOR_CLASS_LOADS = 0x2,

@Rem COR_PRF_MONITOR_MODULE_LOADS = 0x4,

@Rem COR_PRF_MONITOR_ASSEMBLY_LOADS = 0x8,

@Rem COR_PRF_MONITOR_APPDOMAIN_LOADS = 0x10,

@Rem COR_PRF_MONITOR_JIT_COMPILATION = 0x20,

@Rem COR_PRF_MONITOR_EXCEPTIONS = 0x40, @ remote cor_prf_monitor_gc = 0x80,

@Rem COR_PRF_MONITOR_OBJECT_ALLOCATED = 0x100,

@Rem COR_PRF_MONITOR_THREADS = 0x200,

@Rem COR_PRF_MONITOR_REMOTING = 0x400,

@Rem COR_PRF_MONITOR_CODE_TRANSITIONS = 0x800,

@Rem COR_PRF_MONITOR_ENTERLEAVE = 0x1000,

@Rem COR_PRF_MONITOR_CCW = 0x2000,

@Rem COR_PRF_MONITOR_REMOTING_COOKIE = 0x4000 |

@Rem COR_PRF_MONITOR_REMOTING,

@Rem COR_PRF_MONITOR_REMOTING_ASYNC = 0x8000 |

@Rem COR_PRF_MONITOR_REMOTING,

@Rem COR_PRF_MONITOR_SUSPENDS = 0x10000,

@Rem COR_PRF_MONITOR_CACHE_SEARCHES = 0x20000,

@Rem COR_PRF_MONITOR_CLR_EXCES = 0x1000000,

@Rem COR_PRF_MONITOR_ALL = 0x107FFFF,

@Rem COR_PRF_ENABLE_REJIT = 0x40000,

@Rem COR_PRF_ENABLE_INPROC_DEBUGGING = 0x80000,

@Rem COR_PRF_ENABLE_JIT_MAPS = 0x100000,

@Rem COR_PRF_DISABLE_INLINING = 0x200000,

@Rem COR_PRF_DISABLE_OPTIMIZATIONS = 0x400000,

@Rem COR_PRF_ENABLE_OBJECT_ALLOCATED = 0x800000,

@Rem COR_PRF_ALL = 0x1fffffff,

SET DN_PROFILER_MASK = 0x1a7fffff

Once run, all the .NET programs that start from that console window will use DNProfiler. In order to set back the normal mode, run the corresponding Profilng_off.bat file.

PROFILING_ON.BAT sets three environment variables, two of which are used by CLR. When the process starts, when the cor_enable_profiling environment variable is set to non-zero, the CLR attempts to load the COM server indicated by the COR_PROFILER environment variable. The COR_PROFILER environment variable can be a CLSID or ProgID.

The third environment variable is DN_PROFILER_MASK, used by DNProfiler. It allows you to dynamically configure what you are interested in, not hardcoded to your code. In the DNProfiler's initialize method, it gets the value of DN_Profiler_Mask and passes to IcorprofilerInfo :: SeteventMask. In the .bat file, I have set Mask to contain any events, but I encourage you to test other values. Figure 7 is the main code segment of DNProfiler. It is a place where the ccorprofiler :: Initialize method is performed. The Initialize method calls a private function, GetInitializationParameters to read environment variables to generate a full path to the output file.

Most of the function in the ProfilerCallback.cpp file is simple to implement the ICORPROFILERCALLBACK interface method. If a function is a function, the code will call the ChangeneStingLevel Help function. The function updates the variable of the tracking nested layer of each thread. When the corresponding function is complete, the code calls ChangeneStingLevel again to restore the original value.

The end of the file is some private functions to do a common function, such as querying the name from the ClassID. The ProfilerPrintf function is the same as FPrintf, just it considers the number of ready-made nested layers, and the space is filled with indentation before actual text.

ProfilerCallback.h is a header file that describes the CPROFILERCALLBACK class. Note How CPROFILERCALLBACK is inherited from the icrprofilerback interface. Some private functions, member variables when the category definitions are defined.

The final source file is comstuff.cpp. It creates DNPROFILER, I want to make things as simple as possible, do not use ATL, MFC, or not anything necessary. I am a Framework enthusiast. In any case, Microsoft's example has taken a similar method. I peefully use the relevant part of the code and make it simpler, the result is comstuff.cpp.

Interop Fun with DNProfiler

It is easy to spend some time learning DNProfiler's output, understand the order of events of the .NET program. I don't learn all the incidents of interest. There is a need to mention, the .NET Windows Forms package is based on the user32.dll, you may be suspected that when sending a message, you need to switch back and forth in the managed code, the non-hosting code.

It is indeed the case. In the hosted code, such a number of recursive codes is surprising. I mean means that the hosted code calls the non-hosting code, and the non-hosting code will then call the managed code. In the hosted code, it is necessary to call the unmanaged code, so. Of course, finally, the stack is expanded to the original level.

View Figure 10

ManagedtounManagedTransition:

System.windows.Forms.unsafenativeMethods :: DispatchMessagew

UnmanagedtomanagedTransition: wndproc :: invoke

ManagedtounManagedTransition:

System.windows.Forms.unsafenativeMethods :: CallWindowProc

UnmanagedtomanagedTransition: wndproc :: invoke

ManagedtounManagedTransition:

System.windows.Forms.unsafenativeMethods :: INTDESTROYWINDOW

UnmanagedtomanagedTransition: WndProc :: InvokeManagedtounManagedTransition:

System.windows.Forms.unsafenativeMethods :: SendMessage

UnmanagedtomanagedTransition: wndproc :: invoke

ManagedtounManagedTransition:

System.windows.Forms.unsafenativeMethods :: CallWindowProc

It shows the processing of destroying window messages. In the hosted code, unsafenativeMethods :: DispatchMessagew is called, it passes to the wndproc :: invoke method of the unmanaged code, WndProc :: Invoke needs to call managed code UnsafenativeMethods :: CallWindowProc. This function then calls the unmanaged method WndProc :: Invoke. This method is called in the hosted code, and the non-hosting code is called in the UNSAFENATIVEMETHODS :: DISPATCHMESSAGEW function, and it has reached a deep call layer before returning.

The profiling interface provided by Microsoft provides tool developers in a long period of time. Very extensive, quite flexible .NET tools will be based on it developed. Here I gave it the overall pre-proportion of its ability, be sure to check the document, get more information through the Profiler example provided by Microsoft.

转载请注明原文地址:https://www.9cbs.com/read-77088.html

New Post(0)