C# 字符* 到字符串
本文关键字:字符串 字符 | 更新日期: 2023-09-27 18:36:14
我环顾四周,似乎找不到与我正在做的事情类似的解决方案。我有两个应用程序,一个本机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
为 null,有时它指向 '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 数组中的第一个 char(在 C++ 应用程序中分配),并尝试将该字符数组读取到 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*,并使用指向该字符的指针。这有效,这让我相信这是一个与保护相关的问题。如果我在某个时候找到规避这个问题的方法,我会回答我自己的问题。在此之前,这将是我的解决方法。感谢所有帮助过的人!
这似乎在我的简单测试中对我有用:
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 终止的,它应该可以工作。
您的代码失败,因为指针仅在拥有它们的进程中有效且有意义。将内存映射到不同的进程,并且存在完全不同的虚拟地址空间。没有任何内容表明内存将映射到相同的虚拟地址。
如果您知道两个进程中的基址,则可以调整指针。但坦率地说,你的整个方法存在严重缺陷。你真的应该在这里使用一些真正的IPC。命名管道,套接字,WCF,甚至好的旧Windows消息就足够了。
在评论中,您说:
如果在堆上分配了 char 数组,我可以很好地取消引用此指针。但是当 char* 初始化为类似 char* a = "Hello world";它不起作用。
当然不是。字符串文本由编译器静态分配,并驻留在可执行模块的地址空间中。您需要在共享堆中分配一个字符串,并使用 strcpy 将文本复制到共享字符串。