Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

External methods with Span<T> overload need the correct buffer size passed #90

Closed
Schwertregen opened this issue Feb 14, 2021 · 4 comments
Assignees
Labels
bug Something isn't working

Comments

@Schwertregen
Copy link

In Use:

  • .NET 5.0 Console-Application
  • Microsoft.Windows.CsWin32 0.1.319-beta

When adding VirtualQueryEx to NativeMethods.txt the following is produced:

        [DllImport("Kernel32", ExactSpelling = true, SetLastError = true)]
        internal static extern unsafe BOOL ReadProcessMemory(CloseHandleSafeHandle hProcess, [In] void *lpBaseAddress, [Out] void *lpBuffer, nuint nSize, [Out, Optional] nuint*lpNumberOfBytesRead);
        /// <inheritdoc cref = "VirtualQueryEx(CloseHandleSafeHandle, void *, MEMORY_BASIC_INFORMATION*, nuint)"/>
        internal static unsafe nuint VirtualQueryEx(CloseHandleSafeHandle hProcess, void *lpAddress, Span<MEMORY_BASIC_INFORMATION> lpBuffer)
        {
            fixed (MEMORY_BASIC_INFORMATION*lpBufferLocal = lpBuffer)
            {
                return PInvoke.VirtualQueryEx(hProcess, lpAddress, lpBufferLocal, (nuint)lpBuffer.Length);
            }
        }

The overload with Span<MEMORY_BASIC_INFORMATION> is not working as expected.
After executing, the returned int is 0 and the buffer contains only default values.

I am totaly new to P/Invoke, Span and so on, so i had to lookup some stuff and experiment.
I hope my understanding is somewhat correct.

I have manually created an external method and used the types from Microsoft.Windows.Sdk.PInvoke to build this working example:

        //[DllImport("kernel32.dll", SetLastError = true)]
        //internal static extern int VirtualQueryEx(IntPtr hProcess, IntPtr lpAddress, out MEMORY_BASIC_INFORMATION lpBuffer, uint dwLength);

        [DllImport("kernel32.dll", SetLastError = true)]
        internal unsafe static extern int VirtualQueryEx(CloseHandleSafeHandle hProcess, IntPtr lpAddress, MEMORY_BASIC_INFORMATION* lpBuffer, uint dwLength);

        internal unsafe static int VirtualQueryEx(CloseHandleSafeHandle hProcess, IntPtr lpAddress, Span<MEMORY_BASIC_INFORMATION> lpBuffer)
        {
            fixed (MEMORY_BASIC_INFORMATION* lpBufferPointer = lpBuffer)
            {
                return VirtualQueryEx(hProcess, lpAddress, lpBufferPointer, (uint)(Marshal.SizeOf<MEMORY_BASIC_INFORMATION>() * lpBuffer.Length));
            }
        }

The parameter dwLength needs the size of the buffer.

Span.Length returns the number of elements of type T.
So the length will be 1 for Span<MEMORY_BASIC_INFORMATION> span = new MEMORY_BASIC_INFORMATION[1];.

But a single element of type MEMORY_BASIC_INFORMATION does not have the size of 1 byte.
So the actual size of the buffer-size has to be queried Marshal.SizeOf<MEMORY_BASIC_INFORMATION>().
Passing the size of the element type should be sufficient in this case, because VirtualQueryEx will only provide a single element anyway.

Not sure if it is a good pattern in this case, but i have also multiplied the size of the element type with the buffer length.
I am also not sure of other cases, but if there are any cases that handle multiple elements, it might be a good practice to multiply the element size with the buffer length.

@jnm2
Copy link
Contributor

jnm2 commented Feb 14, 2021

I think some APIs need the byte length and others need the element count, and bad information was gathered about which way VirtualQueryEx is (and maybe other APIs).

@AArnott
Copy link
Member

AArnott commented Feb 16, 2021

Great report. I have filed a new bug against the metadata that must first be fixed, then we can update CsWin32 to adjust code gen based on their update.

@AArnott AArnott added bug Something isn't working blocked This issue cannot be fixed without first a change to a dependency and removed blocked This issue cannot be fixed without first a change to a dependency labels Feb 16, 2021
@AArnott AArnott self-assigned this Feb 25, 2021
@AArnott
Copy link
Member

AArnott commented Feb 25, 2021

Ironically, it never came up till now that the 3rd parameter isn't even supposed to be an array. Go figure.
microsoft/win32metadata#284

But you still caught a bug in our handling of length by count vs length in bytes, so I'm finalizing a fix for that.

@AArnott
Copy link
Member

AArnott commented Feb 25, 2021

But our own bug is implicitly fixed by the metadata change, so I'll close this with no further changes.

@AArnott AArnott closed this as completed Feb 25, 2021
AArnott pushed a commit that referenced this issue May 6, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
None yet
Development

No branches or pull requests

3 participants