-
Notifications
You must be signed in to change notification settings - Fork 196
Manual native memory extensions
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:
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);
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);
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
}
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
}
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.
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);
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);
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);
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);