Access violation when calling a DLL function
-
יום רביעי 01 יוני 2011 10:57
I'm a newbie to VB and interop, so please be patient - it's probably something silly but not easy to find in the documentation.
I'm trying to call the DeviceIoControl function from kernel32. Thanks to Paul Clement who pointed me towards the PInvoke helper, I'm calling it, and it's doing what I asked it to (open the CD drawer), but it hen crashes with an access violation. Surrounding it with try/catch doesn't work - presumably that doesn't work for unmanaged code. Here's copy of the code (edited down a bit to make it readable).
If I put a breakpoint before the call to DeviceIoControl it's OK up to there. Do a single step, and the CD drawer opens and there's the Access Violation.
What am I doing wrong please?
Imports System.IO, System.Runtime.InteropServices, Microsoft.Win32.SafeHandles
Public Class Form1
<StructLayoutAttribute(LayoutKind.Sequential)> _
Public Structure CHANGER_PRODUCT_DATA
'''BYTE[8]
<MarshalAsAttribute(UnmanagedType.ByValArray, SizeConst:=8, ArraySubType:=UnmanagedType.I1)> _
Public VendorId() As Byte
'''BYTE[16]
<MarshalAsAttribute(UnmanagedType.ByValArray, SizeConst:=16, ArraySubType:=UnmanagedType.I1)> _
Public ProductId() As Byte
'''BYTE[4]
<MarshalAsAttribute(UnmanagedType.ByValArray, SizeConst:=4, ArraySubType:=UnmanagedType.I1)> _
Public Revision() As Byte
'''BYTE[32]
<MarshalAsAttribute(UnmanagedType.ByValArray, SizeConst:=32, ArraySubType:=UnmanagedType.I1)> _
Public SerialNumber() As Byte
'''BYTE->unsigned char
Public DeviceType As Byte
End Structure
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
Dim da As DriveInfo() = DriveInfo.GetDrives()
Dim di As DriveInfo
Dim hand As SafeFileHandle
Dim name As String
Dim dwda As UInt32
Dim errorcode As Integer
Dim controlResult As Boolean
' Dim cpd As CHANGER_PRODUCT_DATA
Dim controlBytes As Integer
Dim ctrlEjectMedia As UInteger = ControlCode(2, 514, 0, 1)
' Dim gchIn As GCHandle
' Dim gchOut As GCHandle
ResultBox.Text = "Drive list:" & vbCrLf
For Each di In da
If di.DriveType = DriveType.CDRom Then
ResultBox.Text = ResultBox.Text & "Name: " & di.Name & vbCrLf
ResultBox.Text = ResultBox.Text & "Type: " & di.DriveType & vbCrLf
ResultBox.Text = ResultBox.Text & "Ready: " & di.IsReady & vbCrLf
If di.IsReady Then
ResultBox.Text = ResultBox.Text & "File system: " & di.DriveFormat & vbCrLf
End If
ResultBox.Text = ResultBox.Text & "CD-ROM" & vbCrLf
name = "\\.\" & di.Name.Substring(0, 2)
ResultBox.Text = ResultBox.Text & name & vbCrLf
dwda = &H80000000UL
hand = Kernel.CreateFile(name, dwda, 0, Nothing, 3, 128, Nothing)' at this point the code seems to be working
errorcode = Err.LastDllError
ResultBox.Text = ResultBox.Text & "Error code: " & errorcode & vbCrLf
ResultBox.Text = ResultBox.Text & "Handle: " & hand.DangerousGetHandle.ToString & vbCrLf
ResultBox.Text = ResultBox.Text & "Code: " & ctrlEjectMedia & vbCrLf
' gchOut = GCHandle.Alloc(cpd)
' eject media'****** This is the call which fails.
Try
controlResult = Kernel.DeviceIoControl(hand.DangerousGetHandle,
ctrlEjectMedia,
Nothing, 0,
Nothing, 0,
controlBytes,
Nothing)
Catch ex As AccessViolationException
ResultBox.Text = ResultBox.Text & "Exception: " & ex.Message & vbCrLf
End Try
ResultBox.Text = ResultBox.Text & "DeviceIoControl return: " & controlResult & vbCrLf
errorcode = Err.LastDllError
ResultBox.Text = ResultBox.Text & "Error code: " & errorcode & vbCrLf
End If
ResultBox.Text = ResultBox.Text & vbCrLf
Next
End Sub
Private Function ControlCode(ByVal DeviceType As UInteger, _
ByVal FuncCode As UInteger, _
ByVal Method As UInteger, _
ByVal Access As UInteger) As UInteger
ControlCode = DeviceType << 16 Or Access << 14 Or FuncCode << 2 Or Method
End Function
End Class
Public Class Kernel
<DllImport("kernel32.dll", SetLastError:=True, CharSet:=CharSet.Unicode)> _
Public Shared Function CreateFile(ByVal lpFileName As String, _
ByVal dwDesiredAccess As UInteger, _
ByVal dwShareMode As UInteger, _
ByVal lpSecurityAttributes As IntPtr, _
ByVal dwCreationDisposition As UInteger, _
ByVal dwFlagsAndAttributes As UInteger, _
ByVal hTemplateFile As IntPtr) As SafeFileHandle
End Function
'****** the code below was copied from the PInvoke helper.<StructLayoutAttribute(LayoutKind.Sequential)> _
Public Structure OVERLAPPED
'''ULONG_PTR->unsigned int
Public Internal As UInteger
'''ULONG_PTR->unsigned int
Public InternalHigh As UInteger
'''Anonymous_7416d31a_1ce9_4e50_b1e1_0f2ad25c0196
Public Union1 As Anonymous_7416d31a_1ce9_4e50_b1e1_0f2ad25c0196
'''HANDLE->void*
Public hEvent As System.IntPtr
End Structure
<StructLayoutAttribute(LayoutKind.Explicit)> _
Public Structure Anonymous_7416d31a_1ce9_4e50_b1e1_0f2ad25c0196
'''Anonymous_ac6e4301_4438_458f_96dd_e86faeeca2a6
<FieldOffsetAttribute(0)> _
Public Struct1 As Anonymous_ac6e4301_4438_458f_96dd_e86faeeca2a6
'''PVOID->void*
<FieldOffsetAttribute(0)> _
Public Pointer As System.IntPtr
End Structure
<StructLayoutAttribute(LayoutKind.Sequential)> _
Public Structure Anonymous_ac6e4301_4438_458f_96dd_e86faeeca2a6
'''DWORD->unsigned int
Public Offset As UInteger
'''DWORD->unsigned int
Public OffsetHigh As UInteger
End Structure
' Partial Public Class NativeMethods
' Public Class Kernel1
' < InAttribute()>
'''Return Type: BOOL->int
'''hDevice: HANDLE->void*
'''dwIoControlCode: DWORD->unsigned int
'''lpInBuffer: LPVOID->void*
'''nInBufferSize: DWORD->unsigned int
'''lpOutBuffer: LPVOID->void*
'''nOutBufferSize: DWORD->unsigned int
'''lpBytesReturned: LPDWORD->DWORD*
'''lpOverlapped: LPOVERLAPPED->_OVERLAPPED*
<DllImportAttribute("kernel32.dll", EntryPoint:="DeviceIoControl")> _
Public Shared Function DeviceIoControl(<InAttribute()> ByVal hDevice As System.IntPtr, _
ByVal dwIoControlCode As UInteger, _
<InAttribute()> ByVal lpInBuffer As System.IntPtr, _
ByVal nInBufferSize As UInteger, _
ByVal lpOutBuffer As System.IntPtr, _
ByVal nOutBufferSize As UInteger, _
ByVal lpBytesReturned As System.IntPtr, _
ByVal lpOverlapped As System.IntPtr) _
As <MarshalAsAttribute(UnmanagedType.Bool)> Boolean
End Function
'End Class
End Class
Peter- נערך על-ידי ptoye יום רביעי 01 יוני 2011 11:10 tidy up code a bit
כל התגובות
-
יום שישי 03 יוני 2011 19:05
I suspect you may be trampling some parts of memory and that may be causing the crash. I noticed that there is a Visual Basic example of this API function at pinvoke.net. You might want to take a glance to make sure that all the parameters are being set up properly:
http://pinvoke.net/default.aspx/kernel32/DeviceIoControl.html
Paul ~~~~ Microsoft MVP (Visual Basic)- סומן כתשובה על-ידי Liliane TengModerator יום ראשון 19 יוני 2011 07:35
- סימון כתשובה בוטל על-ידי ptoye יום שני 08 אוגוסט 2011 13:44
-
שבת 04 יוני 2011 18:04
I agree. I suspect that the problem is that one of the parameters isn't as optional as Microsoft say it is. Given that it did actually open the CD drawer for me, it's probably lpOutBuffer.
But this gets me back to the question from my previous thread: this is an IntPtr and I don't know how to set it up in VB as there doesn't seem to be an AddressOf operator in the language for value variables. Do I have to pin it myself or should PInvoke do it for me? I can't see any useful code in the various examples (which are mostly in C# anyway). This is where I need the help!
Peter -
יום ראשון 05 יוני 2011 11:37
BytesReturned should be a ByRef UInteger or Integer.
You are trying to do it the hard way.
In C it's declared so that the caller passes the address of an integer. The function can then alter the value at the address, and on return the caller can read the memory to see what value is there. In VB.Net the equivalent is to use ByRef.
Option Strict On Option Explicit On Imports System.Runtime.InteropServices Imports System.IO Imports Microsoft.Win32.SafeHandles Imports System.Security Public Class Form1 <SuppressUnmanagedCodeSecurity()> _ Private Class NativeMethods <DllImportAttribute("kernel32.dll")> _ Public Shared Function DeviceIoControl(ByVal deviceHandle As SafeFileHandle, _ ByVal ioControlCode As UInteger, _ ByVal inBuffer As IntPtr, _ ByVal inBufferSize As Integer, _ ByVal outBuffer As IntPtr, _ ByVal outBufferSize As Integer, _ <Out()> ByRef bytesReturned As Integer, _ ByVal overlapped As IntPtr) As Boolean End Function <DllImport("kernel32.dll", SetLastError:=True, CharSet:=CharSet.Unicode)> _ Public Shared Function CreateFile(ByVal lpFileName As String, _ ByVal dwDesiredAccess As UInteger, _ ByVal dwShareMode As UInteger, _ ByVal lpSecurityAttributes As IntPtr, _ ByVal dwCreationDisposition As UInteger, _ ByVal dwFlagsAndAttributes As UInteger, _ ByVal hTemplateFile As IntPtr) As SafeFileHandle End Function End Class Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click Const ioCodeEjectMedia As UInteger = &H24808UI ' might as well pre-calculate them rather than matching C For Each di As DriveInfo In DriveInfo.GetDrives() If di.DriveType = DriveType.CDRom Then Dim driveName As String = "\\.\" & di.Name.Substring(0, 2) Using deviceHandle As SafeFileHandle = NativeMethods.CreateFile(driveName, &H80000000UI, 0, IntPtr.Zero, 3, 128, IntPtr.Zero) Dim bytesReturned As Integer Dim controlResult As Boolean = NativeMethods.DeviceIoControl(deviceHandle, ioCodeEjectMedia, IntPtr.Zero, 0, IntPtr.Zero, 0, bytesReturned, IntPtr.Zero) End Using End If Next End Sub End Class
You could do it the way you tried - by sending a ByVal IntPtr - but it's long-winded and unnecessary.
The pointer would have to point to a 4 byte blob of memory. You could allocate it directly like this:Using deviceHandle As SafeFileHandle = NativeMethods.CreateFile(driveName, &H80000000UI, 0, IntPtr.Zero, 3, 128, IntPtr.Zero) Dim pBlob As IntPtr Try pBlob = Marshal.AllocHGlobal(4) Dim controlResult As Boolean = NativeMethods.DeviceIoControl(deviceHandle, ioCodeEjectMedia, IntPtr.Zero, 0, IntPtr.Zero, 0, pBlob, IntPtr.Zero) Dim bytesReturned As Integer = Marshal.ReadInt32(pBlob) Finally Marshal.FreeHGlobal(pBlob) End Try End Using
Or you could pin an integer like this:
Using deviceHandle As SafeFileHandle = NativeMethods.CreateFile(driveName, &H80000000UI, 0, IntPtr.Zero, 3, 128, IntPtr.Zero) Dim bytesReturned As Integer Dim gch As GCHandle Try gch = GCHandle.Alloc(bytesReturned, GCHandleType.Pinned) Dim controlResult As Boolean = NativeMethods.DeviceIoControl(deviceHandle, ioCodeEjectMedia, IntPtr.Zero, 0, IntPtr.Zero, 0, gch.AddrOfPinnedObject, IntPtr.Zero) Finally gch.Free() End Try End Using
Note:
You were declaring a SafeFileHandle, but not really using it the right way. The benefit of a SafeHandle over an IntPtr is that you guarantee the handle will be closed in exceptional cirucumstances. You should be calling Dispose to make sure it is closed. A Using block will call dispose when it exits, or on exception. You should also declare DeviceIOControl to take the SafeHandle rather than IntPtr - that way you avoid having to read the handle with DangerousGetHandle.
Use UI to declare a UInteger constant, not UL which is a Long (64 bits).
For the IO Control codes you can declare them as constants - stick them in an enum if you are using a lot of them. I know C uses the CTL_CODE macro, but it's a bit long-winded.
- סומן כתשובה על-ידי Liliane TengModerator יום ראשון 19 יוני 2011 07:35
- סימון כתשובה בוטל על-ידי ptoye יום שני 08 אוגוסט 2011 13:43
-
יום שני 06 יוני 2011 09:33
Thanks very much. I'm not trying to do it the hard way - I'm trying to do it any way! Paul pointed me towards the PInvoke Helper, which used the ByVal construction.
I've not had time to read through your comments in detail - certainly changing it to ByRef gets rid of the crash.I'm well aware of how C passes its parameters, but my problem is in the mismatch between the managed VB code and the C requirements.The online documentation is very fragmented and hard to read, especially when you don't know exactly what you're looking for. And by the time a book (which you can flick through to find what you want) has been published it's already out of date; a major problem with the IT field.
Again, I used a SafeHandle because that's the only way in which I could get it to match with the PInvoke helper's parameter list. So the helper doesn't really seem to give the best results in your opinion. I think I agree, but I'm trying to do a job, not learn the total ins and outs of how to drive a language, so I use whatever helpful tools are available.
On the IO Control codes I'm using the function to avoid arithmetic. OK, I can make it a constant, but that's surely not a major issue. What I don't want to have to do is lots of bit-shifting on paper which will certainly lead to a mistake.
But now I need an answer to my next question: when I need to pass a structure into the function (for doing more complicated things than just opening the drawer, which doesn't need parameters), how do I get the reference to the structure into an IntPtr? The problem with DeviceIoControl is that it's horrendously polymorphic: each control function uses a different structure for input and output. In C it's easy - just a *void, but AFAIK this isn't available in VB. I suppose that one way would be to have a different version of DeviceIoControl for each control function (and hence input & output structure) and use ByRef. Yuk!
Peter -
יום שני 06 יוני 2011 11:37
It's much the same. You can overload DeviceIOControl with the different structure definitions - passing them ByRef.
Or you can use a ByVal IntPtr, create a blob of memory with Marshal.AllocHGlobal and use Marshal.PtrToStructure and Marshal.StructureToPtr to copy the structure in and out of the memory. Or you can use a GcHandle to pin the structure in memory and use the AddressOfPinnedObject property to get it's address.
I've used DeviceIOControl to send commands as described in the docs at www.t10.org to cd devices. It's hard to get it right.
- סומן כתשובה על-ידי Liliane TengModerator יום ראשון 19 יוני 2011 07:36
- סימון כתשובה בוטל על-ידי ptoye יום שני 08 אוגוסט 2011 13:43
-
יום שני 06 יוני 2011 21:17
I'm slooowly getting there I think with your and Paul's help. One next question though: DeviceIoControl needs the size of the buffers you're passing into it. Is there a Sizeof() in VB (I've not managed to find it in the language spec)? Or do I just have to add up the byte counts of the various components? And then get it wrong...
I couldn't work out the www.t10.org site - whereabouts are the code samples? It ooks like more of an interface standard than a coding one.
Peter -
יום חמישי 16 יוני 2011 08:58מנחה דיון
Hi ptoye,
There is no sizeof() operator in VB.NET. There is System.Runtime.InteropServices.Marshal.Sizeof which returns the unmanaged size of an object in bytes. Useful only for P/Invoke calls to unmanaged code. It is not the size of the managed object itself. As far as I know, the closest you can come is to iterate over adding up the size of each object.
Have a nice day.
Best regards
Liliane Teng [MSFT]
MSDN Community Support | Feedback to us
Get or Request Code Sample from Microsoft
Please remember to mark the replies as answers if they help and unmark them if they provide no help.

- סומן כתשובה על-ידי Liliane TengModerator יום ראשון 19 יוני 2011 07:36
- סימון כתשובה בוטל על-ידי ptoye יום שני 08 אוגוסט 2011 13:43
-
יום חמישי 16 יוני 2011 09:25
Thanks Lilian. I'll try it out, as that's what I'm doing. THe unmanaged function needs to know the size of the buffer I'm giving it, so the function should work.
I won't be able to report back for some time, as a higher-priority project has come up (organising my tax - yuk).
Peter -
יום ראשון 19 יוני 2011 07:38מנחה דיון
Hi ptoye,
I closed this thread by marking above replies as answer. When you come back in future, if you need further assistance, please feel free to follow up.
Have a nice day.
Best regards
Liliane Teng [MSFT]
MSDN Community Support | Feedback to us
Get or Request Code Sample from Microsoft
Please remember to mark the replies as answers if they help and unmark them if they provide no help.

-
יום שני 20 יוני 2011 08:56Thanks Liliane. I was going to do it today, but you beat me to it! And thanks also to Jo0ls and Paul.
Peter -
יום שישי 22 יולי 2011 11:50
After some delay (been offline for a bit), I'm still having problems, but can't work out where they are.
The DeviceIOControl function is incredibly polymorphic - every different control code parameter needs a different set of input and output data structures which is easy in C (if unsafe) but hard in any type-safe language.
But also, it's meant to report errors via Err.LastDllError. And it doesn't seem to. The function call returns false (indicating an error), but Err.LastDllError is always zero (which I gather means success). And no bytes are returned in the output data structure. I've checked that the lengths of the input and output data structures are correct (and the function is meant to tell you if they aren't).
I've tried every combination of ByRef and ByVal, and also adding InAttribute() and OutAttribute() tp the relevant parameters, but the same result occurs.
However, when I call the function with a control code which doesn't need any input or output data (so I put Nothing in the relevant parameters) it works OK.
So now I'm completely stuck - it is a failure in the calling sequence or a bug in the function? Any ideas welcome!
I've unmarked the answers (sorry Liliane) so that this now comes up as a question again.
Here are a few snippets (calling the function, defining it and the output) in case they help.
Try controlResult = Kernel.DeviceIoControl(hand, IOCTL_CDROM_READ_TOC_EX, readTocEx, rTELength, tocSD, tocSDLength, controlBytes, Nothing) errorcode = Err.LastDllError Catch Ex As AccessViolationException ResultBox.Text = ResultBox.Text & "Exception: " & Ex.Message & vbCrLf End Try ResultBox.Text = ResultBox.Text & "DeviceIoControl return: " & controlResult & vbCrLf ResultBox.Text = ResultBox.Text & "Error code: " & errorcode & vbCrLf ResultBox.Text = ResultBox.Text & "Bytes returned: " & controlBytes & vbCrLf
where readTocEx is (as far as I can work out) correctly formatted as input data, and tocSD is the output data. controlbytes should hold the number of bytes in tocSD that have been filled in by the function.<DllImportAttribute("kernel32.dll", EntryPoint:="DeviceIoControl")> _ Public Shared Function DeviceIoControl(ByVal hDevice As SafeFileHandle, _ ByVal dwIoControlCode As UInteger, _ ByVal lpInBuffer As Form1.CDROM_READ_TOC_EX, ByVal nInBufferSize As UInteger, _ ByRef lpOutBuffer As Form1.CDROM_TOC_SESSION_DATA, _ ByVal nOutBufferSize As UInteger, _ ByRef lpBytesReturned As UInteger, _ ByVal lpOverlapped As System.IntPtr) _ As <MarshalAsAttribute(UnmanagedType.Bool)> Boolean End Function
The output is:
DeviceIoControl return: False Error code: 0 Bytes returned: 0
Peter
- נערך על-ידי ptoye יום שני 08 אוגוסט 2011 13:46 Added comment about unmarking answers