【问题标题】:C# Char* to StringC# Char* 到字符串
【发布时间】:2013-10-30 09:17:49
【问题描述】:

我环顾四周,似乎无法找到与我正在做的事情类似的任何事情的解决方案。我有两个应用程序,一个本机 C++ 应用程序和一个托管 C# 应用程序。 C++ 应用程序分配在内存管理器中使用的字节池。在此内存管理器中分配的每个对象都有一个标头,每个标头都有一个 char* 指向对象的名称。 C# 应用程序充当此内存的查看器。我使用内存映射文件来允许 C# 应用程序在 C++ 应用程序运行时读取内存。我的问题是我试图从标题结构中读取对象的名称并将其显示在 C# 中(或者只是将其存储在字符串中,等等)。使用不安全代码,我能够将构成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* 之后,调试器会说Unable to evaluate the expression. The pointer is not valid 或类似的东西(重现返回的每一个不同的东西并不容易)。有时我在读取内存时遇到访问冲突,这导致我使用 C++。

在 C++ 方面,我认为实际读取内存可能存在一些问题,因为虽然存储指针的内存是内存映射文件的一部分,但构成文本的实际字节却不是。因此,我查看了如何更改对内存的读/写访问(在 Windows 上,请注意),并在 Windows 库中找到了 VirtualProtect 方法,我用它来将内存访问更改为 PAGE_EXECUTE_WRITECOPY,我认为这会给任何应用程序有一个指向该地址的指针至少能够读取那里的内容。但这也没有解决问题。

简单地说:

我有一个指针(在 C# 中)指向 char 数组(在 C++ 应用程序中分配)中的第一个 char,并且正在尝试将该 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* 并使用指向它的指针。这很有效,这让我相信这是一个与保护相关的问题。如果我在某个时候找到一种方法来规避这个问题,我会回答我自己的问题。在那之前,这将是我的解决方法。感谢所有帮助过的人!

【问题讨论】:

  • 也许你可以使用GetByteCount(),分配一个字节数组,使用GetBytes()复制字节,然后在新填充的字节数组上使用GetString()。可能有点令人费解,但它应该起作用。
  • 哦,是的 - 为了使用 GetBytes(),您必须在数组上使用 fixed 块才能将其转换为指针。
  • 我会尝试并告诉你它是否有效。一会儿……
  • C++ char 数组是每个字符单字节吗?还是UTF-16?如果是后者,这应该只是new string(addr, 0, len)
  • 您不能在内存映射文件中使用指针,因为映射文件的每个进程将使用完全不同的虚拟地址。您必须用文件开头的字节偏移量替换指针。如果您无法修改 c++ 代码来执行此操作,那么您需要有某种方法将外部指针转换为地址空间中的等效地址。

标签: c# c++ pointers memory unsafe


【解决方案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 的索引上得到Cannot dereference 'unsafeCString'. The pointer is not valid.,例外是Attempted to read or write protected memory. This is often an indication that other memory is corrupt.
  • @WilliamCustode 很奇怪。您的源结构到底是什么样的?
  • @WilliamCustode 哦!它刚刚来找我。在 C# 中,char 是 16 位的。在 C++ 中,它通常是 8 位。您需要使用字节指针,而不是字符指针。
  • 编辑并添加了结构。我也会发布一些 C++。
【解决方案2】:

您的代码失败是因为指针仅在拥有它们的进程中才有效且有意义。您将内存映射到不同的进程,并且存在完全不同的虚拟地址空间。没有说内存会被映射到同一个虚拟地址。

如果您知道两个进程中的基地址,则可以调整指针。但坦率地说,你的整个方法存在严重缺陷。你真的应该在这里使用一些真正的IPC。命名管道、套接字、WCF,甚至是好的旧 Windows 消息就足够了。


在评论中你说:

如果 char 数组是在堆上分配的,我可以很好地取消引用这个指针。但是当 char* 被初始化为 char* a = "Hello world";它不起作用。

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

【讨论】:

  • 这种方法没有缺陷;在托管端读取的指针是指向本机端字符串(char*)的实际指针。问题是 char* 本身位于静态空间中,而不是在堆上,并且对其具有更高级别的保护,这可以防止数据执行和动态修改指令等事情超出进程。这是我试图克服的障碍,并且在堆上分配 char* 的解决方法就足够了。
  • 你刚刚证实了我所说的。由于我所说的原因,托管代码无法取消引用本机地址。
  • 我不这么认为;您是说指针指向无效的东西,因为它被脱离上下文,而实际上它指向的是有效的东西,尽管有上下文。只是它所指向的东西周围有保护,这不允许我尝试做的事情。它不是相对于执行应用程序的地址的基于指针,它仍然是一个有意义的地址。
  • 原生应用程序如何知道 mmap 映射到托管进程的位置?您是如何将堆放入 mmap 的?
  • 我创建了一个内存映射文件,它有一个给定大小的缓冲区。此缓冲区由本机代码中的内存管理器管理。标头在此堆中分配,它们包含一个 char*。这指向静态空间。托管应用程序读取 mmfile 的字节并重建指针,并取消引用它以获取 char 数组。如果 char 数组是在堆上分配的,我可以很好地取消引用这个指针。但是当 char* 初始化为 char* a = "Hello world"; 之类的东西时,它就不起作用了。
猜你喜欢
  • 2018-09-02
  • 2012-02-20
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-01-21
  • 2017-06-15
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多