I'll re-emphasize the fact that your C++ code is invalid. You are returning a pointer to a local variable on a stack frame that is no longer valid when the function returns. That it works now is merely an accident. As soon as you call another function, the stack space will be reused, corrupting the buffer content. This is guaranteed to happen when you call this code from C#, the P/Invoke marshaller implementation will overwrite the stack frame and corrupt the buffer.
To make matters worse, the P/Invoke marshaller will try to free the buffer. It will assume that the buffer was allocated by CoTaskMemAlloc() and call CoTaskMemFree() to release the buffer. That will silently fail on Windows XP and earlier but crash your program on Vista and up.
Which is one solution to your issue, use CoTaskMemAlloc() to allocate the buffer instead of using a local variable. The all-around better solution is to let the caller of the function pass the buffer, to be filled with the string. That way, the caller can decide how to allocate the buffer and clean it up as necessary. The function signature should look like this:
extern "C" __declspec(dllexport)
void __stdcall GetMessage(int id, char* buffer, size_t bufferSize);
Use strcpy_s() to safely fill the buffer and avoid any buffer overflow. The corresponding C# declaration would then be:
[DllImport("mydll.dll")]
private static extern void GetMessage(int id, StringBuilder buffer, int bufferSize);
and called like this:
var sb = new StringBuilder(250);
GetMessage(id, sb, sb.Capacity);
string retval = sb.ToString();