QA@IT

VB.NETからWinAPIを使って部数指定してPDFファイルを印刷

9441 PV

VB.NETから部数指定してPDFファイルを印刷したいと考えています。
VB.NETのライブラリではPDFが対応されてないようなのでWinAPIを使用して実施していますが
設定が反映されません。
※プログラム実行後、プリンタ設定を確認しても変化なし。
ただし、実行後にデバックで取得したdmcopiesの値を見ると設定した値になっている。

ソースを載せますのでアドバイスをもらえないでしょうか。

環境: Windows7 Professional SP1
Visual Studio2010 .NET FrameWork4.0

モジュール
Imports System.Runtime.InteropServices
Imports System.ComponentModel
Imports System.IO
Imports System.Drawing.Printing
Module Module1
    <DllImport("winspool.drv", CharSet:=CharSet.Auto, SetLastError:=True)> _
    Public Function OpenPrinter( _
    ByVal pPrinterName As String, ByRef hPrinter As IntPtr, _
    ByVal pDefault As IntPtr) As Boolean
    End Function

    <DllImport("winspool.drv", SetLastError:=True)> _
    Public Function ClosePrinter( _
    ByVal hPrinter As IntPtr) As Boolean
    End Function

    <DllImport("winspool.drv", SetLastError:=True)> _
    Public Function GetPrinter( _
    ByVal hPrinter As IntPtr, ByVal dwLevel As Integer, _
    ByVal pPrinter As IntPtr, ByVal cbBuf As Integer, _
    ByRef pcbNeeded As Integer) As Boolean
    End Function

    Public Declare Function SetPrinter Lib "winspool.drv" Alias "SetPrinterA" (ByVal hPrinter As IntPtr, _
                     ByVal Level As Integer, ByRef pPrinter As PRINTER_INFO_2, ByVal Command_Renamed As Integer) As Boolean

    Public Declare Sub MoveMemory Lib "KERNEL32" Alias "RtlMoveMemory" (ByVal Dest As PRINTER_INFO_2, ByVal Source As IntPtr, ByVal length As Integer)
    Public Declare Sub MoveMemory Lib "KERNEL32" Alias "RtlMoveMemory" (ByVal Dest As DEVMODE, ByVal Source As DEVMODE, ByVal length As Integer)
    Public Declare Sub MoveMemory Lib "KERNEL32" Alias "RtlMoveMemory" (ByVal Dest As IntPtr, ByVal Source As DEVMODE, ByVal length As Integer)

    <StructLayout(LayoutKind.Sequential, CharSet:=CharSet.Auto)> _
    Public Structure PRINTER_DEFAULTS
        Public pDatatype As IntPtr
        Public pDevMode As IntPtr
        Public DesiredAccess As Integer
    End Structure

    <StructLayout(LayoutKind.Sequential, CharSet:=CharSet.Auto)> _
Public Structure PRINTER_INFO_2
        Public pServerName As String
        Public pPrinterName As String
        Public pShareName As String
        Public pPortName As String
        Public pDriverName As String
        Public pComment As String
        Public pLocation As String
        Public pDevMode As IntPtr
        Public pSepFile As String
        Public pPrintProcessor As String
        Public pDatatype As String
        Public pParameters 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

    <StructLayout(LayoutKind.Sequential, CharSet:=CharSet.Auto)> _
    Public Structure DEVMODE
        Public Const CCHDEVICENAME As Integer = 32
        Public Const CCHFORMNAME As Integer = 32

        <MarshalAs(UnmanagedType.ByValTStr, SizeConst:=CCHDEVICENAME)> _
        Public dmDeviceName As String
        Public dmSpecVersion As Integer
        Public dmDriverVersion As Integer
        Public dmSize As Integer
        Public dmDriverExtra As Integer
        Public dmOrientation As Integer
        Public dmPaperSize As Integer
        Public dmPaperLength As Integer
        Public dmPaperWidth As Integer
        Public dmScale As Integer
        Public dmCopies As Integer
        Public dmDefaultSource As Integer
        Public dmPrintQuality As Integer
        Public dmColor As Integer
        Public dmDuplex As Integer
        Public dmYResolution As Integer
        Public dmTTOption As Integer
        Public dmCollate As Integer

        <MarshalAs(UnmanagedType.ByValTStr, SizeConst:=CCHFORMNAME)> _
        Public dmFormName As String
        Public dmLogPixels As Integer
        Public dmBitsPerPel As Integer ' Declared wrong in the full framework
        Public dmPelsWidth As Integer
        Public dmPelsHeight As Integer
        Public dmDisplayFlags As Integer
        Public dmDisplayFrequency As Integer
        Public dmICMMethod As Integer
        Public dmICMIntent As Integer
        Public dmMediaType As Integer
        Public dmDitherType As Integer
        Public dmReserved1 As Integer
        Public dmReserved2 As Integer
        Public dmPanningWidth As Integer
        Public dmPanningHeight As Integer
        Public dmPositionX As Integer ' Using a PointL Struct does not work
        Public dmPositionY As Integer

    End Structure

    Public Const DMORIENT_PORTAIT = 1
    Public Const STANDARD_RIGHTS_REQUIRED = &HF0000
    Public Const PRINTER_ACCESS_ADMINISTER = &H4
    Public Const PRINTER_ACCESS_USE = &H8
    Public Const PRINTER_ALL_ACCESS = (STANDARD_RIGHTS_REQUIRED Or PRINTER_ACCESS_ADMINISTER Or PRINTER_ACCESS_USE)
    Public Const DM_PAPERSIZE = &H2             'dmPaperSize 
    Public Const DM_PAPERLENGTH = &H4           'dmPaperLength 
    Public Const DM_PAPERWIDTH = &H8            'dmPaperWidth 

End Module

フォーム
Imports System.Runtime.InteropServices
Imports System.ComponentModel
Imports System.IO
Imports System.Drawing.Printing

Public Class Form1

    Private Sub Button1_Click(sender As System.Object, e As System.EventArgs) Handles Button1.Click


        Dim mos As New System.Management.ManagementObjectSearcher( _
        "Select * from Win32_Printer")
        Dim moc As System.Management.ManagementObjectCollection = _
            mos.Get()

        'プリンタを列挙する
                Dim mo As System.Management.ManagementObject
        For Each mo In moc
            If CStr(mo("Name")) = "TEST" Then
                '名前を見つけたとき、デフォルトプリンタに設定する
                Dim mbo As System.Management.ManagementBaseObject = _
                    mo.InvokeMethod("SetDefaultPrinter", Nothing, Nothing)
                Dim ret As Long = Convert.ToInt64(mbo("returnValue"))
                If ret <> 0 Then
                    Throw New Exception("失敗しました。")
                End If
                Exit For
            End If
        Next mo

        Dim PD As New System.Drawing.Printing.PrintDocument
        Dim PrinterName As String
        Dim PrinterHandle As Long
        Dim Result As Long
        Dim Needed As Integer
        Dim pPrinterInfo As IntPtr = IntPtr.Zero 'Dim pi2_buffer() As Long 
        Dim PrinterInfo As PRINTER_INFO_2 = Nothing
        Dim pFullDevMode As IntPtr
        Dim MyDevMode As DEVMODE = Nothing

        PrinterName = "TEST"

        Call OpenPrinter(PrinterName, PrinterHandle, pPrinterInfo)
        Call GetPrinter(PrinterHandle, 2, 0, 0, Needed)
        'メモリを割り当てる 
                pPrinterInfo = Marshal.AllocHGlobal(Needed)
        Call GetPrinter(PrinterHandle, 2, pPrinterInfo, Needed, Needed)

        PrinterInfo = CType(Marshal.PtrToStructure( _
                                     pPrinterInfo, GetType(PRINTER_INFO_2)), PRINTER_INFO_2)
        pFullDevMode = PrinterInfo.pDevMode
        MyDevMode = CType(Marshal.PtrToStructure( _
                                     pFullDevMode, GetType(DEVMODE)), DEVMODE)

        With MyDevMode
            .dmCopies = 100
        End With
        Marshal.StructureToPtr(MyDevMode, pFullDevMode, True)
        PrinterInfo.pDevMode = pFullDevMode
        Marshal.StructureToPtr(PrinterInfo, pPrinterInfo, True)

        Result = SetPrinter(PrinterHandle, 2, PrinterInfo, IntPtr.Zero)
        Call ClosePrinter(PrinterHandle)

        Try
            'PDF印刷
                        Dim pro As New Process
            pro.StartInfo.FileName = "AcroRd32.exe"
            pro.StartInfo.Verb = "open"
            pro.StartInfo.Arguments = " /n /h /t """ + "D:\test.pdf" + """ """ + "" + """ """
            pro.StartInfo.WindowStyle = ProcessWindowStyle.Hidden
            pro.StartInfo.CreateNoWindow = True
            pro.Start()
            pro.WaitForInputIdle()
            If pro.CloseMainWindow() = False Then
                pro.Kill()
            End If
            pro.Close()
            pro.Dispose()

        Catch ex As Exception

        End Try
    End Sub

End Class
  • WinAPIでやりたいことはPDFの印刷ではなくてプリンタの変更ですか?印刷そのものはAdobe Acrobat(AcroRd32.exe)で実行しているんですよね?
    -
  • 手動でプリンタの設定を変えておけば、AcroRd32を起動してPDFをそのプリンタから印刷できていると思っていいでしょうか? -

回答

テスト環境は32bitだったのですが、実際に動作するPCは64bitでした。
すると下の部分で実行時にエラーにならず落ちました。

Marshal.StructureToPtr(PrinterInfo, pPrinterInfo, True)

ポインタ含め、サイズが異なるものが出てくるからな気がしますが、
64bitでのコンパイルが必須でなければ、x86でコンパイルするのが近道な気はします。

Visual Studioのビルドの設定で AnyCPUが選ばれていた場合は x86を選んでビルドしてみてください。32bitアプリケーションとしてビルドされますので、64bit環境でも32bitで起動します。
(2010だとExeプロジェクトだとx86がデフォルトだったような気もしますのでうまくいかなかった場合はすいません)
http://msdn.microsoft.com/ja-jp/library/vstudio/5b4eyb0k(v=vs.100).aspx


それとは別に動くようになった構造体かコードを可能であれば公開してください。
同じ問題を抱えた人の参考になると思いますので。

編集 履歴 (1)
  • 今回は時間があまりないため、x86でコンパイルして進めました。
    色々ありがとうございました。
    -

32bit環境でうまくいったのは下記ソースになります。
64bit環境で落ちたのは以下です。

Marshal.StructureToPtr(infoPrinter, pPrinter, True)

32bitに変換してコンパイルするのは知っていました。
現状は64bit環境で再コンパイルして実行しています。
そのため、できれば64bit環境でコンパイルして動作したいと考えます。
最悪の場合は、32bitでコンパイルしてみるつもりですが。

Imports System.Runtime.InteropServices

Public Class Form1

    'Public Const DMPAPER_A4 = 9
    Public Const DC_PAPERS As Integer = 2
    Public Const DC_PAPERNAMES As Integer = 16
    Public Const STANDARD_RIGHTS_REQUIRED As Integer = &HF0000
    Public Const PRINTER_ACCESS_ADMINISTER As Integer = &H4
    Public Const PRINTER_ACCESS_USE As Integer = &H8
    Public Const PRINTER_ALL_ACCESS As Integer = (STANDARD_RIGHTS_REQUIRED Or PRINTER_ACCESS_ADMINISTER Or PRINTER_ACCESS_USE)

    <DllImport("winspool.drv", CharSet:=CharSet.Auto)> _
    Private Shared Function OpenPrinter( _
  ByVal pPrinterName As String, _
  ByRef hPrinter As IntPtr, _
  ByRef pDefault As PRINTER_DEFAULTS) As Boolean
    End Function

    <DllImport("winspool.drv", CharSet:=CharSet.Auto)> _
    Private Shared Function GetPrinter( _
  ByVal hPrinter As IntPtr, _
  ByVal dwLevel As Integer, _
  ByVal pPrinter As IntPtr, _
  ByVal cbBuf As Integer, _
  ByRef pcbNeeded As Integer) As Boolean
    End Function

    <DllImport("winspool.drv", CharSet:=CharSet.Auto)> _
    Private Shared Function SetPrinter( _
  ByVal hPrinter As IntPtr, _
  ByVal dwLevel As Integer, _
  ByVal pPrinter As IntPtr, _
  ByVal Command As Integer) As Boolean
    End Function

    <DllImport("winspool.drv", CharSet:=CharSet.Auto)> _
    Private Shared Function ClosePrinter( _
  ByVal hPrinter As IntPtr) As Boolean
    End Function

    Friend Structure PRINTER_DEFAULTS
        Public pDatatype As IntPtr
        Public pDevMode As IntPtr
        Public DesiredAccess As Integer
    End Structure

    <Serializable(), StructLayout(LayoutKind.Sequential, CharSet:=CharSet.Auto)> _
    Friend Structure PRINTER_INFO_2
        Public pServerName As String
        Public pPrinterName As String
        Public pShareName As String
        Public pPortName As String
        Public pDriverName As String
        Public pComment As String
        Public pLocation As String
        Public pDevMode As IntPtr
        Public pSepFile As String
        Public pPrintProcessor As String
        Public pDatatype As String
        Public pParameters As String
        Public pSecurityDescriptor As IntPtr
        Public Attributes As System.UInt32
        Public Priority As System.UInt32
        Public DefaultPriority As System.UInt32
        Public StartTime As System.UInt32
        Public UntilTime As System.UInt32
        Public Status As System.UInt32
        Public cJobs As System.UInt32
        Public AveragePPM As System.UInt32
    End Structure

    <StructLayout(LayoutKind.Sequential, CharSet:=CharSet.Auto)> _
    Friend Structure DevMode
        <VBFixedString(32), MarshalAs(UnmanagedType.ByValTStr, SizeConst:=32)> _
        Public dmDeviceName As String
        Public dmSpecVersion As Short
        Public dmDriverVersion As Short
        Public dmSize As Short
        Public dmDriverExtra As Short
        Public dmFields As Integer
        Public dmOrientation As Short
        Public dmPaperSize As Short
        Public dmPaperLength As Short
        Public dmPaperWidth As Short
        Public dmScale As Short
        Public dmCopies As Short
        Public dmDefaultSource As Short
        Public dmPrintQuality As Short
        Public dmColor As Short
        Public dmDuplex As Short
        Public dmYResolution As Short
        Public dmTTOption As Short
        Public dmCollate As Short
        <VBFixedString(32), MarshalAs(UnmanagedType.ByValTStr, SizeConst:=32)> _
        Public dmFormName As String
        Public dmUnusedPadding As Short
        Public dmBitsPerPel As Short
        Public dmPelsWidth As Integer
        Public dmPelsHeight As Integer
        Public dmDisplayFlags As Integer
        Public dmDisplayFrequency As Integer
    End Structure

    Private Sub Button1_Click(sender As System.Object, e As System.EventArgs) Handles Button1.Click

        Dim hPrinter As IntPtr
        Dim returnValue As Boolean
        Dim byteNeeded As Integer
        Dim pPrinter As IntPtr
        Dim infoPrinter As PRINTER_INFO_2
        Dim modeDev As DevMode


        Try
            'デフォルトプリンタに設定
            Dim mos As New System.Management.ManagementObjectSearcher( _
        "Select * from Win32_Printer")
            Dim moc As System.Management.ManagementObjectCollection = _
                mos.Get()

            'プリンタを列挙する
            Dim mo As System.Management.ManagementObject
            For Each mo In moc
                If CStr(mo("Name")) = "LX-D5500-01(1号機)" Then
                    '名前を見つけたとき、デフォルトプリンタに設定する
                    Dim mbo As System.Management.ManagementBaseObject = _
                        mo.InvokeMethod("SetDefaultPrinter", Nothing, Nothing)
                    Dim ret As Long = Convert.ToInt64(mbo("returnValue"))
                    If ret <> 0 Then
                        Throw New Exception("失敗しました。")
                    End If
                    Exit For
                End If
            Next mo

            ' アクセス権を設定する。
            Dim pDefault As New PRINTER_DEFAULTS
            With pDefault
                .DesiredAccess = PRINTER_ALL_ACCESS
            End With

            ' プリンターを開く。
            returnValue = OpenPrinter("LX-D5500-01(1号機)", _
            hPrinter, pDefault)
            Debug.Print("OpenPrinter:" & returnValue)

            ' バッファサイズを取り出す。

            returnValue = GetPrinter(hPrinter, _
            2, IntPtr.Zero, 0, byteNeeded)

            ' メモリの割当て。

            pPrinter = Marshal.AllocHGlobal(byteNeeded)

            ' プリンター情報を読み取りする。
            returnValue = GetPrinter(hPrinter, _
            2, pPrinter, byteNeeded, byteNeeded)
            Debug.Print("GetPrinter:" & returnValue)

            ' 構造体にメモリのデータをコピーする。

            infoPrinter = CType(Marshal.PtrToStructure(pPrinter, GetType(PRINTER_INFO_2)), PRINTER_INFO_2)
            modeDev = CType(Marshal.PtrToStructure(infoPrinter.pDevMode, GetType(DevMode)), DevMode)

            ' 部数を変更する。
            modeDev.dmCopies = 150

            ' メモリに構造体のデータをコピー する。
            Marshal.StructureToPtr(modeDev, infoPrinter.pDevMode, True)
            Marshal.StructureToPtr(infoPrinter, pPrinter, True)

            ' プリンター情報を書き込みする。
            returnValue = SetPrinter(hPrinter, _
            2, pPrinter, 0)
            Debug.Print("SetPrinter:" & returnValue)

            ' プリンターを閉じる。
            returnValue = ClosePrinter(hPrinter)
            Debug.Print("ClosePrinter:" & returnValue)

            ' メモリを開放する。
            Marshal.FreeHGlobal(pPrinter)

        Catch ex As Exception
            Debug.Print(ex.ToString)
        End Try
    End Sub

End Class
編集 履歴 (0)

すみません。
もう一点質問させてください。

テスト環境は32bitだったのですが、実際に動作するPCは64bitでした。
すると下の部分で実行時にエラーにならず落ちました。

Marshal.StructureToPtr(PrinterInfo, pPrinterInfo, True)

構造体の定義を見直す必要があるように見受けるのですが
どのような修正が必要なんでしょうか。

編集 履歴 (0)

参考にしたものはこのあたりです。

http://dobon.net/vb/bbs/log3-18/10638.html
https://sites.google.com/site/n3kh4dev/vb-net/purinta-guan-lian
http://bbs.wankuma.com/index.cgi?mode=al2&namber=57499&KLOG=96
http://rucio.cloudapp.net/ThreadDetail.aspx?ThreadId=10679

dmFieldsを追加してやってみましたがだめでした。
また最初の質問時に値が反映されないと書いていましたが
今実行してみるとSetPrinter実行後に全ての値がデフォルトに戻ってしまいました。
(元の構造体MyDevModeをデバックで見るとdmCopiesは変わっています。)

編集 履歴 (0)
  • うーん、わんくまのBBSで指摘される前の構造体使っているみたいですね。
    とりあえず構造体見直した方がいいです。サイズが全然ちがいますので、
    動いたとしても偶然です。
    ちなみにResultは成功すると0以外が入ります。
    -
  • わんくまBBSを参考にするのであれば、No57529 の構造体であればパッと見ちゃんとしているように見えます。
    元々の構造体がWORDのものはVB.NETではShortに、DWORDのものはVB.NETではIntegerになります。(VB6や32bitVBAだとWORDがIntegerでDWORDがLongだったんで、BBSではその時代のコードを参考にしている事が指摘されてます。)
    -
  • 指摘もらったページを参考にしたらできました。
    データ構造が正しくなかったのを把握しました。
    とても助かりました、ありがとうございました。
    -

このDEVMODE構造体はどこからもってきたものですか?

Public dmDriverExtra As Integer
Public dmOrientation As Integer

この間にdmFieldsという32bitのメンバがいるはずで、ここに構造体のうち何を反映するか設定します(論理和で設定します)。
メンバの型が数値だとほとんど Integerになっているのも気になります。

あと検証用に変更しているのでしょうが、

With MyDevMode
   .dmCopies = 100
End With

ですのでこれだと100部になりそうですね。一応指摘しておきます。

全て直った場合は

Const DM_COPIES = &H100
With MyDevMode
   .dmFields = DM_COPIES
   .dmCopies = 10
End With

という指定方法になるかと思います。

他の構造体もどうなってるかわかりませんし、私が間違ってる可能性もありますので、ひとまず参考にしたサイトなどがあればそれを教えてください。

編集 履歴 (0)

flied_oninさん

ありがとうございます。

手動でプリンタの設定を変えておけば、AcroRd32を起動してPDFをそのプリンタから印刷できていると思ってい>いでしょうか?

その通りです。
システムで行いたい流れはこんな感じです。

1.指定したプリンタをデフォルトプリンタに変更する。
2.デフォルトプリンタの部数を10に変更する。
3.PDFを印刷する。(AcroRd32を起動)

今できないのは2になります。

編集 履歴 (0)
ウォッチ

この質問への回答やコメントをメールでお知らせします。