10

I'm working in C# with a Borland C API that uses a lot of byte pointers for strings. I've been faced with the need to pass some C# strings as (short lived) byte*.

It would be my natural assumption that a const object would not be allocated on the heap, but would be stored directly in program memory, but I've been unable to verify this in any documentation.

Here's an example of what I've done in order to generate a pointer to a constant string. This does work as intended in testing, I'm just not sure if it's really safe, or it's only working via luck.

private const string pinnedStringGetWeight = "getWeight";

unsafe public static byte* ExampleReturnWeightPtr(int serial)
{
    fixed (byte* pGetWeight = ASCIIEncoding.ASCII.GetBytes(pinnedStringGetWeight))
        return pGetWeight;
}

Is this const really pinned, or is there a chance it could be moved?


@Kragen:

Here is the import:

[DllImport("sidekick.dll", CallingConvention = CallingConvention.Winapi)]
public static extern int getValueByFunctionFromObject(int serial, int function, byte* debugCallString);

This is the actual function. Yes, it actually requires a static function pointer:

 private const int FUNC_GetWeight = 0x004243D0;

 private const string pinnedStringGetWeight = "getWeight";

 unsafe public static int getWeight(int serial)
 {
     fixed (byte* pGetWeight = ASCIIEncoding.ASCII.GetBytes(pinnedStringGetWeight))
         return Core.getValueByFunctionFromObject(serial, FUNC_GetWeight, pGetWeight);
 }

Following is another method that I used when mocking my API, using a static struct, which I also hoped was pinned. I was hoping to find a way to simplify this.

public byte* getObjVarString(int serial, byte* varName)
{
    string varname = StringPointerUtils.GetAsciiString(varName);
    string value = MockObjVarAttachments.GetString(serial, varname);
    if (value == null)
        return null;
    return bytePtrFactory.MakePointerToTempString(value);
}

static UnsafeBytePointerFactoryStruct bytePtrFactory = new UnsafeBytePointerFactoryStruct();
private unsafe struct UnsafeBytePointerFactoryStruct
{
    fixed byte _InvalidScriptClass[255];
    fixed byte _ItemNotFound[255];
    fixed byte _MiscBuffer[255];

    public byte* InvalidScriptClass
    {
        get
        {
            fixed (byte* p = _InvalidScriptClass)
            {
                CopyNullString(p, "Failed to get script class");
                return p;
            }
        }
    }

    public byte* ItemNotFound
    {
        get
        {
            fixed (byte* p = _ItemNotFound)
            {
                CopyNullString(p, "Item not found");
                return p;
            }
        }
    }

    public byte* MakePointerToTempString(string text)
    {
        fixed (byte* p = _ItemNotFound)
        {
            CopyNullString(p, text);
            return p;
        }
    }

    private static void CopyNullString(byte* ptrDest, string text)
    {
        byte[] textBytes = ASCIIEncoding.ASCII.GetBytes(text);
        fixed (byte* p = textBytes)
        {
            int i = 0;
            while (*(p + i) != 0 && i < 254 && i < textBytes.Length)
            {
                *(ptrDest + i) = *(p + i);
                i++;
            }
            *(ptrDest + i) = 0;
        }
    }
}
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Derrick
  • 2,502
  • 2
  • 24
  • 34
  • 1
    This isn't the way to marshal strings to native code - can you post an example of the (presumably P/Invoke) call to your C API? – Justin Jul 18 '11 at 16:16
  • 1
    I strongly suspect that constant strings are effectively "pinned", as I don't believe they are stored on the garbage-collected heap. One piece of evidence for this is that constant strings are loaded using the "Ldstr" IL opcode, with the constant string provided as part of the method body. – Dan Bryant Jul 18 '11 at 16:18
  • 1
    I should clarify my previous statement. "The fixed statement prevents the garbage collector from relocating a movable variable. The fixed statement is only permitted in an unsafe context. Fixed can also be used to create fixed size buffers." - http://msdn.microsoft.com/en-us/library/f58wzh21(v=VS.100).aspx - RunUO is so 2010 :-) – Security Hound Jul 18 '11 at 16:18
  • I should add that one of the reasons I've not looked into using more advanced marshalling is that the majority (so far: all but this example) of my calls made to the API which use pointer arguments are made with pointers obtained from the API. While the case that I need to generate a byte* myself is rare or event eliminateable in the production code. The ability to find a relaible way to generate these byte*'s for my mocks would be handy. – Derrick Jul 18 '11 at 18:21
  • 1
    I'm pretty sure there is no *guarantee* for string constants to be pinned(for value type constants the question makes no sense, so we're only talking about string constants here). I think the main difference is that they get interned by default, but interning does not imply pinning. – CodesInChaos Jul 18 '11 at 19:49
  • @CodeInChaos: Thanks. Any thoughts about the static struct using a fixed byte[] buffer? Does it seem that this would be a semi-safe place to manufacture a temporary byte*? Regardless of how clever i thought I was when i wrote it, in retrospect it seems way too unconventional (hacky) to be valid. – Derrick Jul 19 '11 at 02:40
  • 1
    How about just allocating some unmanaged memory? That one is guaranteed to be pinned. – CodesInChaos Jul 19 '11 at 08:21
  • @CodeInChaos: I honestly didn't know that was possible in C#. I've been relying on stack allocated structs more since this post. Thanks for this tip! I found another question regarding this if anyone stumbles across this and is interested: http://stackoverflow.com/questions/2648560/allocating-unmanaged-memory-in-c – Derrick Jul 20 '11 at 18:41

2 Answers2

12

How constants are allocated shouldn't matter in this case, because ASCIIEncoding.ASCII.GetBytes() returns a new byte array (it cannot return the constant's internal array, since it is encoded differently (edit: there is hopefully no way to get a pointer to a string's internal array, since strings are immutable)). However, the guarantee that the GC won't touch the array only lasts as long as the fixed scope does - in other words, when the function returns, the memory is no longer pinned.

Aasmund Eldhuset
  • 37,289
  • 4
  • 68
  • 81
  • This definately answers my question. Totally missed this. Thanks so much. My returned byte* is completely unreliable once returned, I've been getting lucky. – Derrick Jul 18 '11 at 16:48
  • If it answers your question, you should mark this as the answer. – Judah Gabriel Himango Jul 18 '11 at 17:44
  • @Judah Himango: I'm not sure if the literal topic question of whether consts are pinned is answered, even though this answer is completely sufficient to satisfy the question about the (in)correctness of my own code. – Derrick Jul 18 '11 at 17:49
  • Thanks for the edit. A good point :) If I rephrased this question I would ask how a pointer to a byte[] could be established and fixed for the liftime of the assembly, to some extent the special nature of strings complicated the issue somewhat. I'm much more on the right track now. Again, a great help, thanks. – Derrick Jul 19 '11 at 03:22
1

Based on Kragans comment, I looked into the proper way to marshall my string to a byte pointer and am now using the following for the first example I used in my question:

        [DllImport("sidekick.dll", CallingConvention = CallingConvention.Winapi)]
        public static extern int getValueByFunctionFromObject(int serial, int function, [MarshalAs(UnmanagedType.LPStr)]string debugCallString);
Derrick
  • 2,502
  • 2
  • 24
  • 34