See the question and my original answer on StackOverflow

You can return a pointer from native world (C/C++, etc.) as long as you use a .NET compatible memory allocator. On Windows, that would be the COM Allocator.

So here are 3 ways to return a string: Ansi, Unicode and BSTR (unicode). Note: you should avoid using Ansi on Windows.

C++ side:

extern "C"  __declspec(dllexport) void* DecryptA(const char* name, const char* password)
{
    char str[] = "hello ansi world";
    int size = (lstrlenA(str) + 1) * sizeof(char); // terminating zero

    // use .NET compatible allocator
    void* buffer = CoTaskMemAlloc(size);
    CopyMemory(buffer, str, size);
    return buffer;
}

extern "C"  __declspec(dllexport) void* DecryptW(const wchar_t* name, const wchar_t* password)
{
    wchar_t str[] = L"hello unicode world";
    int size = (lstrlenW(str) + 1) * sizeof(wchar_t); // terminating zero

    // use .NET compatible allocator
    void* buffer = CoTaskMemAlloc(size);
    CopyMemory(buffer, str, size);
    return buffer;
}

extern "C"  __declspec(dllexport) BSTR DecryptBSTR(const wchar_t* name, const wchar_t* password)
{
    wchar_t str[] = L"hello BSTR world";

    // use .NET compatible allocator and COM coolness
    return SysAllocString(str);
}

C# side:

[DllImport("mydll", CharSet = CharSet.Ansi)]
private static extern string DecryptA(string name, string password);

[DllImport("mydll", CharSet = CharSet.Unicode)]
private static extern string DecryptW(string name, string password);

[DllImport("mydll", CharSet = CharSet.Unicode)]
[return: MarshalAs(UnmanagedType.BStr)]
private static extern string DecryptBSTR(string name, string password);

...

static void Main()
{
    Console.WriteLine(DecryptA("name", "password"));
    Console.WriteLine(DecryptW("name", "password"));
    Console.WriteLine(DecryptBSTR("name", "password"));
}