Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

C# Char* to String

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!

like image 726
Will Custode Avatar asked Oct 21 '13 18:10

Will Custode


1 Answers

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.

like image 85
Richard J. Ross III Avatar answered Sep 25 '22 15:09

Richard J. Ross III