PInvoke unbalanced the stack kernel32.dll

Hi,

I am trying to learn how to use the Ghostscript API following the Ghostscript vb.net code example in “parrot fashion”. I do not have sufficient technical knowledge to understand error messages or the implications.

I am picking up an error message during the code implementation. I suspect it is because the version of kernel32.dll on my computer is incompatible with the vb.net source code.

Any pointers where I can learn about PInvoke and stacks will be much appreciated.

Any help in pointing me in the right direction is appreciated.

HerbiNZ

New Zealand

------------------------------------------------------------------------

File: kernel32.dll

Name: Windows NT BASE API Client DLL

Version: 5.1.2600.2945

File version: (xpsp_sp2_gdr.060704-2349)

Error message:

PInvokeStackImbalance was detected

Message: A call to PInvoke function 'ghostscriptTest!ConsoleApplication1.gsapi::CopyMemory' 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.

This happens when the subroutine copyMemory is called.

Software: Ghostscript 8.54

Downloaded file: gs854w32-gpl.exe

The .exe file and sample source code was supplied by Ghostscript at: http://www.cs.wisc.edu/~ghost/

'Copyright (c) 2002 Dan Mount and Ghostgum Software Pty Ltd

'

' Permission is hereby granted, free of charge, to any person obtaining

' a copy of this software and associated documentation files (the

' "Software"), to deal in the Software without restriction, including

' without limitation the rights to use, copy, modify, merge, publish,

' distribute, sublicense, and/or sell copies of the Software, and to

' permit persons to whom the Software is furnished to do so, subject to

' the following conditions:

'

' The above copyright notice and this permission notice shall be

' included in all copies or substantial portions of the Software.

'

' THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,

' EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF

' MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND

' NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS

' BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN

' ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN

' CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE

' SOFTWARE.

' This is an example of how to call the Ghostscript DLL from

' Visual Basic.NET. There are two examples, one converts

' colorcir.ps to PDF, the other is like command line Ghostscript.

' The display device is not supported.

'

' This code is not compatible with VB6. There is another

' example which does work with VB6. Differences include:

' 1. VB.NET uses GCHandle to get pointer

' VB6 uses StrPtr/VarPtr

' 2. VB.NET Integer is 32bits, Long is 64bits

' VB6 Integer is 16bits, Long is 32bits

' 3. VB.NET uses IntPtr for pointers

' VB6 uses Long for pointers

' 4. VB.NET strings are always Unicode

' VB6 can create an ANSI string

' See the following URL for some VB6 / VB.NET details

' http://msdn.microsoft.com/library/default.asp url=/library/en-us/dnvb600/html/vb6tovbdotnet.asp

Imports System.Runtime.InteropServices

Module gsapi

Public Declare Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" (ByVal dest As IntPtr, ByVal source As IntPtr, ByVal bytes As Long)

'------------------------------------------------

'UDTs Start

'------------------------------------------------

<StructLayout(LayoutKind.Sequential)> Public Structure GS_Revision

Public strProduct As IntPtr

Public strCopyright As IntPtr

Public intRevision As Integer

Public intRevisionDate As Integer

End Structure

'------------------------------------------------

'UDTs End

'------------------------------------------------

'------------------------------------------------

'Callback Functions Start

'------------------------------------------------

'These are only required if you use gsapi_set_stdio

Public Delegate Function StdioCallBack(ByVal handle As IntPtr, ByVal strptr As IntPtr, ByVal count As Integer) As Integer

Public Function gsdll_stdin(ByVal intGSInstanceHandle As IntPtr, ByVal strz As IntPtr, ByVal intBytes As Integer) As Integer

' This is dumb code that reads one byte at a time

' Ghostscript doesn't mind this, it is just very slow

If intBytes = 0 Then

gsdll_stdin = 0

Else

Dim ich As Integer = Console.Read()

If ich = -1 Then

gsdll_stdin = 0 ' EOF

Else

Dim bch As Byte = ich

Dim gcByte As GCHandle = GCHandle.Alloc(bch, GCHandleType.Pinned)

Dim ptrByte As IntPtr = gcByte.AddrOfPinnedObject()

CopyMemory(strz, ptrByte, 1)

ptrByte = IntPtr.Zero

gcByte.Free()

gsdll_stdin = 1

End If

End If

End Function

Public Function gsdll_stdout(ByVal intGSInstanceHandle As IntPtr, ByVal strz As IntPtr, ByVal intBytes As Integer) As Integer

' If you can think of a more efficient method, please tell me!

' We need to convert from a byte buffer to a string

' First we create a byte array of the appropriate size

Dim aByte(intBytes) As Byte

' Then we get the address of the byte array

Dim gcByte As GCHandle = GCHandle.Alloc(aByte, GCHandleType.Pinned)

Dim ptrByte As IntPtr = gcByte.AddrOfPinnedObject()

' Then we copy the buffer to the byte array

CopyMemory(ptrByte, strz, intBytes)

' Release the address locking

ptrByte = IntPtr.Zero

gcByte.Free()

' Then we copy the byte array to a string, character by character

Dim str As String = ""

Dim i As Integer

For i = 0 To intBytes - 1

str = str + Chr(aByte(i))

Next

' Finally we output the message

Console.Write(str)

gsdll_stdout = intBytes

End Function

Public Function gsdll_stderr(ByVal intGSInstanceHandle As IntPtr, ByVal strz As IntPtr, ByVal intBytes As Integer) As Integer

gsdll_stderr = gsdll_stdout(intGSInstanceHandle, strz, intBytes)

End Function

'------------------------------------------------

'Callback Functions End

'------------------------------------------------

'------------------------------------------------

'API Calls Start

'------------------------------------------------

'Win32 API

'GhostScript API

' Public Declare Function gsapi_revision Lib "gsdll32.dll" (ByVal pGSRevisionInfo As IntPtr, ByVal intLen As Integer) As Integer

Public Declare Function gsapi_revision Lib "gsdll32.dll" (ByRef pGSRevisionInfo As GS_Revision, ByVal intLen As Integer) As Integer

Public Declare Function gsapi_new_instance Lib "gsdll32.dll" (ByRef lngGSInstance As IntPtr, ByVal lngCallerHandle As IntPtr) As Integer

Public Declare Function gsapi_set_stdio Lib "gsdll32.dll" (ByVal lngGSInstance As IntPtr, ByVal gsdll_stdin As StdioCallBack, ByVal gsdll_stdout As StdioCallBack, ByVal gsdll_stderr As StdioCallBack) As Integer

Public Declare Sub gsapi_delete_instance Lib "gsdll32.dll" (ByVal lngGSInstance As IntPtr)

Public Declare Function gsapi_init_with_args Lib "gsdll32.dll" (ByVal lngGSInstance As IntPtr, ByVal lngArgumentCount As Integer, ByVal lngArguments As IntPtr) As Integer

Public Declare Function gsapi_run_file Lib "gsdll32.dll" (ByVal lngGSInstance As IntPtr, ByVal strFileName As String, ByVal intErrors As Integer, ByVal intExitCode As Integer) As Integer

Public Declare Function gsapi_exit Lib "gsdll32.dll" (ByVal lngGSInstance As IntPtr) As Integer

'------------------------------------------------

'API Calls End

'------------------------------------------------

'------------------------------------------------

'User Defined Functions Start

'------------------------------------------------

Public Function StringToAnsiZ(ByVal str As String) As Byte()

' Convert a Unicode string to a null terminated Ansi string for Ghostscript.

' The result is stored in a byte array. Later you will need to convert

' this byte array to a pointer with GCHandle.Alloc(XXXX, GCHandleType.Pinned)

' and GSHandle.AddrOfPinnedObject()

Dim intElementCount As Integer

Dim intCounter As Integer

Dim aAnsi() As Byte

Dim bChar As Byte

intElementCount = Len(str)

ReDim aAnsi(intElementCount + 1)

For intCounter = 0 To intElementCount - 1

bChar = Asc(Mid(str, intCounter + 1, 1))

aAnsi(intCounter) = bChar

Next intCounter

aAnsi(intElementCount) = 0

StringToAnsiZ = aAnsi

End Function

Public Function AnsiZtoString(ByVal strz As IntPtr) As String

' We need to convert from a byte buffer to a string

Dim byteCh(1) As Byte

Dim bOK As Boolean = True

Dim gcbyteCh As GCHandle = GCHandle.Alloc(byteCh, GCHandleType.Pinned)

Dim ptrByte As IntPtr = gcbyteCh.AddrOfPinnedObject()

Dim j As Integer = 0

Dim str As String = ""

While bOK

' This is how to do pointer arithmetic!

Dim sPtr As New IntPtr(strz.ToInt64() + j)

CopyMemory(ptrByte, sPtr, 1)

If byteCh(0) = 0 Then

bOK = False

Else

str = str + Chr(byteCh(0))

End If

j = j + 1

End While

AnsiZtoString = str

End Function

Public Function CheckRevision(ByVal intRevision As Integer) As Boolean

' Check revision number of Ghostscript

Dim intReturn As Integer

Dim udtGSRevInfo As GS_Revision

Dim gcRevision As GCHandle

gcRevision = GCHandle.Alloc(udtGSRevInfo, GCHandleType.Pinned)

intReturn = gsapi_revision(udtGSRevInfo, 16)

Console.WriteLine("Revision = " & udtGSRevInfo.intRevision)

Console.WriteLine("RevisionDate = " & udtGSRevInfo.intRevisionDate)

Console.WriteLine("Product = " & AnsiZtoString(udtGSRevInfo.strProduct))

Console.WriteLine("Copyright = " & AnsiZtoString(udtGSRevInfo.strCopyright))

If udtGSRevInfo.intRevision = intRevision Then

CheckRevision = True

Else

CheckRevision = False

End If

gcRevision.Free()

End Function

Public Function CallGS(ByVal astrGSArgs() As String) As Boolean

Dim intReturn As Integer

Dim intGSInstanceHandle As IntPtr

Dim aAnsiArgs() As Object

Dim aPtrArgs() As IntPtr

Dim aGCHandle() As GCHandle

Dim intCounter As Integer

Dim intElementCount As Integer

' Dim iTemp As Integer

Dim callerHandle As IntPtr

Dim gchandleArgs As GCHandle

Dim intptrArgs As IntPtr

' Print out the revision details.

' If we want to insist on a particular version of Ghostscript

' we should check the return value of CheckRevision().

CheckRevision(704)

' Load Ghostscript and get the instance handle

intReturn = gsapi_new_instance(intGSInstanceHandle, callerHandle)

If (intReturn < 0) Then

Return (False)

End If

' Capture stdio

Dim stdinCallback As StdioCallBack

stdinCallback = AddressOf gsdll_stdin

Dim stdoutCallback As StdioCallBack

stdoutCallback = AddressOf gsdll_stdout

Dim stderrCallback As StdioCallBack

stderrCallback = AddressOf gsdll_stderr

intReturn = gsapi_set_stdio(intGSInstanceHandle, stdinCallback, stdoutCallback, stderrCallback)

If (intReturn >= 0) Then

' Convert the Unicode strings to null terminated ANSI byte arrays

' then get pointers to the byte arrays.

intElementCount = UBound(astrGSArgs)

ReDim aAnsiArgs(intElementCount)

ReDim aPtrArgs(intElementCount)

ReDim aGCHandle(intElementCount)

For intCounter = 0 To intElementCount

aAnsiArgs(intCounter) = StringToAnsiZ(astrGSArgs(intCounter))

aGCHandle(intCounter) = GCHandle.Alloc(aAnsiArgs(intCounter), GCHandleType.Pinned)

aPtrArgs(intCounter) = aGCHandle(intCounter).AddrOfPinnedObject()

Next

gchandleArgs = GCHandle.Alloc(aPtrArgs, GCHandleType.Pinned)

intptrArgs = gchandleArgs.AddrOfPinnedObject()

callerHandle = IntPtr.Zero

intReturn = gsapi_init_with_args(intGSInstanceHandle, intElementCount + 1, intptrArgs)

' Release the pinned memory

For intCounter = 0 To intElementCount

aGCHandle(intCounter).Free()

Next

gchandleArgs.Free()

' Stop the Ghostscript interpreter

gsapi_exit(intGSInstanceHandle)

End If

' release the Ghostscript instance handle

gsapi_delete_instance(intGSInstanceHandle)

If (intReturn >= 0) Then

CallGS = True

Else

CallGS = False

End If

End Function

Private Function ConvertFile() As Boolean

Dim astrArgs(10) As String

astrArgs(0) = "ps2pdf" 'The First Parameter is Ignored

astrArgs(1) = "-dNOPAUSE"

astrArgs(2) = "-dBATCH"

astrArgs(3) = "-dSAFER"

astrArgs(4) = "-r300"

astrArgs(5) = "-sDEVICE=pdfwrite"

astrArgs(6) = "-sOutputFile=d:\temp\out.pdf"

astrArgs(7) = "-c"

astrArgs(8) = ".setpdfwrite"

astrArgs(9) = "-f"

astrArgs(10) = "d:\temp\colorcir.ps"

Return CallGS(astrArgs)

End Function

Private Function InteractiveGS() As Boolean

Dim astrArgs(2) As String

astrArgs(0) = "gs" 'The First Parameter is Ignored

astrArgs(1) = "-c"

astrArgs(2) = "systemdict /start get exec"

Return CallGS(astrArgs)

End Function

Sub Main()

ConvertFile()

'InteractiveGS()

MsgBox("Done")

End Sub

'------------------------------------------------

'User Defined Functions End

'------------------------------------------------

End Module



Answer this question

PInvoke unbalanced the stack kernel32.dll

  • P R W

    Thank you, Max

    I found the article very useful.

    Regards

    Herman


  • shohaib

    Hello HerziNZ,

    Could you tell me what code changes you've made to make this work
    I'm facing the same problem.

    Many thanks
    Michel Pikard



  • mario.muja

    Brendan,

    I agree that the unbalanced stack warnings/exceptions are a worthwhile enhancement in the 2.0 framework.

    I have made the changes and the code works.

    Let the developer who upgrades to the 2.0 framework beware!

    Thank you for the help.

    Herman

    Auckland, New Zealand


  • Chris.Stewart

  • Chris Lively

    The unbalanced stack warnings/exceptions are one of my favorite features in the new 2.0 framework as it makes it much much harder for you to send the wrong data (or wrong size of data) to unmanaged code.

    An example of what you are seeing would be when VB.NET thinks it’s going to call a method whose first argument is 32-bits wide, but in fact is only 16-bits wide and if you are really unlucky, this kind of issue could cause you much pain and suffering.

    From the looks of it you are having a problem with calling MoveMemory in VB.NET which correlates to RtlMoveMemory in Kernel32.dll and a quick hop onto pinvoke.net suggests the following signature for the method:

    Declare Auto Sub MoveMemory Lib "Kernel32.dll" _
    Alias "RtlMoveMemory" (ByRef dest As IntPtr, ByRef src As IntPtr, ByVal size As Integer)

    While the one you are using very close so let’s try replacing yours with the one from above and see if that doesn’t fix your problem. At the very least I expect that the change from yours specifying of a long for the third argument now being an Integer might fix a few things.

    It's also worth noting that from the looks of it that code is a port from VB6 code where a Long integer was 32-bits, but where in VB.NET a Long is 64-bits.



  • PInvoke unbalanced the stack kernel32.dll