Closed skst closed 1 year ago
The problem is CsWin32 doesn't recognize this as a variable length array, so it just uses a fixed 1-length array.
See #387, which tracks improving the usability of this case.
As a workaround, assuming the struct is initialized elsewhere, you can access the full list like this:
JOBOBJECT_BASIC_PROCESS_ID_LIST list;
unsafe
{
Span<nuint> pids = new(&list.ProcessIdList._0, (int)list.NumberOfProcessIdsInList);
for (int i = 0; i < pids.Length; i++)
{
Console.WriteLine(pids[i]);
}
}
But if you need to initialize the list, you'll have to allocate enough memory for the struct, and then pin the struct at that location yourself. I can provide sample code for that if you need.
Yes, QueryInformationJobObject
requires the caller to allocate the memory, and I have been unsuccessful at coercing a block of memory to be a JOBOBJECT_BASIC_PROCESS_ID_LIST
. If you could provide that sample code, I would appreciate it.
I got this far, which seems to work, but might not be the most efficient.
nint nativeMemory = Marshal.AllocHGlobal(nData);
JOBOBJECT_BASIC_PROCESS_ID_LIST* pidList = (JOBOBJECT_BASIC_PROCESS_ID_LIST*)nativeMemory.ToPointer();
PInvoke.QueryInformationJobObject(hJob, JOBOBJECTINFOCLASS.JobObjectBasicProcessIdList, pidList, (uint)nData, &returnLength);
JOBOBJECT_BASIC_PROCESS_ID_LIST idList = *pidList;
var ids = idList.ProcessIdList.AsReadOnlySpan();
foreach (var item in ids)
{
}
JOBOBJECT_BASIC_PROCESS_ID_LIST idList = *pidList;
var ids = idList.ProcessIdList.AsReadOnlySpan();
This is broken for two reasons: AsReadOnlySpan()
will only give you the first item. And secondly, because your *pidList
expression will copy the struct from native memory onto your stack, without all the elements after the first one.
I'm preparing a sample of what I believe should work.
using Windows.Win32;
using Windows.Win32.Foundation;
using Windows.Win32.System.JobObjects;
unsafe
{
HANDLE hJob = default;
const int MaxProcessCount = 4096; // TODO: pick a good number here
int MemorySize = sizeof(JOBOBJECT_BASIC_PROCESS_ID_LIST) + MaxProcessCount * sizeof(nuint);
fixed (byte* pBytes = new byte[MemorySize])
{
JOBOBJECT_BASIC_PROCESS_ID_LIST* pList = (JOBOBJECT_BASIC_PROCESS_ID_LIST*)pBytes;
uint returnLength = 0;
PInvoke.QueryInformationJobObject(hJob, JOBOBJECTINFOCLASS.JobObjectBasicProcessIdList, pList, (uint)MemorySize, &returnLength);
Span<nuint> pids = new(&pList->ProcessIdList, (int)pList->NumberOfProcessIdsInList);
for (int i = 0; i < pids.Length; i++)
{
Console.WriteLine(pids[i]);
}
}
}
I'm using memory allocated from the GC heap so that you don't have to worry about a try/finally
block to ensure you don't leak unmanaged memory.
Thank you very much, Andrew. I'll give that a go.
Update: Yep, works great. Thanks again!
Actual behavior
Full disclosure: I am probably missing something or do not know how to do something (although, I have spent the day reading and researching and testing).
When calling an API and passing a structure which contains a variable array, CsWin32 generates a
struct
that has a member of type__nuint_1
. However, I see no way to set the__nuint_1
'sSpanLength
so that it allocates a specific number of elements.Expected behavior
It seems that
__nuint_1
should have a way to initialize it to have a user-specified number of elements, since we have to pass the size of the memory block to the function.Repro steps
NativeMethods.txt
content:Generated:
Any of your own code that should be shared?
Context
0.2.188-beta
.NET 7
LangVersion
(if explicitly set by project):latest (C# 11)