1 Manual native memory extensions
David Hall edited this page 2022-08-08 09:30:01 -06:00

Including the namespace Vanara.Extensions provides access to many extensions to existing .NET classes and structures, including extensions for IntPtr. Here are some of the things you can do:

Reading memory

Extract a structure

The library can extract structures or classes that have an implied or expressed layout. This includes classes marked with the VanaraMarshalerAttribute.

// Extract a RECT (This is a copy and no longer points to the native memory)
IntPtr ptr = /* a pointer to native memory */;
CharSet charSet = CharSet.Unicode; /* Type of characters in the strings */
int offset = 0; /* number of bytes to offset the beginning of the enumeration */
int memSize = /* number of bytes allocated to ptr */;
RECT r = ptr.ToStructure<RECT>(memSize, offset);

// Get a reference to value types (This reference still points to the native memory)
ref RECT rr = ref ptr.AsRef<RECT>(offset, memSize);

Convert a pointer to an array of a type

This copies the contents of native memory to a managed array.

IntPtr ptr = /* a pointer to native memory */;
int elemCount = /* number of array elements */;
int offset = 0; /* number of bytes to offset the beginning of the enumeration */
int memSize = /* number of bytes allocated to ptr */;
ptr.ToArray<RECT>(elemCount, offset, memSize);

Convert a pointer to an IEnumerable sequence

You may also just need to enumerate over the memory. You can do this in two ways, with a direct iterator or with a Span. The only difference is that ToIEnum can also handle custom structure marshaling using IVanaraMarshaler.

// Use the iterator
IntPtr ptr = /* a pointer to native memory */;
int elemCount = /* number of array elements */;
int offset = 0; /* number of bytes to offset the beginning of the enumeration */
int memSize = /* number of bytes allocated to ptr */;
foreach (var rect in ptr.ToIEnum<RECT>(elemCount, offset, memSize))
{
   // iterate while ptr is still pointing to valid memory
}

// Use Span
foreach (var rect in ptr.AsSpan<RECT>(elemCount, offset, memSize))
{
   // iterate while ptr is still pointing to valid memory
}

Get a string array from memory

String arrays, natively, take two forms: 1) An array of pointers to string, and 2) An array of strings where each string is terminated with null and the array is terminated with an additional null.

// Array of string pointers
IntPtr ptr = /* a pointer to native memory */;
int elemCount = /* number of array elements */;
CharSet charSet = CharSet.Unicode; /* Type of characters in the strings */
int offset = 0; /* number of bytes to offset the beginning of the enumeration */
int memSize = /* number of bytes allocated to ptr */;
foreach (var rect in ptr.ToStringEnum(elemCount, charSet, offset, memSize))
{
   // iterate while ptr is still pointing to valid memory
}

// Array of zero terminated strings
foreach (var rect in ptr.ToStringEnum(charSet, offset, memSize))
{
   // iterate while ptr is still pointing to valid memory
}

Writing memory

To start, it is important to know that there are multiple allocation schemes in the Windows SDK. Two are supported in .NET: LocalAlloc (using Marshal.AllocHGlobal) and CoTaskMemAlloc (using Marshal.AllocCoTaskMem). Under the hood, since Win8, they both allocate memory from the process heap. Then you have all the heap functions from Kernel32 - HeapCreate/HeapAlloc and others. These are more complex and support locking. On top of these, you have the C/C++ functions too. To accommodate all these, in all the Vanara extensions, you must specify an allocator when creating native memory.

Copy a structure to newly allocated native memory

RECT r = new RECT(5, 5, 20, 20);
IntPtr ptr = r.MarshalToPtr(Marshal.AllocCoTaskMem, out int memSize, 0 /*prefixBytes*/, null /*lock method*/, null /*unlock method*/);
// use the ptr
Marshal.FreeCoTaskMem(ptr);

Copy a sequence (array or IEnumerable) of structures to newly allocated native memory

RECT[] r = new[] { new RECT(5, 5, 20, 20), new RECT(1, 1, 5, 5), new RECT(50, 50, 200, 200) };
IntPtr ptr = r.MarshalToPtr(Marshal.AllocHGlobal, out int memSize, 0 /*prefixBytes*/,
   null /*lock method*/, null /*unlock method*/);
// use the ptr
Marshal.FreeHGlobal(ptr);

Copy a sequence (array or IEnumerable) of strings to newly allocated native memory

string[] s = new[] { "ABCD", "EFGH", "IJKL" };
// **Note** the need to supply the packing method and character set
IntPtr ptr = s.MarshalToPtr(StringListPackMethod.Concatenated, Marshal.AllocHGlobal, out int memSize,
   CharSet.Unicode, 0 /*prefixBytes*/, null /*lock method*/, null /*unlock method*/);
// use the ptr
Marshal.FreeHGlobal(ptr);

Write objects to pre-allocated native memory

Matching all the allocating methods are write-only methods.

const int memSize = 1024;
IntPtr ptr = Marshal.AllocCoTaskMem(memSize);

RECT r = new RECT(5, 5, 20, 20);
ptr.Write(r, 0 /*offset*/, memSize);

RECT[] ra = new[] { new RECT(5, 5, 20, 20), new RECT(1, 1, 5, 5), new RECT(50, 50, 200, 200) };
ptr.Write(ra, 0 /*offset*/, memSize);

string[] s = new[] { "ABCD", "EFGH", "IJKL" };
// **Note** the need to supply the packing method and character set
ptr.Write(StringListPackMethod.Concatenated, CharSet.Ansi, 0 /*offset*/, memSize);