Description
BUG REPORT: C++/CLI marshaling of wchar_t* buffer causes silent CLR heap corruption
Summary
Calling a native DLL function that writes into a wchar_t* buffer using an incorrect
C++/CLI marshaling pattern does not produce any compiler error or runtime error.
Instead, the CLR heap becomes corrupted, and the crash appears later in unrelated
WinForms code (new Form() or Form.Show()), producing misleading exceptions.
The incorrect code compiles and runs, but silently corrupts memory.
Environment
- Windows 11 x64
- Visual Studio 2022
- .NET Framework 4.8 (C++/CLI)
- Native C++ DLL exporting:
int __stdcall Camera_EnumDevices(wchar_t* buffer, int bufferSize);
Incorrect C++/CLI code (compiles, runs, but corrupts memory)
StringBuilder^ sb = gcnew StringBuilder(2048);
pin_ptr<const wchar_t> pBuffer = PtrToStringChars(sb->ToString());
int count = Camera_EnumDevices((wchar_t*)pBuffer, sb->Capacity);
Problem
sb->ToString() creates a new immutable System.String, not the internal buffer of
the StringBuilder. The native function writes into this immutable string, causing
heap corruption.
The corruption does not crash immediately. Instead, it crashes later inside
WinForms code, for example:
var f = new Form2();
f.Show(); // crash here
The exception is misleading and unrelated to the actual cause.
Correct C++/CLI code (works, no corruption)
array<wchar_t>^ buf = gcnew array<wchar_t>(2048);
pin_ptr<wchar_t> pBuffer = &buf[0];
int count = Camera_EnumDevices(pBuffer, 2048);
String^ names = gcnew String(pBuffer);
Expected behavior
- Compiler warning or error when attempting to pin an immutable string for native
write access.
- Runtime protection or exception when native code writes into immutable managed
memory.
Actual behavior
- Code compiles without warnings.
- Code runs without immediate error.
- Native write corrupts CLR heap.
- Crash occurs later in unrelated WinForms code.
- Exception message does not indicate the real cause.
Impact
This issue is extremely hard to diagnose because:
- The crash happens far away from the actual bug.
- The exception message is misleading.
- The incorrect code looks valid and compiles cleanly.
- Developers may incorrectly blame WinForms, COM, Media Foundation, or MFStartup/MFShutdown.
Reproduction Steps
Reproduce
The following C++/CLI code compiles and executes without any warnings or errors,
but causes silent CLR heap corruption. The crash appears later in unrelated
WinForms code (new Form() or Form.Show()), producing misleading exceptions.
Incorrect version (causes CLR heap corruption)
List<String^>^ CameraWrapper::GetCameraList()
{
StringBuilder^ sb = gcnew StringBuilder(2048);
// Pinning an immutable System.String created by ToString()
pin_ptr<const wchar_t> pBuffer = PtrToStringChars(sb->ToString());
int count = Camera_EnumDevices((wchar_t*)pBuffer, sb->Capacity);
if (count <= 0)
return gcnew List<String^>();
String^ names = gcnew String(pBuffer);
array<String^>^ arr = names->Split(
gcnew array<wchar_t>{'|'},
StringSplitOptions::RemoveEmptyEntries);
return gcnew List<String^>(arr);
}
This code silently corrupts the CLR heap because sb->ToString() returns a new
immutable System.String, not the internal buffer of the StringBuilder. The native
function writes into this immutable string, corrupting memory.
The corruption does not crash immediately. Instead, it crashes later inside
WinForms code, for example:
var f = new Form2();
f.Show(); // crash here
Correct version (no heap corruption)
List<String^>^ CameraWrapper::GetCameraList()
{
const int maxChars = 2048;
// Managed mutable buffer
array<wchar_t>^ buf = gcnew array<wchar_t>(maxChars);
// Pin the actual writable buffer
pin_ptr<wchar_t> pBuffer = &buf[0];
int count = Camera_EnumDevices(pBuffer, maxChars);
if (count <= 0)
return gcnew List<String^>();
String^ names = gcnew String(pBuffer);
array<String^>^ arr = names->Split(
gcnew array<wchar_t>{'|'},
StringSplitOptions::RemoveEmptyEntries);
return gcnew List<String^>(arr);
}
This version uses a real mutable buffer, which the native code can safely write
into. No heap corruption occurs and WinForms no longer crashes.
Expected behavior
List<String^>^ CameraWrapper::GetCameraList()
{
const int maxChars = 2048;
// Managed mutable buffer
array<wchar_t>^ buf = gcnew array<wchar_t>(maxChars);
// Pin the actual writable buffer
pin_ptr<wchar_t> pBuffer = &buf[0];
int count = Camera_EnumDevices(pBuffer, maxChars);
if (count <= 0)
return gcnew List<String^>();
String^ names = gcnew String(pBuffer);
array<String^>^ arr = names->Split(
gcnew array<wchar_t>{'|'},
StringSplitOptions::RemoveEmptyEntries);
return gcnew List<String^>(arr);
}
Actual behavior
List<String^>^ CameraWrapper::GetCameraList()
{
StringBuilder^ sb = gcnew StringBuilder(2048);
// Pinning an immutable System.String created by ToString()
pin_ptr<const wchar_t> pBuffer = PtrToStringChars(sb->ToString());
int count = Camera_EnumDevices((wchar_t*)pBuffer, sb->Capacity);
if (count <= 0)
return gcnew List<String^>();
String^ names = gcnew String(pBuffer);
array<String^>^ arr = names->Split(
gcnew array<wchar_t>{'|'},
StringSplitOptions::RemoveEmptyEntries);
return gcnew List<String^>(arr);
}
does not generate any exception during it's execution, but impact CLR heap
Regression?
No response
Known Workarounds
No response
Configuration
No response
Other information
Closing note: Diagnostics on 5.7.2026 from 15:00 to 18:30 confirmed silent CLR heap corruption caused by incorrect C++/CLI marshaling.
Description
BUG REPORT: C++/CLI marshaling of wchar_t* buffer causes silent CLR heap corruption
Summary
Calling a native DLL function that writes into a wchar_t* buffer using an incorrect
C++/CLI marshaling pattern does not produce any compiler error or runtime error.
Instead, the CLR heap becomes corrupted, and the crash appears later in unrelated
WinForms code (new Form() or Form.Show()), producing misleading exceptions.
The incorrect code compiles and runs, but silently corrupts memory.
Environment
int __stdcall Camera_EnumDevices(wchar_t* buffer, int bufferSize);
Incorrect C++/CLI code (compiles, runs, but corrupts memory)
Problem
sb->ToString()creates a new immutable System.String, not the internal buffer ofthe
StringBuilder. The native function writes into this immutable string, causingheap corruption.
The corruption does not crash immediately. Instead, it crashes later inside
WinForms code, for example:
The exception is misleading and unrelated to the actual cause.
Correct C++/CLI code (works, no corruption)
Expected behavior
write access.
memory.
Actual behavior
Impact
This issue is extremely hard to diagnose because:
Reproduction Steps
Reproduce
The following C++/CLI code compiles and executes without any warnings or errors,
but causes silent CLR heap corruption. The crash appears later in unrelated
WinForms code (new Form() or Form.Show()), producing misleading exceptions.
Incorrect version (causes CLR heap corruption)
List<String^>^ CameraWrapper::GetCameraList() { StringBuilder^ sb = gcnew StringBuilder(2048); // Pinning an immutable System.String created by ToString() pin_ptr<const wchar_t> pBuffer = PtrToStringChars(sb->ToString()); int count = Camera_EnumDevices((wchar_t*)pBuffer, sb->Capacity); if (count <= 0) return gcnew List<String^>(); String^ names = gcnew String(pBuffer); array<String^>^ arr = names->Split( gcnew array<wchar_t>{'|'}, StringSplitOptions::RemoveEmptyEntries); return gcnew List<String^>(arr); }This code silently corrupts the CLR heap because sb->ToString() returns a new
immutable System.String, not the internal buffer of the StringBuilder. The native
function writes into this immutable string, corrupting memory.
The corruption does not crash immediately. Instead, it crashes later inside
WinForms code, for example:
Correct version (no heap corruption)
List<String^>^ CameraWrapper::GetCameraList() { const int maxChars = 2048; // Managed mutable buffer array<wchar_t>^ buf = gcnew array<wchar_t>(maxChars); // Pin the actual writable buffer pin_ptr<wchar_t> pBuffer = &buf[0]; int count = Camera_EnumDevices(pBuffer, maxChars); if (count <= 0) return gcnew List<String^>(); String^ names = gcnew String(pBuffer); array<String^>^ arr = names->Split( gcnew array<wchar_t>{'|'}, StringSplitOptions::RemoveEmptyEntries); return gcnew List<String^>(arr); }This version uses a real mutable buffer, which the native code can safely write
into. No heap corruption occurs and WinForms no longer crashes.
Expected behavior
List<String^>^ CameraWrapper::GetCameraList() { const int maxChars = 2048; // Managed mutable buffer array<wchar_t>^ buf = gcnew array<wchar_t>(maxChars); // Pin the actual writable buffer pin_ptr<wchar_t> pBuffer = &buf[0]; int count = Camera_EnumDevices(pBuffer, maxChars); if (count <= 0) return gcnew List<String^>(); String^ names = gcnew String(pBuffer); array<String^>^ arr = names->Split( gcnew array<wchar_t>{'|'}, StringSplitOptions::RemoveEmptyEntries); return gcnew List<String^>(arr); }Actual behavior
List<String^>^ CameraWrapper::GetCameraList() { StringBuilder^ sb = gcnew StringBuilder(2048); // Pinning an immutable System.String created by ToString() pin_ptr<const wchar_t> pBuffer = PtrToStringChars(sb->ToString()); int count = Camera_EnumDevices((wchar_t*)pBuffer, sb->Capacity); if (count <= 0) return gcnew List<String^>(); String^ names = gcnew String(pBuffer); array<String^>^ arr = names->Split( gcnew array<wchar_t>{'|'}, StringSplitOptions::RemoveEmptyEntries); return gcnew List<String^>(arr); }does not generate any exception during it's execution, but impact CLR heap
Regression?
No response
Known Workarounds
No response
Configuration
No response
Other information
Closing note: Diagnostics on 5.7.2026 from 15:00 to 18:30 confirmed silent CLR heap corruption caused by incorrect C++/CLI marshaling.