Get LastError Caching

Since these thunks are created from interoperability metadata, it makes sense to compare the P/Invoke functions that the compiler generates for fNativeFromDLL and fNativeLocal:

.method public static pinvokeimpl(lasterr stdcall)

void modopt([mscorlib]System.Runtime.CompilerServices.CallConvStdcall) fNativeFromDLL() native unmanaged preservesig { /* further metadata elided for clarity */}

.method public static pinvokeimpl(stdcall)

void modopt([mscorlib]System.Runtime.CompilerServices.CallConvStdcall) fNativeLocal() native unmanaged preservesig { /* further metadata elided for clarity */}

The metadata for fNativeFromDLL contains the pinvokeimpl specifier pinvokeimpl (lasterr stdcall), whereas the pinvokeimpl specifier for fNative contains only the keyword stdcall. The keyword lasterr instructs the JIT compiler to generate a managed-to-unman-aged thunk that performs the so-called GetLastError-caching.

To understand the motivation for GetLastError-caching, it is necessary to take a look at the error handling strategy of most Win32 API functions. Unless a function returns an HRESULT value, functions from the Win32 API express that they have not executed successfully by returning either the BOOL value FALSE or a HANDLE of an illegal value (typically either NULL or the macro INVALID_HANDLE_VALUE, which has the value -1).

To get further information about the error, an error code can be retrieved by calling GetLastError. If this function had been called via a normal managed-to-unmanaged thunk, you could easily get an error value that is not set by the function you expected, but by a totally different function. Executing managed code often causes further calls to Win32 API functions internally. For example, assume that the IL instruction call is executed to invoke a method. It is easily possible that this will cause further calls to LoadLibraryEx, because the method that should be invoked has to be JIT-compiled first, and quite often, the JIT compiler has to load an additional assembly. Also, IL instructions like newobj, newarr, and box can obviously cause the managed heap to allocate further memory via calls to VirtualAlloc and related APIs. These internal method calls can obviously overwrite the GetLastError value. To face this problem, the CLR allows managed-to-unmanaged thunks to perform GetLastError-caching.

Thunks that perform GetLastError-caching read the GetLastError value after calling the target function and store it in CLR-specific thread local storage (TLS). When managed code calls GetLastError to retrieve the error code, the cached error code is used instead of the real error code returned by GetLastError!

To achieve this, the JIT compiler treats P/Invoke metadata for the GetLastError function of kernel32.dll specially. The thunk that is generated for P/Invoke metadata for GetLastError calls FalseGetLastError from mscowks.dll instead of kernel32.dll's GetLastError function. This function returns the cached error value from the CLR-specific TLS.

There are two reasons why thunks that update the cached GetLastError value are more expensive than thunks without GetLastError-caching. Obviously, determining the last error value and storing it in TLS takes time. Furthermore, thunks with GetLastError-caching are never inlined, whereas thunks that do not perform GetLastError-caching are usually inlined. (The CLR 2.0 does not inline P/Invoke thunks for functions that return either float or double, even if they do not support GetLastError-caching. However, that special case shall be ignored here.)

By default, P/Invoke metadata that is automatically created for functions imported from DLLs have the lasterror flag to ensure that GetLastError works for all Win32 API calls. For native local functions, C++/CLI interoperability automatically generates P/Invoke metadata without the lasterror flag because SetLastError and GetLastError are usually not the preferred error reporting mechanisms for non-Win32 APIs.

Since the thunk for fNativeFromDLL performs GetLastError-caching, it is not inlined, in contrast to the thunk for fNativeLocal. This explains the difference in the execution performance of the two thunks. However, you can use the linker command-line option /CLRSUPPORTLASTERROR:NO to instruct the linker to generate P/Invoke metadata without the lasterror flag. I strictly advise against using this option because of the large number of Win32 functions that report error codes via the GetLastError value.

If a native function that never touches the GetLastError value is called very often from managed code, and you want to optimize the performance of its thunk, you can define custom P/Invoke metadata instead. The following code shows an example:

[System::Security::SuppressUnmanagedCodeSecurity] [System::Runtime::InteropServices::DllImport( "TestLib.dll",

Was this article helpful?

0 0

Post a comment