C# Char* 转换为字符串

6
我已经搜索了很多,但似乎找不到与我所做的类似的解决方案。我有两个应用程序,一个是本地的C++应用程序,另一个是托管的C#应用程序。C++应用程序分配一组字节,用于内存管理器。在此内存管理器中分配的每个对象都具有标题,每个标题都有一个指向对象名称的char*。C#应用程序充当此内存的查看器。我使用内存映射文件使C#应用程序能够在C++应用程序运行时读取内存。我的问题是,我正在尝试从标题结构中读取对象的名称并在C#中显示它(或者只是将其存储在字符串中)。使用unsafe代码,我能够将组成char*的四个字节转换为IntPtr,将其转换为void*,并调用Marshal.PtrToStringAnsi。这是代码:
IntPtr namePtr = new IntrPtr(BitConverter.ToInt32(bytes, index));
unsafe
{
    void* ptr = namePtr.ToPointer();
    char* cptr = (char*)ptr;
    output = Marshal.PtrToStringAnsi((IntPtr)ptr);
}

在这种情况下,bytes是从内存映射文件中读取的数组,表示本机应用程序创建的所有字节池,而index是名称指针的第一个字节的索引。
我已经验证,在托管方面,调用namePtr.ToPointer()返回的地址正好是本机应用程序中名称指针的地址。如果这是本机代码,我只需将ptr转换为char*即可,但在托管代码中,我必须使用Marshaller来完成这个过程。
这段代码产生不同的结果。有时cptr为空,有时指向\0,有时指向几个亚洲字符(当通过PtrToStringAnsi方法运行时,会产生看似不相关的字符)。我认为这可能是一个fixed问题,但ToPointer会产生一个固定的指针。有时,在将其转换为char*之后,调试器会说无法评估表达式。指针无效或类似的内容(不容易复制每个变化的事物)。其他时候,当读取内存时,我会收到访问冲突的消息,这导致我进入了C++的一面。
在C++方面,我想可能存在一些问题,实际上无法读取存储指针的内存,因为存储文本的实际字节不是内存映射文件的一部分。因此,我查看了如何更改对内存的读/写访问权限(在Windows上),并找到了Windows库中的VirtualProtect方法,我使用它来将内存的访问权限更改为PAGE_EXECUTE_WRITECOPY,我认为任何具有该地址指针的应用程序都能够至少读取其中内容。但这也没有解决问题。
简而言之:
我在C#中有一个指向char数组中第一个char的指针(该数组是在C++应用程序中分配的),并正在尝试将那些char数组读入C#中的字符串中。
编辑:
源标题看起来像这样:
struct AllocatorHeader
{
// These bytes are reserved, and their purposes may change.
char _reserved[4];

// A pointer to a destructor mapping that is associated with this object.
DestructorMappingBase* _destructor;

// The size of the object this header is for.
unsigned int _size;

char* _name;
};

_name字段是我在C#中尝试解引用的字段。

编辑:

截至目前,即使使用下面提供的解决方案,我仍然无法在托管代码中解引用这个char*。因此,我只是在内存映射文件引用的池中制作了char*的副本,并使用指针指向它。这有效,这让我相信这是一个与保护相关的问题。如果我找到了绕过这个问题的方法,我会回答自己的问题。在那之前,这将是我的解决方法。感谢所有帮助我的人!


1
哦,是的 - 为了使用 GetBytes(),您将不得不在数组上使用 fixed 块,以将其转换为指针。 - Richard J. Ross III
C++的char数组是单字节字符还是UTF-16?如果是后者,那么应该只需使用new string(addr, 0, len) - Marc Gravell
这是前者,其中每个字符都是ASCII(1字节)。我正在尝试@RichardJ.RossIII的想法,但遇到了一些问题。 - Will Custode
@WilliamCustode 让我将其添加为答案,这样您就可以一次性测试整个内容。 - Richard J. Ross III
2
你不能在内存映射文件中使用指针,因为每个映射文件的进程将使用完全不同的虚拟地址。你必须用文件开头的字节偏移量替换指针。如果你无法修改C++代码来实现这一点,那么你需要有一种方法将外部指针转换为你地址空间中的等效地址。 - Brian Sandlin
显示剩余3条评论
2个回答

1

在我的简单测试中,这似乎对我有效:

private static unsafe String MarshalUnsafeCStringToString(IntPtr ptr, Encoding encoding) {
    void *rawPointer = ptr.ToPointer();
    if (rawPointer == null) return "";

    char* unsafeCString = (char*)rawPointer;

    int lengthOfCString = 0;
    while (unsafeCString[lengthOfCString] != '\0') {
        lengthOfCString++;
    }

    // now that we have the length of the string, let's get its size in bytes
    int lengthInBytes = encoding.GetByteCount (unsafeCString, lengthOfCString);
    byte[] asByteArray = new byte[lengthInBytes];

    fixed (byte *ptrByteArray = asByteArray) {
        encoding.GetBytes(unsafeCString, lengthOfCString, ptrByteArray, lengthInBytes);
    }

    // now get the string
    return encoding.GetString(asByteArray);
}

也许有点复杂,但是假设您的字符串以NUL结尾,它应该可以工作。


从逻辑上看,这似乎可以工作(谢谢),但现在我在对unsafeCString进行索引时始终收到“无法取消引用'unsafeCString'。指针无效。”的错误,并且异常是“尝试读取或写入受保护的内存。这通常是其他内存已损坏的迹象。” - Will Custode
1
@WilliamCustode 非常奇怪。你的源结构体具体是什么样子? - Richard J. Ross III
1
@WilliamCustode 哦!我想起来了。在 C# 中,char 是 16 位的。而在 C++ 中,通常是 8 位。你需要使用字节指针,而不是字符指针。 - Richard J. Ross III
编辑并添加了结构体。我也会发布一些C++代码。 - Will Custode

1
你的代码失败是因为指针只在拥有它们的进程中有效且有意义。你将内存映射到不同的进程中,这样会有完全不同的虚拟地址空间。没有什么能够保证内存将被映射到相同的虚拟地址。
如果你知道两个进程中的基址,你可以调整指针。但坦白地说,你的整个方法是极其错误的。你真的应该在这里使用一些真正的IPC。命名管道、套接字、WCF,甚至是老式的Windows消息都足够。

在评论中,你说:

如果字符数组在堆上分配,我可以很好地解引用此指针。但是,当char*初始化为char *a =“Hello world”;之类的内容时,它不起作用。

当然不行。字符串文字由编译器静态分配,并驻留在可执行模块的地址空间中。您需要在共享堆中分配一个字符串,并使用strcpy将文本复制到共享字符串中。


这种方法并没有缺陷;在托管端读取的指针是本机端字符串(char*)的实际指针。问题在于char本身位于静态空间,而不是堆上,并且具有更高级别的保护,防止像数据执行和动态修改指令之类的事情跨进程进行。我试图克服的是这个障碍,通过在堆上分配char的解决方法已经足够。 - Will Custode
你刚刚证实了我所说的。托管代码不能解引用本机地址,原因就是我所说的。 - David Heffernan
我不这么认为;你说指针指向了无效的东西,因为它被脱离了上下文,但实际上它指向的是有效的东西,尽管上下文不同。只是它所指向的东西有保护措施,不允许我想做的事情。它不是相对于应用程序执行地址的基础指针,它仍然是一个有意义的地址。 - Will Custode
本地应用程序如何知道mmap映射到托管进程的位置?您是如何将堆放入mmap中的? - David Heffernan
我创建了一个内存映射文件,它具有给定大小的缓冲区。该缓冲区由本地代码中的内存管理器进行管理。这些头文件在此堆中分配,并且它们包含一个char。这指向静态空间。托管应用程序读取mmfile的字节并重建指针,然后取消引用它以访问char数组。如果char数组在堆上分配,那么我可以很好地取消引用此指针。但是当char初始化为类似char* a =“Hello world”的内容时,它不起作用。 - Will Custode
显示剩余3条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接