Getting the size of the array pointed to by IntPtr

6.4k views Asked by At

I have a native C++ function that I call from a C# project using pinvoke.

extern "C" _declspec(dllexport) void GetCmdKeyword( wchar_t** cmdKeyword, uint  pCmdNum )
{
 int status = 1;
 int       count     = 0;
 int       i         = 0;

 if( cmdKeyword == NULL )
      return ERR_NULL_POINTER;

 //search command in command list by letter from 'A' to 'Z'
 count = sizeof( stCommandList ) / sizeof( COMMANDLIST ) ;

 for ( i = 0 ; i < count && status != 0; i++ )
 {
      if ( pCmdNum != stCommandList[i].ulCommand )
           continue;
      *cmdKeyword = &stCommandList[i].CommandKeyWord[0];
      status = 0 ;
 }

}

where stCommandList is a strucutre of type COMMANDLIST and CommandKeyWord member is a char array.

To call this function from C#, I need to pass what arguments? cmdKeyword should be populated in a char array or a string on the C# side i.e. I need to copy the contents of the location that ptr is pointing to an int array in C# file. If I knew the length, I could use Marshal.Copy to do the same. How can I do it now? Also, I do not wish to use unsafe. Does Globalsize help in this?

2

There are 2 answers

13
David Heffernan On BEST ANSWER

You cannot infer the length from the pointer. The information must be passed as a separate value alongside the pointer to the array.

I wonder why you use raw IntPtr rather than C# arrays. I think the answer you accepted to your earlier question has the code that you need: Pinvoking a native function with array arguments.


OK, looking at the edit to the question, the actual scenario is a little different. The function returns a pointer to a null-terminated array of wide characters. Your pinvoke should be:

[DllImport(...)]
static extern void GetCmdKeyword(out IntPtr cmdKeyword, uint pCmdNum);

Call it like this:

IntPtr ptr;
GetCmdKeyword(ptr, cmdNum);
string cmdKeyword = Marshal.PtrToStringUni(ptr);
2
Igor Levicki On

You cannot infer the length from the pointer.

You can absolutely do that on a Windows platform as long as the pointer was allocated using GlobalAlloc(), LocalAlloc(), or HeapAlloc() API (though the latter is a bit more complicated).

IMPORTANT: Keep in mind that the size of a memory block may be larger than the size requested when the memory was allocated.

Here is an example on how to find memory block size allocated using Marshal.AllocHGlobal() or Marshal.StringToHGlobalUni(), which both internally use LocalAlloc():

namespace HGlobalSizeTest
{
    using System;
    using System.Runtime.InteropServices;

    internal static class WinAPI
    {
        [DllImport("kernel32.dll", SetLastError = true)]
        private static extern UIntPtr LocalSize(IntPtr hMem);

        internal static UInt64 HGlobalSize(IntPtr hMem)
        {
            return LocalSize(hMem).ToUInt64();
        }
    }

    internal static class Program
    {
        static void Main(string[] args)
        {
            string TestString = "Test string!\0With embedded null character!";

            IntPtr TestStringPtr = Marshal.StringToHGlobalUni(TestString);

            UInt64 cb = WinAPI.HGlobalSize(TestStringPtr);

            Marshal.FreeHGlobal(TestStringPtr);

            Console.WriteLine($"    TestString length = {TestString.Length}");
            Console.WriteLine($" TestStringPtr  bytes = {cb}");
            Console.WriteLine($" TestStringPtr length = {cb >> 1}");

            Console.ReadKey();
        }
    }
}

Code meant for demonstration purposes only, all error checking omitted for brevity. Do not use as-is in production.

Finally, note that GlobalAlloc() and LocalAlloc() are both deprecated in favor of HeapAlloc which has considerably less overhead so this is of very limited use in practice and it is always prudent to pass the actual unmanaged memory size as a parameter where knowing the unmanaged memory size is necessary.