how can I call WinUsb_WritePipe method in C# without issue - c#

I have an issue when calling the 'WinUsb_WritePipe' method.
At first, I start by call 'CreateFile' method that provides me a handle.
Then I call 'WinUsb_Initialize' method to get the WinUsbHandle.
After that, I retrieve some information about my USB device by calling 'WinUsb_QueryDeviceInformation', 'WinUsb_QueryInterfaceSettings' and 'WinUsb_QueryPipe' without any issue.
My problem appends when I have to call the 'WinUsb_WritePipe' method.
In a first library that I have made in C language, it works correctly.
But I have to switch from C to C# for my new library and the method returns False and the last error that I get is INVALID_PARAMETER.
It seems that comes from overlapped parameter. I can't passed NULL to this parameter as indicated in the specification.
I don't see what I am doing wrong.
I've tried already:
I already try to change the type of Overlapped parameter from IntPtr, to Int to put 0 as for C language call.
I have tried to change IntPtr by NativeOverlapped structure. In a first time with a NULL structure but I have the same issue.
If I gave a initialized structure, I try to call GetOverlappedResult method but it just returns INVALID_PARAMETER so the issue is always present.
I have tried to use unsafe to manage the method as a C calling but the issue is still here.
You can find my code below :
[DllImport("winusb.dll", SetLastError = true)]
internal static extern Boolean WinUsb_WritePipe(IntPtr InterfaceHandle, Byte PipeId, Byte[] Buffer, UInt32 BufferLength, ref UInt32 LengthTransferred, IntPtr Overlapped);
Byte[] SendBuffer = new Byte[3];
SendBuffer[0] = 0x01;
SendBuffer[1] = 0x0D;
SendBuffer[2] = 0x00;
UInt32 BytesToWrite = Convert.ToUInt32(SendBuffer.Length);
UInt32 BytesWritten=0;
IntPtr Handle = DeviceUSB.GetHandle(); //corresponding to the WinUsbHandle
Byte Pipe = DeviceUSB.GetPipe(DeviceUSB.GetSelectedMode()).PipeOutId; // 0x04
Success = WinUsbApiCalls.WinUsb_WritePipe(Handle, Pipe, SendBuffer, SizeToSend, ref SizeSent, IntPtr.Zero);
I expect the return value is set to true and SizeSent to be 3. but the actual returns false with lastError set to INVALID_PARAMETER and SizeSent is 0.

I have tried again with my first declaration of DllImport with UInt32 instead of UInt64. There is no exception of stack imbalance but just an error by getting last error which is INVALID_PARAMETER. I think there is an issue with overlapped param and not with types of size parameters. It seems that it is not supported by writepipe method to put IntPtr.Zero into this param overlapped.

If you are using this API https://learn.microsoft.com/en-us/windows/desktop/api/winusb/nf-winusb-winusb_writepipe you need to change your BufferLength and LengthTransferred parameters from UInt32 to UInt64
C++
BOOL WinUsb_WritePipe(
WINUSB_INTERFACE_HANDLE InterfaceHandle,
UCHAR PipeID,
PUCHAR Buffer,
ULONG BufferLength,
PULONG LengthTransferred,
LPOVERLAPPED Overlapped
);
C#
internal static extern Boolean WinUsb_WritePipe(
IntPtr InterfaceHandle,
Byte PipeId,
Byte[] Buffer,
UInt64 BufferLength,
ref UInt64 LengthTransferred,
IntPtr Overlapped);

Related

Pinvoke: passing a new struct pointer to a function which outs the size of the struct

I have a prototype of the following function: https://learn.microsoft.com/en-us/windows/win32/api/wininet/nf-wininet-GetUrlCacheGroupAttributeA
Which looks like this:
[DllImport("wininet", SetLastError = true, CharSet = CharSet.Auto, EntryPoint = "GetUrlCacheGroupAttributeA", CallingConvention = CallingConvention.StdCall)]
private static extern bool GetUrlCacheGroupAttribute(
long gid,
uint dwFlags,
uint dwAttributes,
IntPtr lpGroupInfo,
ref uint lpcbGroupInfo,
IntPtr lpReserved);
The lpdwGroupInfo might make more sense as out instead of ref but otherwise, I think this is correct.
What I don't understand is how I am expected to pass in a pointer for the INTERNET_CACHE_GROUP_INFO struct lpGroupInfo(which I have also defined/prototyped along with this function). I know how everything else is passed in, just this pointer confuses me.
The function states for the pointer the following:
lpGroupInfo - Pointer to an INTERNET_CACHE_GROUP_INFO structure that receives the requested information.
lpcbGroupInfo - Pointer to a variable that contains the size of the lpGroupInfo buffer. When the function returns, the variable contains the number of bytes copied to the buffer, or the required size of the buffer, in bytes.
Do I need to allocate memory via Marshal.AllocHGlobal or something? This seems to suggest I will only get the size of the struct after passing it in but how can I pass it in if it isn't first defined? I'm not at all clear on how to create the initial pointer and then how I am expected to Marshal.PtrToStructure it.
Simon's comment answered my question:
for functions where a struct buffer pointer is passed in as a parameter(intended to be modified by the function) and where it also outs the size of the buffer needed when it fails due to insufficient buffer size, the proper way to use these functions is to pass in these values initialized to the default values(IntPtr.Zero and 0), then when the function fails(you can confirm the error was due to buffer size), it will have set the buffer size required and you can call the function again after allocating memory to the pointer.
Here is a sample snippet:
private static void ClearUrlCacheGroups()
{
IntPtr enumHandle = FindFirstUrlCacheGroup(0, CACHEGROUP_SEARCH_ALL, IntPtr.Zero, 0, out long lpGroupId, IntPtr.Zero);
if (enumHandle != IntPtr.Zero)
{
bool foundNextGroup;
bool isDeleted;
uint cacheGroupInfoBufferSize = 0;
IntPtr cacheGroupInfoBuffer = Marshal.AllocHGlobal((IntPtr)cacheGroupInfoBufferSize);
do
{
bool getAttributeSucceeded = GetUrlCacheGroupAttribute(lpGroupId, 0, CACHEGROUP_ATTRIBUTE_GET_ALL, cacheGroupInfoBuffer, ref cacheGroupInfoBufferSize, IntPtr.Zero);
if (!getAttributeSucceeded && Marshal.GetLastWin32Error() == ERROR_INSUFFICIENT_BUFFER)
{
cacheGroupInfoBuffer = Marshal.ReAllocHGlobal(cacheGroupInfoBuffer, (IntPtr)cacheGroupInfoBufferSize);
getAttributeSucceeded = GetUrlCacheGroupAttribute(lpGroupId, 0, CACHEGROUP_ATTRIBUTE_GET_ALL, cacheGroupInfoBuffer, ref cacheGroupInfoBufferSize, IntPtr.Zero);
}
if (getAttributeSucceeded)
{
INTERNET_CACHE_GROUP_INFOA internetCacheEntry = (INTERNET_CACHE_GROUP_INFOA)Marshal.PtrToStructure(cacheGroupInfoBuffer, typeof(INTERNET_CACHE_GROUP_INFOA));
}
isDeleted = DeleteUrlCacheGroup(lpGroupId, CACHEGROUP_FLAG_FLUSHURL_ONDELETE, IntPtr.Zero);
foundNextGroup = FindNextUrlCacheGroup(enumHandle, ref lpGroupId, IntPtr.Zero);
}
while (foundNextGroup);
Marshal.FreeHGlobal(cacheGroupInfoBuffer);
}
}

Pinvoke cdecl convention with char**

In summary:
I`m trying to use a C++ dll with cdecl calling convention all ran fine unless i get to this method signature:
int SaveToBuffer( char **buf, int *buf_size );
from what i have read i should use it like this:
[DllImport("entry.dll",
CallingConvention = CallingConvention.Cdecl,
EntryPoint = "SaveToBuffer")]
private static int SaveToBuffer( ref sbyte[] buf, ref int buf_size );
This does not work if this function is called from C# program crashes.
I suppose this is related to Cdecl calling model and should use Marshal.AllocHGlobal(value),
I can`t imagine how should it be done correct.
I also tryed this:
[DllImport("entry.dll",
CallingConvention = CallingConvention.Cdecl,
EntryPoint = "SaveToBuffer")]
private static int SaveToBuffer( IntPtr buf, ref int buf_size );
And then alocate enough memory
IntPtr data=Marshal.AllocHGlobal(128000);
int bufSize=128000;
var sCode=SaveToBuffer(data,bufSize ); /* value of scode idicate succses*/
Calling this way i get return value from SaveToBuffer indicating function succseeded but: bufSize returns to 0 and how should i read my data from IntPtr.
I`m completly stuck on this.
This is not an issue with the calling convention. The problem is in the buffer handling.
There's really only one sensible way to interpret the C++ argument types and the apparent intent to return an array of bytes. That is that the buffer is allocated and populated by the callee, and its address returned in buf. The buffer length is returned in buf_size.
With these semantics the function arguments cannot be marshalled automatically and you'll have to do it manually:
[DllImport("entry.dll", CallingConvention = CallingConvention.Cdecl)]
private static int SaveToBuffer(out IntPtr buf, out int buf_size);
Call like this
IntPtr buf;
int buf_size;
int retval SaveToBuffer(out buf, out buf_size);
// check retval
Then copy to byte array like this:
byte[] buffer = new byte[buf_size];
Marshal.Copy(buf, buffer, 0, buf_size);
The DLL will also need to export a function to deallocate the unmanaged buffer.

how to set a ref int as null in SetFilePointer(file.SafeFileHandle, moveDistance, ref moveDistanceHighBits, EMoveMethod.Begin)

I tried the solution proposed in Using SetFilePointer in C# has unblanced the stack to move file's pointer and it worked. The only problem was that when reading a device rather than a file, setfilepointer places the pointer at the beginning of the sector containing the address required, instead of placing it at the required address itself. Don't know why.
But my question is another. According to the docs i've read if you don't need to use moveDistanceHighBits because only the low order bytes are needed to address your wanted offset, you have to set moveDistanteHighBits to null. But i don't know how to do this.
Could someone help please?
Don't special-case this just because the winapi function does. Unlike C compilers that were in use 25 years ago, C# supports 64-bit integers well. And it is too risky. You want to write a wrapper method anyway so you can properly throw an exception when the winapi function fails. Never skip that. Do it like this:
using System;
using System.IO;
using System.Runtime.InteropServices;
using Microsoft.Win32.SafeHandles;
public class NativeMethods {
public static void SetFilePointer(SafeFileHandle hFile, long dist, SeekOrigin method) {
int lodist = (int)dist;
int hidist = (int)(dist >> 32);
int retval = SetFilePointer(hFile, lodist, ref hidist, method);
if (retval == -1) throw new System.ComponentModel.Win32Exception();
}
[DllImport("Kernel32.dll", SetLastError = true)]
private static extern int SetFilePointer(SafeFileHandle hFile,
int distlo, ref int disthi, SeekOrigin method);
}
}
Do note that you don't need pinvoke it at all. You want to use one of the FileStream constructors that takes an IntPtr or SafeHandle. This one for example. Now you can simply use FileStream.Seek().
You can change the PInvoke signature to use an IntPtr instead and then you can pass IntPtr.Zero when you want to pass NULL. The new signature would look like this:
[DllImport("kernel32.dll", EntryPoint="SetFilePointer", SetLastError=true)]
static extern uint SetFilePointer(
[In] Microsoft.Win32.SafeHandles.SafeFileHandle hFile,
[In] int lDistanceToMove,
[In] IntPtr lpDistanceToMoveHigh,
[In] EMoveMethod dwMoveMethod);
This works because in both definitions you're passing a pointer but using an IntPtr lets you control the actual pointer value. When you use ref, you have to have an exisiting local variable and the compiler will take its address.
If you need to pass/receive a value back (other than NULL) from the SetFilePointer call, you can do it this way:
int nValue = ...; // Value to pass for lpDistanceToMoveHigh
IntPtr pInt = IntPtr.Zero;
try
{
pInt = Marshal.AllocHGlobal ( sizeof ( int ) );
Marshal.WriteInt32 ( pInt, nValue );
// Call SetFilePointer(); upon successful return,
// you can read the value returned for lpDistanceToMoveHigh
nValue = Marshal.ReadInt32 ( pInt );
// ...
}
finally
{
if ( pInt != IntPtr.Zero )
Marshal.FreeHGlobal ( pInt );
}

Using SetFilePointer in C# has unblanced the stack

Ok, I am using the SetFilePointer function in C# with .NET 4.0. Below are the dllimports I used to call this function:
[DllImport("Kernel32.dll", SetLastError = true, CharSet = CharSet.Auto, CallingConvention = CallingConvention.Cdecl)]
static extern uint SetFilePointer([In] SafeFileHandle hFile, [In] long lDistanceToMove, [Out] out int lpDistanceToMoveHigh, [In] EMoveMethod dwMoveMethod);
Whenever I run the code that uses the SetFilePointer function in the debugger I get this exception:
PInvokeStackImbalance was detected
Message: A call to PInvoke function 'MyDiskStuff!MyDiskStuff.HardDisk::SetFilePointer' has unbalanced the stack. This is likely because the managed PInvoke signature does not match the unmanaged target signature. Check that the calling convention and parameters of the PInvoke signature match the target unmanaged signature.
Whenever I run the same code outside of the debugger I do not get the above exception.
Below is the code I am using to make the calls to SetFilePointer:
public enum EMoveMethod : uint
{
Begin = 0,
Current = 1,
End = 2
}
uint retvalUint = SetFilePointer(mySafeFileHandle, myLong, out myInt, EMoveMethod.Begin);
Is there something wrong with my dllimport signatures?
Your P/Invoke signature is a little off:
Here's the Win32 definition:
DWORD WINAPI SetFilePointer(
_In_ HANDLE hFile,
_In_ LONG lDistanceToMove,
_Inout_opt_ PLONG lpDistanceToMoveHigh,
_In_ DWORD dwMoveMethod
);
And here's the P/Invoke with your enum specified:
[DllImport("kernel32.dll", EntryPoint="SetFilePointer")]
static extern uint SetFilePointer(
[In] Microsoft.Win32.SafeHandles.SafeFileHandle hFile,
[In] int lDistanceToMove,
[In, Out] ref int lpDistanceToMoveHigh,
[In] EMoveMethod dwMoveMethod) ;
EDIT: Oh, and some test code:
var text = "Here is some text to put in the test file";
File.WriteAllText(#"c:\temp\test.txt", text);
var file = File.Open(#"c:\temp\test.txt", FileMode.OpenOrCreate);
int moveDistance = 10;
int moveDistanceHighBits = 0;
uint retvalUint = SetFilePointer(file.SafeFileHandle, moveDistance, ref moveDistanceHighBits, EMoveMethod.Begin);
Debug.Assert(Encoding.ASCII.GetBytes(text)[moveDistance] == file.ReadByte());
Also note from the docs:
lDistanceToMove [in]
The low order 32-bits of a signed value that specifies the number of bytes to move the file pointer.
If lpDistanceToMoveHigh is not NULL, lpDistanceToMoveHigh and lDistanceToMove form a single 64-bit signed value that specifies the distance to move.
If lpDistanceToMoveHigh is NULL, lDistanceToMove is a 32-bit signed value. A positive value for lDistanceToMove moves the file pointer forward in the file, and a negative value moves the file pointer back.
lpDistanceToMoveHigh [in, out, optional]
A pointer to the high order 32-bits of the signed 64-bit distance to move.
If you do not need the high order 32-bits, this pointer must be set to NULL.
When not NULL, this parameter also receives the high order DWORD of the new value of the file pointer. For more information, see the Remarks section in this topic.
Likely.
pinvoke.net lets CallingConvention default to StdCall (instead of your Cdecl setting) and since SetFilePointer is declared as WINAPI (which is __stdcall). Incorrect calling convention will damage your stack.

AccessViolationException when using FileRead API

I am using the FileRead API.
I used Windows 7 x64 and my code worked good and correct.
Now I installed a new Windows 7 x86 and VS2008 teamsuit and .NET 2, 3+SP1+SP2, 3.5, 3.5.1.
I run my code as Administrator but still encounter the follwoing error:
AccessViolationException(Attempted to read or write protected memory. This is often an indication that other memory is corrupt.)
int nread = 0;
uint handle;
byte[] buff = new byte[1024];
string driveRoot = string.Concat("\\\\.\\", driveLetter);
uint hRoot = CreateFile(driveRoot,
GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_READ | FILE_SHARE_WRITE,
IntPtr.Zero,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL,
IntPtr.Zero);
if (hRoot != -1)
handle = ReadFile(hRoot, buff, 1024, nread, new System.Threading.NativeOverlapped());
While I'm no C# guru, it appears to me that you're invoking ReadFile() with wrong parameters.
The 4th parameter must be a pointer to an integer that will receive the number of bytes read. You supply the integer itself (nread), not its address (&nread).
And unless you want asynchronous file I/O, the last parameter to ReadFile() must be a NULL pointer (or just 0).
See this example on MSDN.
I suspect that the main problem with your code is that you are requesting overlapped I/O but supplying a buffer that ceases to exist when ReadFile returns. It works on some systems an not others because the system decides whether or not to perform the operation asynchronously, and it may choose not to do async on one system and choose differently on another.
I'm sure you don't want overlapped I/O so you should simply pass NULL to the final parameter of ReadFile.
On the other hand, perhaps your code isn't working at all on the x64 system and never gets as far as an AV. Your handle types are mis-declared as 32 bit integers.
There are many other minor problems with your code. Here's an edited version of the code that corrects these errors. The P/invoke signatures were taken from pinvoke.net.
[DllImport("kernel32.dll", SetLastError = true)]
public static extern IntPtr CreateFile(
string lpFileName,
uint dwDesiredAccess,
uint dwShareMode,
IntPtr SecurityAttributes,
uint dwCreationDisposition,
uint dwFlagsAndAttributes,
IntPtr hTemplateFile
);
[DllImport("kernel32.dll", SetLastError = true)]
static extern bool ReadFile(
IntPtr hFile,
[Out] byte[] lpBuffer,
uint nNumberOfBytesToRead,
out uint lpNumberOfBytesRead,
IntPtr lpOverlapped
);
static void Main(string[] args)
{
string physicalDrive = #"\\.\PhysicalDrive0";
IntPtr hFile = CreateFile(
physicalDrive,
GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_READ | FILE_SHARE_WRITE,
IntPtr.Zero,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL,
IntPtr.Zero
);
if (hFile.ToInt64() != INVALID_HANDLE_VALUE)
{
byte[] buff = new byte[1024];
uint nread;
if (ReadFile(hFile, buff, (uint)buff.Length, out nread, IntPtr.Zero))
Console.WriteLine("Read successful");
}
}
To summarise the errors in your code:
Incorrect use of 32 bit integers to store handles.
Your P/invoke declaration of ReadFile declares the lpNumberOfBytesRead incorrectly.
ReadFile does not return a handle, it returns a boolean indicating success of the function call.
Use of overlapped I/O which you do not want, and which cannot work with a marshalled byte[] buffer.
You must never call GetLastError from managed code (you did so in code shown in a comment). Instead call Marshal.GetLastWin32Error. The reasons are explained in the documentation for that method.

Categories

Resources