DOBON.NET DOBON.NETプログラミング掲示板過去ログ

API DeletePrinterの使用について

環境/言語:[WindowsXP VB.NET2003]
分類:[.NET]

初めて投稿させて頂きます。よろしくお願いします。

プリンタドライバのインストール、アンインストールを行うプログラムを作成しています。プリンタキューを削除するのに、APIのDeletePrinter関数を使っていますが、アクセス拒否(GetLastError()エラー番号:5)が発生してしまい、削除ができません。丸一日悩みましたが、どこがいけないのかわかりません。実際のコードを下記に載せますので、いけない点をご指摘頂けないでしょうか?
よろしくお願いいたします。


*************************************************************
Private Declare Function OpenPrinter Lib "winspool.drv" Alias "OpenPrinterA" _
(ByVal PrinterName As String, ByRef hPrinter As IntPtr, ByVal pDefault As PRINTER_DEFAULTS) As Boolean

Private Declare Function ClosePrinter Lib "winspool.drv" _
(ByVal hPrinter As IntPtr) As Int32

Public Declare Function DeletePrinter Lib "winspool.drv" _
(ByVal hPrinter As IntPtr) As Int32
Structure PRINTER_DEFAULTS
Dim pDatatype As Integer
Dim pDevMode As Integer
Dim DesiredAccess As Integer
End Structure
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 Boolean = (STANDARD_RIGHTS_REQUIRED Or PRINTER_ACCESS_ADMINISTER Or PRINTER_ACCESS_USE)

Public sub Main()
Dim hPrinter As IntPtr
Dim udtPrinterDefaults As PRINTER_DEFAULTS
'プリンタアクセス権を指定
With udtPrinterDefaults
.DesiredAccess = PRINTER_ALL_ACCESS
End With
'プリンタのハンドルを取得する
If Not OpenPrinter("HITACHI SP-Component", hPrinter, udtPrinterDefaults) Then
MsgBox("[" & Err.LastDllError & "] " & Err.Description, MsgBoxStyle.Critical + MsgBoxStyle.OKOnly, "エラー")
End If
'プリンタキューの削除
Dim x As Int32 = DeletePrinter(hPrinter)
If x = 0 Then
MsgBox("[" & Err.LastDllError & "] " & Err.Description, MsgBoxStyle.Critical + MsgBoxStyle.OKOnly, "エラー")
End If
'プリンタのハンドルを閉じる
ClosePrinter(hPrinter)
End Sub
*************************************************************
■No26997に返信(ぷりんさんの記事)
> 初めて投稿させて頂きます。よろしくお願いします。
>
> プリンタドライバのインストール、アンインストールを行うプログラムを作成しています。プリンタキューを削除するのに、APIのDeletePrinter関数を使っていますが、アクセス拒否(GetLastError()エラー番号:5)が発生してしまい、削除ができません。丸一日悩みましたが、どこがいけないのかわかりません。実際のコードを下記に載せますので、いけない点をご指摘頂けないでしょうか?
> よろしくお願いいたします。

> Public Const PRINTER_ALL_ACCESS As Boolean = (STANDARD_RIGHTS_REQUIRED Or PRINTER_ACCESS_ADMINISTER Or PRINTER_ACCESS_USE)

これの型が Boolean になっているのは変じゃないでしょうか。
PRINTER_DEFAULTS 型の DesireAccess は Integer 型なので、それに合わせないといけない気がしますが。

>> Public Const PRINTER_ALL_ACCESS As Boolean = (STANDARD_RIGHTS_REQUIRED Or PRINTER_ACCESS_ADMINISTER Or PRINTER_ACCESS_USE)
>
> これの型が Boolean になっているのは変じゃないでしょうか。
> PRINTER_DEFAULTS 型の DesireAccess は Integer 型なので、それに合わせないといけない気がしますが。

ぽぴ王子さん、回答ありがとうございます。
なるほど、気づきませんでした。ご指摘のとおり、修正して実行しましたが発生エラーは以前と同じでした。

引き続きよろしくお願いいたします。
■No26997に返信(ぷりんさんの記事)
> 実際のコードを下記に載せますので、いけない点をご指摘頂けないでしょうか?
検証できる環境が手元に無いので、机上デバッグのみで指摘していきます。


> アクセス拒否(GetLastError()エラー番号:5)が発生してしまい、
提示されたコードでは、Err.LastDllError を利用されているようですので、
既に御存知なのかとは思いますが、VB からは GetLastError API ではなく、
Err.LastDllError もしくは Marshal.GetLastWin32Error() を利用すべきです。


> Private Declare Function OpenPrinter Lib "winspool.drv" Alias "OpenPrinterA" _
> (ByVal PrinterName As String, ByRef hPrinter As IntPtr, ByVal pDefault As PRINTER_DEFAULTS) As Boolean
VB6 当時ならいざ知らず、Win9x 系 OS がほぼ使われなくなった現在では
「Alias "〜A"」の ANSI 系 API を利用する意味は無いはずです。
通常は、Unicode バージョンを利用するようにしましょう。


>     Private Declare Function ClosePrinter Lib "winspool.drv" _
>     (ByVal hPrinter As IntPtr) As Int32
OpenPrinter の戻り値を Boolean として、ClosePrinter/DeletePrinter の戻り値を
Int32 にしているのは何故でしょうか? 

これらの戻り値は、いずれも "BOOL" として定義されています。
.NET でマーシャリングする場合には、
 As <MarshalAs(UnmanagedType.Bool)> Boolean
とした方が良いでしょう。


>     Structure PRINTER_DEFAULTS
この型は文字列の受け渡しを伴うため、
 <StructLayout(LayoutKind.Sequential, CharSet:=CharSet.Auto)>
の属性を付与しておいた方が良いと思います。

それと、この型は Class で宣言してください。Structure でも間違いではありませんが、
その場合には OpenPrinter の第3引数を ByRef に変更する必要があります。

ちなみに、OpenPrinter に PRINTER_DEFAULTS を渡さない(NULL 指定)としたい場合には、
pDefault 引数を「ByVal + クラス」で宣言しておいて Nothing を渡すようにするか、
あるいは ByVal IntPtr のオーバーロードを用意しておき、IntPtr.Zero を渡すようにします。


>     Structure PRINTER_DEFAULTS
>         Dim pDatatype As Integer
>         Dim pDevMode As Integer
>         Dim DesiredAccess As Integer
>     End Structure
すべて Integer 型で宣言されていますね。
本来は、それぞれ "LPTSTR"、"LPDEVMODE"、"ACCESS_MASK" 型です。

最初のメンバーは As String または As IntPtr、
次のメンバーは As DEVMODE または As IntPtr です。
(ただし、DEVMODE の宣言は面倒なので、IntPtr で済ませてしまうのも手かも)
最後のメンバーは、As Integer で良さそうです(あるいは ACCESS_MASK 列挙体)。


>             MsgBox("[" & Err.LastDllError & "] " & Err.Description, MsgBoxStyle.Critical + MsgBoxStyle.OKOnly, "エラー")
本題とは無関係ですが、MsgBox の第2引数は「+ 演算子」ではなく「Or 演算子」で繋ぐべきです。


>         Dim x As Int32 = DeletePrinter(hPrinter)
>         ClosePrinter(hPrinter)
これだと、OpenPrinter が失敗したときにも hPrinter が処理されてしまう気がします。
魔界の仮面弁士さん、回答ありがとうございます。
ご指摘内容をふまえて修正しましたところ、正常動作するものを作ることができました。
まずかった箇所は
1.ClosePrinter/DeletePrinterの戻り値がBOOLでなかったこと
2.PRINTER_DEFAULTSの宣言がClassでなかったこと
でした。それぞれ部分的に修正しテストしてみたところ、どの修正がかけてもエラーが発生しました。

上記修正で正常動作するようになりましたが、あわせて
1.OpenPriterは、Unicodeバージョンを使用する
2.MsgBoxの第2引数で使用する演算子の修正
3.OpenPrinterで失敗した時には処理を終了
の変更を加えて、無事にプリンタドライバのインストール・アンインストールを行うプログラムを作成することができました。


本当に助かりました。的を射たご指摘とわかりやすい説明、ありがとうございました。お蔭様であきらめていた方法でプログラムを作成することができます。(納期が迫っていたので既にVC++で作成してしまっていましたが...)
.NETになってから、APIのところでは悩むことが多く、嫌になっていました。

以前から様々な掲示板等でお見かけしていましたが、まさか私が質問して答えて頂く事があるとは思っていませんでした。また何かありましたら、よろしくお願いいたします!!
解決済み!

DOBON.NET | プログラミング道 | プログラミング掲示板