Click here to Skip to main content
Click here to Skip to main content

Tagged as

Go to top

.NET CLR Injection: Modify IL Code on Run-time

, 5 Oct 2012
Rate this:
Please Sign up or sign in to vote.
Modify methods' IL codes on runtime even if they have been JIT-compiled, supports Release mode, and variants of .NET versions, from 2.0 to 4.0.
This is an old version of the currently published article.

Introduction  

Modifying .NET methods' MSIL codes during run-time is very cool, it helps to implement hooking, software protection, and other amazing stuff. That's why I want it, but there is a big challenge on the road -- the MSIL code could have been complied to native code by JIT-complier before we have a chance to modify them; also the .NET CLR implantation is not documented and it changes during each version, we need a reliable and stable way without dependency to the exact memory layout. 

Anyway, after more than one week research, finally I made it! Here is a simple method in the demo problem:

protected string CompareOneAndTwo()
{
    int a = 1;
    int b = 2;
    if (a < b)
    {
        return "Number 1 is less than 2";
    }
    else
    {
        return "Number 1 is greater than 2 (O_o)";
    }
}

Certainly it returns "Number 1 is less than 2"; let's try to make it return the incorrect result "Number 1 is greater than 2 (O_o)".

Looking at the MSIL code for this method, we can do it by changing the opcode from Bge_S to Blt_S. And then the jump works in a different logic which returns in a wrong result, that is what I need.

And if you try in the demo application, it shows a wrong answer as below.

Here is the code replacing the IL, I assume there are enough comments between the lines.

You can download the demo program and have a try.

  • Supports variants of .NET versions from 2.0 to 4.0 
  • Supports variants of methods to be modified, including dynamic methods and generic methods.
  • Supports release mode .NET process.   

Using the code

Copy the InjectionHelper.cs file into your project, it contains several methods.

public static class InjectionHelper
{
    // Load the unmanaged injection.dll, the initlaization happens in a background thread
    // you can check if the initialization is completed by GetStatus()
    public static void Initialize()
 
    // Unload the unmanaged injection.dll
    public static void Uninitialize()
 
    // Update the IL Code of a Method.
    public static void UpdateILCodes(MethodInfo method, byte[] ilCodes) 
 
    // The method returns until the initialization is completed
    public static Status WaitForIntializationCompletion()
 
    // Query the current status of the unmanaged dll, returns immediately.
    public static Status GetStatus()
}

The InjectionHelper.Initialize method loads the unmanaged injection.dll from the directory of the current assembly directory, so all the related files need to be there, or you can modify the code to change the location.

Here is the file list. 

File NameDescription
Injection.dllunmanaged DLL to do the work in this article (x86 version, x64 version will be out sooner or later)
EasyHook32.dll x86 EasyHook DLL from http://easyhook.codeplex.com/ (used by Injection.dll)
EasyHook64.dll x86 EasyHook DLL from http://easyhook.codeplex.com/ (will be used by x64 Injection.dll)
symchk.exe
SymbolCheck.dll
Tool to download PDB file, copied from Windows Debug Tools.
dbg32.dllx86 version of dbghelp.dll 6.2.
used by Injection.dll and symchk.exe
I changed the file name to avoid version confliction
Also the PE import table of symchk.exe is modified to link to this DLL
PDB_symbols/*The PDB symbol files local cache. Can be removed but will slow down the initialization.
Test_x86_DotNet20_Release.exeTest program for x86 / .NET 2.0 / Release mode, not required for distribution.
Test_x86_DotNet35_Release.exeTest program for x86 / .NET 3.5 / Release mode, not required for distribution. 
Test_x86_DotNet40_Release.exeTest program for x86 / .NET 4.0 / Release mode, not required for distribution.

Background

Replace the IL code

First, take a look at how the CLR and JIT works.

The JIT implementation DLLs (clrjit.dll for .Net4.0+ / mscorjit.dll for .NET 2.0+) expose a _stdcall method getJit, which returns the ICorJitCompiler interface. 

The CLR implementation DLLs (clr.dll for .NET 4.0+ / mscorwks.dll for .NET 2.0+) invokes the getJit method to obtain the ICorJitCompiler interface, then calls its compileMethod method to compile MSIL code to native code.

CorJitResult compileMethod(ICorJitInfo * pJitInfo, CORINFO_METHOD_INFO * pMethodInfo, 
   UINT nFlags, LPBYTE * pEntryAddress, ULONG * pSizeOfCode);

This part is quite easy, just find the location of the compileMethod method, replace the entry via EasyHook.

// define the interface method function pointer
typedef CorJitResult (__stdcall ICorJitCompiler::*PFN_compileMethod)(ICorJitInfo * pJitInfo
	, CORINFO_METHOD_INFO * pMethodInfo
	, UINT nFlags
	, LPBYTE * pEntryAddress
	, ULONG * pSizeOfCode
	);
 
// store the address of the real compileMethod
PFN_compileMethod_V4 s_pComplieMethod = ...; 
 
// hook the compileMethod with my own compileMethod
LhInstallHook( (PVOID&)s_pComplieMethod
		, &(PVOID&)CInjection::compileMethod
		, NULL
		, &s_hHookCompileMethod
		);
 
// and here is my compileMethod
CorJitResult __stdcall CInjection::compileMethod(ICorJitInfo * pJitInfo
	, CORINFO_METHOD_INFO * pCorMethodInfo
	, UINT nFlags
	, LPBYTE * pEntryAddress
	, ULONG * pSizeOfCode
	)
{
	// TO DO: modify IL codes here 

	// Call real compileMethod
	CorJitResult result = (pCorJitCompiler->*s_pComplieMethod_V4)( 
	  pJitInfo, pCorMethodInfo, nFlags, pEntryAddress, pSizeOfCode);

	return result;
}

Modify IL code for JIT-complied methods

Now we are here, the compileMethod method above won't be called by CLR for the JIT-compiled method. To solve this problem, my idea is to restore the data structures in CLR to the previous status before JIT-compliation. And in this case, complileMethod will be called again and we can replace the IL.

Thus we have to look into the implementation of CLR a bit, SSCLI (Shared Source Common Language Infrastructure) is a good reference from Microsoft although it is quite out of date and we can't use it in our code.

The above diagram is a bit out of date, but the primary structure is the same. For each "class" in .NET, there is at least one MethodTable structure in memory. And each MethodTable is related to a EEClass, which stores the runtime type information for Reflection and other use.  

For each "method", there is corresponding MethodDesc data structure in memory containing the information of this method like flags / slot address / entry address / etc. 

Before a method is JITted-complied, the slot is pointed to a JMI thunk (prestub), which triggers JIT compliation; when the IL code is complied, the slot is rewritten to point to the JMI thunk, which jumps to complied native code directly.

To restore the data structure, first clear the flags, then modify the entry address back to a temporary entry address, and so on. I successfully did that in the debugger by modifying the memory directly. But this is messy, it depends on the layout of the data structures, and the code is unreliable for different versions of .NET.

I was seeking a reliable manner, and luckily, I found the MethodDesc::Reset method in SSCLI source code (vm/method.cpp). 

void MethodDesc::Reset()
{
    CONTRACTL
    {
        THROWS;
        GC_NOTRIGGER;
    }
    CONTRACTL_END
 
    // This method is not thread-safe since we are updating
    // different pieces of data non-atomically.
    // Use this only if you can guarantee thread-safety somehow.

    _ASSERTE(IsEnCMethod() || // The process is frozen by the debugger
             IsDynamicMethod() || // These are used in a very restricted way
             GetLoaderModule()->IsReflection()); // Rental methods                                                                 

    // Reset any flags relevant to the old code
    ClearFlagsOnUpdate();
 
    if (HasPrecode())
    {
        GetPrecode()->Reset();
    }
    else
    {
        // We should go here only for the rental methods
        _ASSERTE(GetLoaderModule()->IsReflection());
 
        InterlockedUpdateFlags2(enum_flag2_HasStableEntryPoint | enum_flag2_HasPrecode, FALSE);
 
        *GetAddrOfSlotUnchecked() = GetTemporaryEntryPoint();
    }
 
    _ASSERTE(!HasNativeCode());
}

As you can see above, it is doing the same thing for me. Hence I just need invoke this method to reset the MethodDesc status to pre-JITted.  

Certainly I can't use the MethodDesc from SSCLI, and the MethodDesc is internal used by MS, whose exact implementation and layout are unknown to everyone except Microsoft. 

After endless mountains and rivers that leave doubt whether there is a path out, suddenly one encounters the shade of a willow, bright flowers, and a lovely village.

Fortunately the address of this internal method exists in the PDB symbol from Microsoft Symbol Server, and it solves my problem. The Reset() method's address in the CLR DLL can be known by parsing the PDB file! 

Now only one mandatory parameter is left -- the this pointer of MethodDesc. It is not hard to obtain this pointer. Actually MethodBase.MethodHandle.Value == CORINFO_METHOD_HANDLE == MethodDesc address == this pointer of MethodDesc .

Thus, I have my MethodDesc class below defined in unmanaged code.

typedef void (MethodDesc::*PFN_Reset)(void);
typedef BOOL (MethodDesc::*PFN_IsGenericMethodDefinition)(void);
typedef ULONG (MethodDesc::*PFN_GetNumGenericMethodArgs)(void);
typedef MethodDesc * (MethodDesc::*PFN_StripMethodInstantiation)(void);
typedef BOOL (MethodDesc::*PFN_HasClassOrMethodInstantiation)(void);
typedef BOOL (MethodDesc::*PFN_ContainsGenericVariables)(void);    
typedef DictionaryLayout * (MethodDesc::*PFN_GetDictionaryLayout)(void);
typedef Dictionary * (MethodDesc::*PFN_GetMethodDictionary)(void);
typedef MethodDesc * (MethodDesc::*PFN_GetWrappedMethodDesc)(void);
class MethodDesc
{
public:
    void Reset(void) { (this->*s_pfnReset)(); }
    BOOL IsGenericMethodDefinition(void) { return (this->*s_pfnIsGenericMethodDefinition)(); }
    ULONG GetNumGenericMethodArgs(void) { return (this->*s_pfnGetNumGenericMethodArgs)(); }
    MethodDesc * StripMethodInstantiation(void) { return (this->*s_pfnStripMethodInstantiation)(); }
    BOOL HasClassOrMethodInstantiation(void)  { return (this->*s_pfnHasClassOrMethodInstantiation)(); }
    BOOL ContainsGenericVariables(void) { return (this->*s_pfnContainsGenericVariables)(); }
    DictionaryLayout * GetDictionaryLayout(void) { return (this->*s_pfnGetDictionaryLayout)(); }
    Dictionary * GetMethodDictionary(void) { return (this->*s_pfnGetMethodDictionary)(); }
    MethodDesc * GetWrappedMethodDesc(void) { return (this->*s_pfnGetWrappedMethodDesc)(); }
private:
    static PFN_Reset s_pfnReset;
    static PFN_IsGenericMethodDefinition s_pfnIsGenericMethodDefinition;
    static PFN_GetNumGenericMethodArgs s_pfnGetNumGenericMethodArgs;
    static PFN_StripMethodInstantiation s_pfnStripMethodInstantiation;
    static PFN_HasClassOrMethodInstantiation s_pfnHasClassOrMethodInstantiation;
    static PFN_ContainsGenericVariables s_pfnContainsGenericVariables;
    static PFN_GetDictionaryLayout s_pfnGetDictionaryLayout;
    static PFN_GetMethodDictionary s_pfnGetMethodDictionary;
    static PFN_GetWrappedMethodDesc s_pfnGetWrappedMethodDesc;
};

The static variables above store the addresses of the internal methods from the MethodDesc implementation from the CLR DLL. And they are initialized when my unmanaged DLL is loaded. And the public members just call the internal method with the this pointer. 

Now it becomes quite easy to invoke Microsoft's internal methods. Like:

MethodDesc * pMethodDesc = (MethodDesc*)pMethodHandle;
pMethodDesc->Reset();

Find internal methods' addresses from the PDB Symbol file

When the unmanaged DLL is loaded, it checks the environment to see which version of CLR/JIT is there. And it tries to seek the address for all the internal methods from the PDB file. If the seek fails, it will try to launch symchk.exe from Windows Debug Tools to download the corresponding PDB symbol files from Microsoft Symbol Server. This procedure requires a long time, from several seconds to several minutes. Maybe we can optimize to cache the address of the CLR/JIT DLLs by calculating their binary hash value.

You can see more details in the source code, the SearchMethodAddresses and Intialize methods from the unmanaged DLL.

Reset the MethodDesc to pre-JITted status

Now everything is ready. The unmanaged DLL exports a method for managed codes, accepts the IL codes and MethodBase.MethodHandle.Value from the managed code.

BOOL CInjection::StartUpdateILCodes( MethodTable * pMethodTable
    , CORINFO_METHOD_HANDLE pMethodHandle
    , LPBYTE pBuffer
    , DWORD dwSize
    )
{
    if( s_nStatus != Status_Ready || !pMethodHandle )
        return FALSE;
 
    MethodDesc * pMethodDesc = (MethodDesc*)pMethodHandle;
    pMethodDesc->Reset();
 
    MethodDesc * pStripMethodDesc = pMethodDesc->StripMethodInstantiation();
    if( pStripMethodDesc )
        pStripMethodDesc->Reset();
 
    // this is a generic method
    if( pMethodDesc->HasClassOrMethodInstantiation() )
    {
        MethodDesc * pWrappedMethodDesc = pMethodDesc->GetWrappedMethodDesc();
        if( pWrappedMethodDesc )
        {
            pWrappedMethodDesc->Reset();
        }
    }
 
    std::map< CORINFO_METHOD_HANDLE, ILCodeBuffer>::iterator iter = s_mpILBuffers.find(pMethodHandle);
    if( iter != s_mpILBuffers.end() )
    {
        LocalFree(iter->second.pBuffer);
        s_mpILBuffers.erase(iter);
    }
 
    ILCodeBuffer tILCodeBuffer = { pBuffer, dwSize };
    s_mpILBuffers[pMethodHandle] = tILCodeBuffer;
 
    return TRUE;
}

The code above just calls the Reset() method, and stores the IL codes in a map, which will be used by complieMethod when the method gets complied.

And in complieMethod, just replace the ILCode, with code like below.

CorJitResult __stdcall CInjection::compileMethod(ICorJitInfo * pJitInfo
    , CORINFO_METHOD_INFO * pCorMethodInfo
    , UINT nFlags
    , LPBYTE * pEntryAddress
    , ULONG * pSizeOfCode
    )
{
    ICorJitCompiler * pCorJitCompiler = (ICorJitCompiler *)this;
    LPBYTE pOriginalILCode = pCorMethodInfo->ILCode;
    unsigned int nOriginalSize = pCorMethodInfo->ILCodeSize;
 
    // find the method to be replaced
    std::map< CORINFO_METHOD_HANDLE, ILCodeBuffer>::iterator iter = s_mpILBuffers.end();
    if( pCorMethodInfo && GetStatus() == Status_Ready )
    {
        MethodDesc * pMethodDesc = (MethodDesc*)pCorMethodInfo->ftn;
        std::map< CORINFO_METHOD_HANDLE, ILCodeBuffer>::iterator iter = 
                       s_mpILBuffers.find((CORINFO_METHOD_HANDLE)pMethodDesc);
 
        // if the current method is not found, try to search its generic definition method
        if( iter == s_mpILBuffers.end() &&
            pMethodDesc->HasClassOrMethodInstantiation() )
        {
            pMethodDesc = pMethodDesc->StripMethodInstantiation();
            iter = s_mpILBuffers.find((CORINFO_METHOD_HANDLE)pMethodDesc);
        }
 
        if( iter != s_mpILBuffers.end() )
        {
            pCorMethodInfo->ILCode = iter->second.pBuffer;
            pCorMethodInfo->ILCodeSize = iter->second.dwSize;
        }
    }
 
    CorJitResult result = (pCorJitCompiler->*s_pComplieMethod_V4)( pJitInfo, 
               pCorMethodInfo, nFlags, pEntryAddress, pSizeOfCode);
 
    if( iter != s_mpILBuffers.end() )
    {
        pCorMethodInfo->ILCode = pOriginalILCode;
        pCorMethodInfo->ILCodeSize = nOriginalSize;
        LocalFree(iter->second.pBuffer);
        s_mpILBuffers.erase(iter);
    }
 
    return result;
}

Generic method 

A generic method is mapped to a MethodDesc in memory . But calling the generic method with different type parameter may cause the CLR to create different instantiations of the definition method. (The instantiation may be shared, you can see the types of generic method instantiation below). 

  1. shared generic method instantiations  
  2. unshared generic method instantiations 
  3. instance methods in shared generic classes
  4. instance methods in unshared generic classes 
  5. static methods in shared generic classes 
  6. static methods in unshared generic classes  

The following line is a simple generic method defined from the demo program 

string GenericMethodToBeReplaced<T, K>(T t, K k)  

Calling GenericMethodToBeReplaced<string, int>("11", 2) for the first time,  CLR creates an    InstantiatedMethodDesc ( sub-class of MethodDesc and its flag marked with mcInstantiated ). 

The InstantiatedMethodDesc is stored in InstMethodHashTable data structure of the method's corresponding module. 

And calling GenericMethodToBeReplaced<long, int>(1, 2) leads to another  InstantiatedMethodDesc creation. 

Hence, we need find & reset all of the  InstantiatedMethodDesc of the generic method so that we can replace the IL code without missing. 

From SSCLI source code (vm/proftoeeinterfaceimpl.cpp), there is a class named LoadedMethodDescIterator can be used for searching. 

LoadedMethodDescIterator MDIter(ADIter.GetDomain(), pModule, methodId);
while(MDIter.Next())
{
    MethodDesc * pMD = MDIter.Current();
    if (pMD)
    {
        _ASSERTE(pMD->IsIL());
        pMD->SetRVA(rva);
    }
} 

Looking at the methods from CLR's PDB symbol file,  the constructor / deconstructor / Start / Next / Current methods' addresses can be retrieved for LoadedMethodDescIterator class. so we can take use of this class from CLR.

 

It doesn't matter that we don't know the exact size of the LoadedMethodDescIterator instance,  just define a big enough memory block to hold the actual instance data. 

class LoadedMethodDescIterator
{
private:
	BYTE dummy[10240]; 
}; 

And then we call the constructor of  LoadedMethodDescIterator to initialize the memory area.  

typedef void (LoadedMethodDescIterator::*PFN_LoadedMethodDescIteratorConstructor)(AppDomain * pAppDomain, Module *pModule,	mdMethodDef md);
class LoadedMethodDescIterator
{
public:
	LoadedMethodDescIterator(AppDomain * pAppDomain, Module *pModule, mdMethodDef md)
	{
		memset( dummy, 0, sizeof(dummy));
		(this->*s_pfnConstructor)( pAppDomain, pModule, md);
	}

	
private:
	// we don't know the exact size of LoadedMethodDescIterator, so add enough memory here
	BYTE dummy[10240]; 

	static PFN_LoadedMethodDescIteratorConstructor s_pfnConstructor;
};
PFN_LoadedMethodDescIteratorConstructor LoadedMethodDescIterator::s_pfnConstructor = NULL; 

As you can see above, PFN_LoadedMethodDescIteratorConstructor is the constructor pointer declaration, which accepts three parameters (AppDomain / Module and the token of the method to be searched).  Static variable s_pfnConstructor holding the pointer gotten from PDB file, is invoked in constructor to initialize the class instance. Now we can use this internal class. 

One thing need be resolved, the parameter of the LoadedMethodDescIterator::Next() methods between .Net2.0 and .Net4.0 are different. 

// .Net 2.0
BOOL LoadedMethodDescIterator::Next(void)
// .Net 4.0
BOOL LoadedMethodDescIterator::Next(CollectibleAssemblyHolder<class DomainAssembly *> *) 

The .Net4.0 version accepts a "strange" pointer. Take a look how it is used from clr.dll.

 

This "strange" pointer parameter is set to an address whose value is initially set to zero,  hence, we don't need to dare how to initialize this pointer. And at the end of the loop, the DoRelease method is called against this pointer of instance. It looks like this pointer is pointing to some class instance which records the current search status, and should be released at loop exit. 

Then we get code below, two types of function pointers defined, PFN_Next_v2 for .Net2.0; PFN_Next_v4 for  .Net4.0. 
And the pCollectibleAssemblyHolder is the strange pointer. you may notice that the deconstructor and DoRelease are not called for now. 

typedef BOOL (LoadedMethodDescIterator::*PFN_Next_v4)(LPVOID pParam);
typedef BOOL (LoadedMethodDescIterator::*PFN_Next_v2)(void);
class LoadedMethodDescIterator
{
public:
	LoadedMethodDescIterator(AppDomain * pAppDomain, Module *pModule, mdMethodDef md)
	{
		// strange pointer is initialized to zero
		pCollectibleAssemblyHolder = NULL;
	}

	BOOL Next() 
	{
		switch(g_tDotNetVersion)
		{
		case DotNetVersion_4:
			return (this->*s_pfnNext_v4)(&pCollectibleAssemblyHolder); 

		case DotNetVersion_2:
			return (this->*s_pfnNext_v2)(); 

		default:
			return FALSE;
		}		
	}
	MethodDesc* Current() { return (this->*s_pfnCurrent)(); }
private:
	// this is the "strange" pointer
	LPVOID pCollectibleAssemblyHolder;

	static PFN_Next_v4 s_pfnNext_v4;
	static PFN_Next_v2 s_pfnNext_v2; 

public:
	static void MatchAddress(LPCWSTR wszName, ULONG64 ulAddr)
	{
		LPVOID* pDest = NULL;
		if( wcscmp( L"LoadedMethodDescIterator::Next", wszName) == 0 )
		{
			switch(g_tDotNetVersion)
			{
			case DotNetVersion_2:
				pDest = (LPVOID*)&(LoadedMethodDescIterator::s_pfnNext_v2);
				break;

			case DotNetVersion_4:
				pDest = (LPVOID*)&(LoadedMethodDescIterator::s_pfnNext_v4);
				break;
			}
		}

		if( pDest )
			*pDest = (LPVOID)ulAddr;
	}


};

Finally,  we can reset all the instantiated MethodDesc for generic method as below. 

// find out all the instantiations of this generic method
Module * pModule = pMethodDesc->GetLoaderModule();
AppDomain * pAppDomain = pMethodDesc->GetDomain();
if( pModule )
{
	LoadedMethodDescIterator * pLoadedMethodDescIter = new LoadedMethodDescIterator( pAppDomain, pModule, md);
	while(pLoadedMethodDescIter->Next())
	{
		MethodDesc * pMD = pLoadedMethodDescIter->Current();
		if( pMD )
			pMD->Reset();
	}
	delete pLoadedMethodDescIter;
}

Points of interest    

Compilation optimization   

I found that if the method is too simple and the IL codes are only several bytes, the method may be complied as inline mode. And in this case, Reset MethodDesc does not help anything because the execution even doesn't reach there. More details can be found in CEEInfo::canInline,  (vm/jitinterface.cpp in SSCLI) 

Dynamic method 

To update the IL code of a dynamic method we need to be very careful. Filling incorrect IL code for other kinds of methods only causes an InvalidApplicationException; but incorrect IL code in a dynamic method can crash the CLR and the whole process! And IL code for a dynamic method is different from that for others. Better to generate the IL code from another dynamic method and then copy and update.

History  

  • 2012 Sep 22 - First Version 
  • 2012 Oct 5 - Added the LoadedMethodDescIterator for generic method  

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Jerry.Wang
Team Leader
China China
Jerry is from China. He was captivated by computer programming since 13 years old when first time played with Q-Basic.
 

  • Windows / Linux & C++
  • iOS & Obj-C
  • .Net & C#
  • Flex/Flash & ActionScript
  • HTML / CSS / Javascript
  • Gaming Server programming / video, audio processing / image & graphics
 
Contact: vcer(at)qq.com
Chinese Blog: http://blog.csdn.net/wangjia184

Comments and Discussions


Discussions posted for the Published version of this article. Posting a message here will take you to the publicly available article in order to continue your conversation in public.
 
QuestionCan't run in Win2003 Server SP2 Pinmemberiolir14-Aug-14 3:26 
AnswerRe: Can't run in Win2003 Server SP2 Pinmemberiolir15-Aug-14 6:05 
GeneralRe: Can't run in Win2003 Server SP2 PinmemberJerry.Wang16-Aug-14 2:10 
QuestionCan inject in x64 but not in x86 Pinmemberiolir13-Aug-14 23:27 
AnswerRe: Can inject in x64 but not in x86 PinmemberJerry.Wang16-Aug-14 2:12 
QuestionMy Vote of 5++ Pinprofessional2006 Flauzer7-Aug-14 8:15 
QuestionCan't replace static method from mscorlib (.net 4) [modified] PinmemberSpadar.Mikalaj17-Mar-14 21:33 
AnswerRe: Can't replace static method from mscorlib (.net 4) PinmemberJerry.Wang30-Jul-14 16:35 
QuestionCan't replace the method in other dll method Pinmemberwangchengh24-Feb-14 20:31 
AnswerRe: Can't replace the method in other dll method PinmemberJerry.Wang26-Feb-14 22:46 
QuestionCan not extend method with additional call PinmemberDvDmanDT3-Jan-14 14:00 
GeneralThanks PinprofessionalAmir Mohammad Nasrollahi25-Jul-13 19:26 
GeneralMy vote of 5 PinmemberOpata Chibueze13-Jun-13 11:16 
GeneralRe: My vote of 5 PinmemberJerry.Wang15-Jun-13 16:51 
GeneralMy vote of 5 PinmemberWong Shao Voon12-May-13 20:55 
GeneralRe: My vote of 5 PinmemberJerry.Wang14-May-13 17:10 
GeneralGood Job Pinmemberwyshdiy8-May-13 15:58 
GeneralMy vote of 5 PinmemberAzziet26-Feb-13 20:09 
GeneralGreat [modified] Pinmemberkrysiaaa15-Feb-13 5:40 
GeneralMy vote of 5 Pinmemberdmihailescu13-Feb-13 4:55 
GeneralMy vote of 5 Pinmemberfaruk19683019-Jan-13 7:26 
QuestionVote 5+++ PinmemberMember 884486015-Nov-12 0:16 
AnswerRe: Vote 5+++ PinmemberJerry.Wang15-Nov-12 16:08 
GeneralMy vote of 5 PinmemberMazen el Senih30-Oct-12 11:07 
GeneralMy vote of 5 PinmemberSentenryu29-Oct-12 23:38 
GeneralRe: My vote of 5 PinmemberJerry.Wang30-Oct-12 0:34 
Question看到中国大神了,支持 Pinmemberwaylife29-Oct-12 17:39 
QuestionMy vote of 1 PinmemberBCantor18-Oct-12 12:27 
AnswerRe: My vote of 1 PinmemberJerry.Wang18-Oct-12 17:10 
GeneralMy vote of 5 PinmemberMihai MOGA15-Oct-12 21:58 
GeneralRe: My vote of 5 PinmemberJerry.Wang15-Oct-12 22:02 
QuestionVery nice PinmemberCIDev15-Oct-12 10:33 
GeneralMy vote of 5 Pinmembergndnet13-Oct-12 2:58 
QuestionWhat about applicability limits? PinmemberSerge Baltic11-Oct-12 3:01 
AnswerRe: What about applicability limits? PinmemberJerry.Wang11-Oct-12 5:10 
GeneralMy vote of 5 PinmemberBorja Prado11-Oct-12 0:43 
QuestionIs there a way to prevent a hacker from injecting code into my application? PinmemberMember 950120510-Oct-12 2:27 
AnswerRe: Is there a way to prevent a hacker from injecting code into my application? PinmemberJerry.Wang10-Oct-12 2:32 
GeneralRe: Is there a way to prevent a hacker from injecting code into my application? PinmemberSentenryu29-Oct-12 23:31 
GeneralRe: Is there a way to prevent a hacker from injecting code into my application? PinmemberJerry.Wang30-Oct-12 0:33 
GeneralRe: Is there a way to prevent a hacker from injecting code into my application? PinmemberJerry.Wang30-Oct-12 0:36 
AnswerRe: Is there a way to prevent a hacker from injecting code into my application? PinmemberDinis Cruz10-Oct-12 12:03 
GeneralMy vote of 5 PinmemberS.P. Tiwari7-Oct-12 23:02 
GeneralNice catch of using the Reset Call PinmemberDinis Cruz7-Oct-12 22:44 
GeneralRe: Nice catch of using the Reset Call PinmemberJerry.Wang7-Oct-12 22:51 
GeneralRe: Nice catch of using the Reset Call PinmemberDinis Cruz7-Oct-12 23:06 
GeneralMy vote of 5 PinmemberVivek Krishnamurthy7-Oct-12 20:44 
GeneralMy vote of 5 Pinmemberdebug19847-Oct-12 17:13 
GeneralMy vote of 5 PinmemberPrakash19876-Oct-12 7:59 
GeneralMy vote of 5 PinmemberAlois Kraus5-Oct-12 20:23 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web01 | 2.8.140916.1 | Last Updated 6 Oct 2012
Article Copyright 2012 by Jerry.Wang
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid