6

I've looked around a lot and can't seem to find a solution to anything similar to what I'm doing. I have two applications, a native C++ app and a managed C# app. The C++ app allocates a pool of bytes that are used in a memory manager. Each object allocated in this memory manager has a header, and each header has a char* that points to a name given to the object. The C# app acts as a viewer for this memory. I use memory mapped files to allow the C# app to read the memory while the C++ app is running. My issue is that I am trying to read the name of the object from the header structure and display it in C# (or just store it in a String, whatever). Using unsafe code I am able to convert the four bytes that make up the char* into an IntPtr, convert that to a void*, and call Marshal.PtrToStringAnsi. This is the code:

IntPtr namePtr = new IntrPtr(BitConverter.ToInt32(bytes, index));
unsafe
{
    void* ptr = namePtr.ToPointer();
    char* cptr = (char*)ptr;
    output = Marshal.PtrToStringAnsi((IntPtr)ptr);
}

In this case, bytes is the array read from the memory mapped file that represents all of the pool of bytes created by the native app, and index is the index of the first byte of the name pointer.

I have verified that, on the managed side of things, the address returned by the call to namePtr.ToPointer() is exactly the address of the name pointer in the native app. If this were native code, I would simply cast ptr to a char* and it would be fine, but in managed code I've read I must use the Marshaller to do this.

This code yields varying results. Sometimes cptr is null, sometimes it points to \0, and other times it points to a few Asian characters (which when run through the PtrToStringAnsi method produce seemingly irrelevant characters). I thought it might be a fixed thing, but ToPointer produces a fixed pointer. And sometimes after the cast to a char* the debugger says Unable to evaluate the expression. The pointer is not valid or something like that (it's not easy to repro every varying thing that comes back). And other times I get an access violation when reading the memory, which leads me to the C++ side of things.

On the C++ side, I figured there might be some issues with actually reading the memory because although the memory that stores the pointer is part of the memory mapped file the actual bytes that make up the text are not. So I looked at how to change read/write access to memory (on Windows, mind you) and found the VirtualProtect method in the Windows libraries, which I use to change access to the memory to PAGE_EXECUTE_WRITECOPY, which I figured would give any application that has a pointer to that address will be able to at least read what's there. But that didn't solve the issue either.

To put it shortly:

I have a pointer (in C#) to the first char in a char array (that was allocated in a C++ app) and am trying to read that array of char's into a string in C#.

EDIT:

The source header looks like this:

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;
};

The _name field is the one I'm trying to dereference in C#.

EDIT:

As of now, even using the solutions provided below, I am unable to dereference this char* in managed code. As such, I have simply made a copy of the char* in the pool referenced by the memory mapped file and use a pointer to that. This works, which makes me believe this is a protection-related issue. If I find a way to circumvent this at some point, I will answer my own question. Until then, this will be my workaround. Thanks to all who helped!

8
  • Perhaps you could use GetByteCount(), allocate a byte array, copy the bytes over using GetBytes(), then use GetString() on the newly filled byte array. A little convoluted perhaps, but it should work. Commented Oct 21, 2013 at 18:40
  • 1
    Oh yes - in order to use GetBytes(), you will have to use a fixed block on the array in order to turn it into a pointer. Commented Oct 21, 2013 at 18:41
  • I'll try that and let you know if it works. One moment... Commented Oct 21, 2013 at 18:42
  • Is the C++ char array single byte per character? or utf-16? if the latter, this should just be new string(addr, 0, len) Commented Oct 21, 2013 at 18:51
  • 2
    You cannot use pointers inside memory mapped files because each process that maps the file will use a completely different virtual address. You have to replace the pointers with byte offsets from the start of the file. If you can't modify the c++ code to do this then you need to have some way to convert the foreign pointer into the equivalent address in your address space. Commented Oct 21, 2013 at 19:46

2 Answers 2

1

This seems to work for me in my simple test:

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);
}

Perhaps a bit convoluted, but assuming your string is NUL-terminated it should work.

Sign up to request clarification or add additional context in comments.

4 Comments

Logically this looks like it would work (thank you) but now I'm consistently getting Cannot dereference 'unsafeCString'. The pointer is not valid. on the indexing of unsafeCString and the exception is Attempted to read or write protected memory. This is often an indication that other memory is corrupt.
@WilliamCustode very strange. What exactly does your source struct look like?
@WilliamCustode OH! It just came to me. In C#, a char is 16-bits. In C++, it's usually 8 bits. You need to use a byte pointer, not a char pointer.
Edited and added the struct. I'll post some of the C++, too.
1

Your code fails because pointers are only valid and meaningful in the process which owns them. You map the memory into a different process and there is a completely different virtual address space. Nothing says that the memory will be mapped into the same virtual address.

If you knew the base address in both processes you could adjust the pointers. But frankly your entire approach is deeply flawed. You really should use some real IPC here. Named pipes, sockets, WCF, even good old windows messages would suffice.


In a comment you say:

If the char array is allocated on the heap, I can dereference this pointer fine. But when the char* is initialized to something like char* a = "Hello world"; it doesn't work.

Of course not. The string literal is statically allocated by the compiler and resides in the executable module's address space. You'd need to allocate a string in your shared heap and use strcpy to copy the literal over to the shared string.

8 Comments

This method isn't flawed; the pointer that is read on the managed side is the actual pointer to the string(char*) on the native side. The issue is that the char* itself is in static space as opposed to being on the heap, and has higher levels of protection on it, which prevent things like data execution and modifying instructions dynamically out of process. It's this roadblock I was trying to overcome, and a workaround of allocation char*'s on the heap sufficed.
You've just confirmed what I said. The managed code cannot dereference a native address for the reasons that I said.
I don't believe so; You're saying that the pointer is pointing to something invalid because it is being taken out of context, when in fact it is pointing to something valid, despite context. It's just that what it's pointing to has protection around it that disallows what I'm trying to do. It's not a based pointer relative to the address from which the application is executed, it's still a meaningful address.
How does the native app know where the mmap is mapped I to the managed process? And how did you put the heap into a mmap?
I create a memory mapped file that has a buffer of a given size. This buffer is managed by the memory manager in native code. The headers are allocated in this heap, and they contain a char*. This points to static space. The managed app reads the mmfile's bytes and reconstructs the pointer, and dereferences it to get to the char array. If the char array is allocated on the heap, I can dereference this pointer fine. But when the char* is initialized to something like char* a = "Hello world"; it doesn't work.
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.