See the question and my original answer on StackOverflow

In any case, when you allocate something from C/C++/Native side, you must use the COM allocator which .NET understands. So there are many ways to return a string, for example:

C++:

extern "C" __declspec(dllexport) void* __stdcall GetBSTR() {
    return SysAllocString(L"hello world"); // uses CoTaskMemAlloc underneath
}

extern "C" __declspec(dllexport) void* __stdcall GetLPSTR() {

    const char* p = "hello world";
    int size = lstrlenA(p) + 1;
    void* lp = CoTaskMemAlloc(size);
    if (lp)
    {
        CopyMemory(lp, p, size);
    }
    return lp;
}

extern "C" __declspec(dllexport) void* __stdcall GetLPWSTR() {

    const wchar_t* p = L"hello world";
    int size = (lstrlenW(p) + 1) * sizeof(wchar_t);
    void* lp = CoTaskMemAlloc(size);
    if (lp)
    {
        CopyMemory(lp, p, size);
    }
    return lp;
}

And C#

[DllImport("MyDll")]
[return: MarshalAs(UnmanagedType.BStr)]
public static extern string GetBSTR();

[DllImport("MyDll")]
[return: MarshalAs(UnmanagedType.LPWStr)]
public static extern string GetLPWSTR();

[DllImport("MyDll")]
[return: MarshalAs(UnmanagedType.LPStr)]
public static extern string GetLPSTR();