Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Are constants pinned in C#?

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;
        }
    }
}
like image 765
Derrick Avatar asked Jul 18 '11 16:07

Derrick


2 Answers

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.

like image 78
Aasmund Eldhuset Avatar answered Sep 28 '22 03:09

Aasmund Eldhuset


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);
like image 43
Derrick Avatar answered Sep 28 '22 03:09

Derrick