dotnet interop:
Hi There, I tried a simple VB.Net console program that uses win32 api call on WinSpool.drv to query a local or remote printer's sharing status. The API calls I used are OpenPrinter, GetPrinter and ClosePrinter. The program runs perfectly on x86 32-bit platform. However, when I compile the same program on a x64 Windows Server 2003 R2 Ent. and run it (with only slight modification on printer name), it caused a FatalExecutionEngineError. On VS2005 debug windows, it showed: --- The error code is 0xC0000005. This error may be a bug in the CLR or in the unsafe or non-verifiable portions of user code. Common sources of this bug include user marshaling errors for COM-interop or PInvoke, which may corrupt the stack. --- And in Application eventlog, a ".NET RunTime" event 1023 ".NET Runtime version 2.0.50727.63 - Fatal Execution Engine Error (000006427F880608) (80131506)" was logged. Here I attach this small program. Line 116, the second invocation of GetPrinter, is the place where .Net engine failed. Grateful if any experts here can give me some hints. Thanks Steve ----- Attached sample code --- Imports System.Runtime.InteropServices Module Module1 Const PRINTER_ATTRIBUTE_SHARED As Integer = &H8 Const ERROR_INSUFFICIENT_BUFFER As Integer = &H7A <StructLayout(LayoutKind.Sequential, CharSet:=CharSet.Unicode)> _ Structure PRINTER_INFO_2W <MarshalAs(UnmanagedType.LPWStr)> Public pServerName As String <MarshalAs(UnmanagedType.LPWStr)> Public pPrinterName As String <MarshalAs(UnmanagedType.LPWStr)> Public pShareName As String <MarshalAs(UnmanagedType.LPWStr)> Public pPortName As String <MarshalAs(UnmanagedType.LPWStr)> Public pDriverName As String <MarshalAs(UnmanagedType.LPWStr)> Public pComment As String <MarshalAs(UnmanagedType.LPWStr)> Public pLocation As String Public pDevMode As IntPtr <MarshalAs(UnmanagedType.LPWStr)> Public pSepFile As String <MarshalAs(UnmanagedType.LPWStr)> Public pPrintProcessor As String <MarshalAs(UnmanagedType.LPWStr)> Public pDataType As String <MarshalAs(UnmanagedType.LPWStr)> Public pParameter As String Public pSecurityDescriptor As IntPtr Public Attributes As Integer Public Priority As Integer Public DefaultPriority As Integer Public StartTime As Integer Public UntilTime As Integer Public Status As Integer Public cJobs As Integer Public AveragePPM As Integer End Structure <DllImport("winspool.Drv", EntryPoint:="OpenPrinterW", _ SetLastError:=True, CharSet:=CharSet.Unicode, _ ExactSpelling:=True, CallingConvention:=CallingConvention.StdCall)> _ Function OpenPrinter(ByVal src As String, _ ByRef hPrinter As IntPtr, ByVal pd As Integer) As Boolean End Function <DllImport("winspool.Drv", EntryPoint:="ClosePrinter", _ SetLastError:=True, CharSet:=CharSet.Unicode, _ ExactSpelling:=True, CallingConvention:=CallingConvention.StdCall)> _ Function ClosePrinter(ByVal hPrinter As IntPtr) As Integer End Function <DllImport("winspool.Drv", EntryPoint:="GetPrinterW", _ SetLastError:=True, CharSet:=CharSet.Unicode, _ ExactSpelling:=True, CallingConvention:=CallingConvention.StdCall)> _ Function GetPrinter(ByVal hPrinter As IntPtr, ByVal Level As Integer, _ ByRef pPrinter As Byte, ByVal cbBuf As Integer, ByRef pcbNeeded As Integer _ ) As Integer End Function Sub Main() Dim buf() As Byte Dim BytesWritten, ByteLength, rc, DLLErrCode As Integer Dim bufptr As IntPtr = IntPtr.Zero Dim bResult As Boolean Dim pPrinter_Info As PRINTER_INFO_2W Dim PrintHandle As IntPtr = IntPtr.Zero Console.WriteLine("Trying to OpenPrinter [\\CCZ166\CCL12]") ' check to see if the printer can be opened. rc = OpenPrinter("\\CCZ166\CCL12", PrintHandle, 0) If rc = 0 Then Console.WriteLine("Cannot OpenPrinter [\\CCZ166\CCL12] Error = [" & Err.LastDllError & "]") Exit Sub End If Console.WriteLine("Printer [CCL12] Found. Checking Shared Status... ") ReDim buf(1) ' check to get printer information, pass no buffer and ask for return buffer size rc = GetPrinter(PrintHandle, 2, buf(0), 0, BytesWritten) If rc = 0 Then DLLErrCode = Err.LastDllError ' Expected to get ERROR_INSUFFICIENT_BUFFER return code If DLLErrCode <> ERROR_INSUFFICIENT_BUFFER Then Console.WriteLine("Cannot GetPrinter [CCL12] DLL Error = [" & DLLErrCode & "]") ClosePrinter(PrintHandle) Exit Sub End If End If Console.WriteLine("First GetPrinter Called... BytesWritten = [" & BytesWritten.ToString & "]") With pPrinter_Info .pServerName = "" .pPrinterName = "" .pShareName = "" .pPortName = "" .pDriverName = "" .pComment = "" .pLocation = "" .pDevMode = IntPtr.Zero .pSepFile = "" .pPrintProcessor = "" .pDataType = "" .pParameter = "" .pSecurityDescriptor = IntPtr.Zero .Attributes = 0 .Priority = 0 .DefaultPriority = 0 .StartTime = 0 .UntilTime = 0 .Status = 0 .cJobs = 0 .AveragePPM = 0 End With ByteLength = BytesWritten ReDim buf(ByteLength - 1) ' Get Printer Info and store it in buf Console.WriteLine("Second GetPrinter Calling... ") rc = GetPrinter(PrintHandle, 2, buf(0), ByteLength, BytesWritten) Console.WriteLine("Second GetPrinter Called... ") If rc = 0 Then ' Unexpected GetPrinter Error Console.WriteLine("Cannot GetPrinter [CCL12] DLL Error = " & _ Err.LastDllError) ClosePrinter(PrintHandle) Exit Sub End If ' Allocate memory and parse the buffer Try bufptr = Marshal.AllocCoTaskMem(ByteLength) Marshal.Copy(buf, 0, bufptr, ByteLength) pPrinter_Info = CType(Marshal.PtrToStructure(bufptr, GetType(PRINTER_INFO_2W)), _ PRINTER_INFO_2W) Marshal.FreeCoTaskMem(bufptr) Catch ex As Exception Console.WriteLine("Unable to marshal PRINTER_INFO_2. ") ClosePrinter(PrintHandle) Exit Sub End Try ' Check whether the printer is shared bResult = (pPrinter_Info.Attributes And PRINTER_ATTRIBUTE_SHARED) ClosePrinter(PrintHandle) If bResult Then Console.WriteLine("Printer [CCL12] found and shared. ") Else Console.WriteLine("Printer [CCL12] found but not shared.") End If End Sub End Module
[quoted text, click to view] >However, when I compile the same program on a x64 Windows Server 2003 R2 >Ent. and run it (with only slight modification on printer name), it caused a >FatalExecutionEngineError.
Great. That helps expose the errors in the code that may go unnoticed on Win32. [quoted text, click to view] > <DllImport("winspool.Drv", EntryPoint:="OpenPrinterW", _ > SetLastError:=True, CharSet:=CharSet.Unicode, _ > ExactSpelling:=True, CallingConvention:=CallingConvention.StdCall)> _ > Function OpenPrinter(ByVal src As String, _ > ByRef hPrinter As IntPtr, ByVal pd As Integer) As Boolean
The last parameters is a pointer, so you have to use IntPtr rather than Integer. [quoted text, click to view] > <DllImport("winspool.Drv", EntryPoint:="GetPrinterW", _ > SetLastError:=True, CharSet:=CharSet.Unicode, _ > ExactSpelling:=True, CallingConvention:=CallingConvention.StdCall)> _ > Function GetPrinter(ByVal hPrinter As IntPtr, ByVal Level As Integer, _ > ByRef pPrinter As Byte, ByVal cbBuf As Integer, ByRef pcbNeeded As >Integer _ > ) As Integer
pPrinter should be a pointer to pointer to byte (i.e. a ByRef IntPtr in managed code). Mattias -- Mattias Sjögren [C# MVP] mattias @ mvps.org http://www.msjogren.net/dotnet/ | http://www.dotnetinterop.com
[quoted text, click to view] > > <DllImport("winspool.Drv", EntryPoint:="GetPrinterW", _ > > SetLastError:=True, CharSet:=CharSet.Unicode, _ > > ExactSpelling:=True, CallingConvention:=CallingConvention.StdCall)> _ > > Function GetPrinter(ByVal hPrinter As IntPtr, ByVal Level As Integer, _ > > ByRef pPrinter As Byte, ByVal cbBuf As Integer, ByRef pcbNeeded As > >Integer _ > > ) As Integer > > pPrinter should be a pointer to pointer to byte (i.e. a ByRef IntPtr > in managed code).
Can you elaborate this point a bit more? Or can you demonstrate me the way to modify my code? I just get confused here. Byref Byte should already passed the starting position of a byte array and the length of byte array should be determined by cbBuf. Also, I noticed that the first call to GetPrinter returned a large volume of pcbNeeded (4 times that in 32-bit environment).
Steve, [quoted text, click to view] >> pPrinter should be a pointer to pointer to byte (i.e. a ByRef IntPtr >> in managed code). > >Can you elaborate this point a bit more?
First I should correct myself. The parameter shouldn't be a pointer to a pointer to byte. That's what my local Platform SDK docs says (LPBYTE*) but it turns out the docs were wrong. The headers and MSDN online library has it correct - it's just a single pointer to a byte array (LPBYTE). So you had the indirection level right but your declaration is still incorrect... [quoted text, click to view] >Byref Byte should already passed >the starting position of a byte array and the length of byte array should be >determined by cbBuf.
Passing the first byte in an array ByRef where a byte array is expected was something you got away with on Win32 and the 32-bit CLR. But no more on Win64. If the function wants a byte array, you should pass it the entire array (ByVal since arrays are reference types). You can read more here http://blogs.msdn.com/joshwil/archive/2005/08/10/450200.aspx [quoted text, click to view] >Also, I noticed that the first call to GetPrinter returned a large volume of >pcbNeeded (4 times that in 32-bit environment).
I wouldn't worry about that if you get it working. Sure 4 times sounds a bit much, but the double pointer size and stricter alignment rules can certainly at least double the buffer needed. Mattias -- Mattias Sjögren [C# MVP] mattias @ mvps.org http://www.msjogren.net/dotnet/ | http://www.dotnetinterop.com
Don't see what you're looking for? Try a search.
|