Symptom
===========
Customer built an ASP.NET web application using Visual Studio 2008 on Windows 2008. In the source code, it called a function which was imported from native c++ dll:
[The source code looks like below]
public partial class _Default : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
}
[System.Runtime.InteropServices.DllImport("PinvokeLib.dll", EntryPoint = " TestStringAsResult", CharSet = System.Runtime.InteropServices.CharSet.Ansi)]
public static extern string TestStringAsResult();
……
protected void Button_Click(object sender, EventArgs e)
{
string str = TestStringAsResult();
}
}
Above code seems working fine during debugging when "Use Visual Studio Development Server" in Web properties. However, when choose the web properties to “Use IIS Web Server”, the web page seems hang and returns server internal error message. This issue will not occur on Windows 2003.
Troubleshoot
===========
We got the source code of native c++ dll and the related method was written like below:
extern "C" PINVOKELIB_API char * TestStringAsResult()
{
return "Test String";
}
We rebuilt the native dll and web application with VS 2008 on a test Windows 2008 server. When we ran the web application, w3wp.exe crashed and WerFault.exe (Windows Error Reporting service) showed up trying to record logs. In the application event log, we found 0xc0000374 exception:
Log Name: Application
Source: Application Error
Date: 12/12/2009 3:37:00 AM
Event ID: 1000
Task Category: (100)
Level: Error
Keywords: Classic
User: N/A
Computer: 182462M
Description:
Faulting application w3wp.exe, version 7.0.6001.18000, time stamp 0x47919413, faulting module ntdll.dll, version 6.0.6001.18000, time stamp 0x4791a7a6, exception code 0xc0000374, fault offset 0x000b015d, process id 0x10e0, application start time 0x01ca7b1f684ff683.
0xc0000374 indicates that there is heap corruption issue existed. We used DebugDiag to catch w3wp.exe crash dump file and found below call stack:
0:023> kL200
ChildEBP RetAddr
052deaf4 77df0d68 ntdll!RtlReportCriticalFailure+0x5b
052deb04 77df0e56 ntdll!RtlpReportHeapFailure+0x21
052deb38 77db0531 ntdll!RtlpLogHeapFailure+0xa1
052deb64 7680c56f ntdll!RtlFreeHeap+0x60
052deb78 77c3dc2c kernel32!HeapFree+0x14
052deb8c 77c3dc53 ole32!CRetailMalloc_Free+0x1c
052deb9c 7a0dce06 ole32!CoTaskMemFree+0x13
052debac 7a0e06c2 mscorwks!DefaultMarshalOverrides<CSTRMarshalerBase>::ReturnCLRFromNative+0x35
052dede8 79e957c3 mscorwks!RunML+0x949
052dee68 79e956b7 mscorwks!NDirectGenericStubPostCall+0x194
052deef4 79e85686 mscorwks!NDirectGenericStubReturnFromCall+0x1f
052deefc 051f0678 mscorwks!NDirectSlimStubWorker2+0x13d
WARNING: Frame IP not in any known module. Following frames may be wrong.
052def38 04836dee 0x51f0678
052def50 04836fdc System_Web_ni+0x276dee
00000000 00000000 System_Web_ni+0x276fdc
0:023> !clrstack
OS Thread Id: 0xb4c (23)
ESP EIP
052def14 77df015d [NDirectMethodFrameGeneric: 052def14] WebApplication3._Default.__PBExternal_fnreturnenstra()
052def24 051f0678 WebApplication3._Default.Button1_Click(System.Object, System.EventArgs)
052def44 04836dee System.Web.UI.WebControls.Button.OnClick(System.EventArgs)
052def58 04836fdc System.Web.UI.WebControls.Button.RaisePostBackEvent(System.String)
……
052df2ec 04663072 System.Web.HttpRuntime.ProcessRequestNoDemand(System.Web.HttpWorkerRequest)
052df2f8 0466157b System.Web.Hosting.ISAPIRuntime.ProcessRequest(IntPtr, Int32)
We could see that CoTaskMemFree was called to free unmanaged memory in this situation. The problem is due to CLR use different protocol to allocate memory and free memory between managed code and unmanaged code. According to below MSDN article, when CLR marshals unmanaged memory to the string, it will always invoke CoTaskMemFree method to free the unmanaged memory:
“Memory Management with the Interop Marshaler”
<http://msdn.microsoft.com/en-us/library/f1cf4kkz(VS.80).aspx>
Since the unmanaged memory is not allocate by CoTaskMemAlloc, it will crashed by
ntdll!RtlpLowFragHeapFree checking. This issue will also happen if we use NEW or malloc function to allocate memory in C++ dll. Below code will fail as well:
TCHAR* WINAPI TestStringAsResult()
{
TCHAR *ret;
ret = new TCHAR[10];
wcscpy(ret, L"test");
return ret;
}
To fix this problem, we need to explicitly call CoTaskMemAlloc method to allocate memory in native dll:
#include <objbase.h>
……
extern "C" PINVOKELIB_API char * TestStringAsResult()
{
STRSAFE_LPWSTR result = (STRSAFE_LPWSTR)CoTaskMemAlloc( 64 );
StringCchCopy( result, sizeof(result) , (STRSAFE_LPWSTR)"This is return value" );
return (char *) result;
}
If the unmanaged memory is not allocated with the CoTaskMemAlloc method and could not be changed, we must use an IntPtr and free the memory manually using the appropriate method. The declaration in c# should be changed from:
[System.Runtime.InteropServices.DllImport("PinvokeLib.dll", EntryPoint = " TestStringAsResult", CharSet = System.Runtime.InteropServices.CharSet.Ansi)]
public static extern string TestStringAsResult();
To:
[System.Runtime.InteropServices.DllImport("PinvokeLib.dll", EntryPoint = " TestStringAsResult", CharSet = System.Runtime.InteropServices.CharSet.Ansi)]
public static extern IntPtr TestStringAsResult();
Another Question
===========
Why this problem doesn’t happen under Win2003?
It is because that Windows Vista and Win2008 support new heap verification feature.
“HeapEnableTerminationOnCorruption”
<http://msdn.microsoft.com/en-us/library/aa366705.aspx>
It enables the terminate-on-corruption feature. If the heap manager detects an error in any heap used by the process, it calls the Windows Error Reporting service and terminates the process. So the heap corruption issue will be reported by Win2008 or Vista explicitly while Win2003 OS doesn’t report this though the heap corruption indeed exsits.
More Information
===========
Below two articles provides the sample code of calling native c++ dll function within c#:
“Strings Sample”
http://msdn.microsoft.com/en-us/library/e765dyyy(VS.80).aspx
“PinvokeLib.dll”
http://msdn.microsoft.com/en-us/library/as6wyhwt(VS.80).aspx
References
===========
Marshaling between Managed and Unmanaged Code
http://msdn.microsoft.com/en-us/magazine/cc164193.aspx
Default Marshaling for Strings
http://msdn.microsoft.com/en-us/library/s9ts558h(VS.80).aspx
Default Marshaling for Arrays
http://msdn.microsoft.com/en-us/library/z6cfh6e6(VS.80).aspx
Memory Management with the Interop Marshaler
http://msdn.microsoft.com/en-us/library/f1cf4kkz(VS.80).aspx
Regards,
Hanson Wang