■92208 |
Re[4]: Marshal.StructureToPtrで落ちる |
□投稿者/ 魔界の仮面弁士 -(2019/09/02(Mon) 03:27:37)
| 2019/09/02(Mon) 04:15:36 編集(投稿者)
■No92186 (いとこんにゃく さん) に返信 > <DllImport("winspool.drv", CharSet:=CharSet.Auto, SetLastError:=True)> > Private Shared Function OpenPrinter(
Declare 宣言と DllImport 属性指定が混在しているので不揃いな印象を受けましたが、それはさておき。
問題点は 2 つあります。一つ目は、CharSet の取り扱いが出鱈目であること。
OpenPrinter の宣言を見ると、CharSet.Auto が指定されていますので、Win7 にしても Win10 にしても、 Unicode 版である OpenPrintW API が呼び出されることになりますね。
> Private 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
一方こちらは、Unicode 版の SetPrinterW ではなく、ANSI 版である SetPrinterA API を呼び出しているようです。 その上で、上記 API で使われる構造体は、CharSet.Auto が指定されてしまっています。これは不自然でしょう。
> <StructLayout(LayoutKind.Sequential, CharSet:=CharSet.Auto)> > Friend Structure PRINTER_INFO_2
さらに加えて、GetPrinter API の宣言では、CharSet 指定なしになっています。これも不揃いですね。 省略した場合、CharSet.Ansi の呼び出しを意味します。 > <DllImport("winspool.drv", SetLastError:=True)> > Private Shared Function GetPrinter(
文字列処理を伴う API の多くは、〜W 系と 〜A 系の二種類を持ちますが、 CharSet 指定はいずれかに揃えておかねばなりません。
加えて述べれば、現状の環境において "〜A" 系の API に揃えるメリットはほぼありません。 昔の Windows には W 系を実装していない物もありましたが、現行のものは全て W 系を実装していますので、 基本的には Auto 指定、手抜き実装で済ますなら Unicode 指定で統一するのが良いでしょう。 http://www5b.biglobe.ne.jp/~yone-ken/VBNET/special/sp06_GetPrivateProfileString.html http://www5b.biglobe.ne.jp/~yone-ken/VBNET/Reference/ref2_GetPrivateProfileString.html
ということで、今回の場合、 > Private Declare Function SetPrinter Lib "winspool.drv" Alias "SetPrinterA" ( ではなく、 Private Declare Unicode Function SetPrinter Lib "winspool.drv" Alias "SetPrinterW" あるいは Private Declare Auto Function SetPrinter Lib "winspool.drv" ( にされることをお奨めします。(他が Auto 指定なので後者を推奨)
また、GetPrinter の宣言についても、CharSet.Auto を付与しておく必要があります。 ※ DllImport を使うか Declare を使うかはお好みで。
もう一つの問題点はこの部分。
> Marshal.StructureToPtr(MyDevMode, pFullDevMode, True) > PrinterInfo.pDevMode = pFullDevMode > Marshal.StructureToPtr(PrinterInfo, pPrinterInfo, True) ←■■■■ここでアプリが落ちます!!
第三引数に True を指定するという事は、データをコピーする前に、サブ構造体や参照が 解放されるという事です。今回のケースで Trueを指定すべきでは無いと思います。
https://stackoverflow.com/questions/3339979/marshal-structuretoptr-crashes-visual-studio https://docs.microsoft.com/ja-jp/dotnet/api/system.runtime.interopservices.marshal.structuretoptr?view=netframework-4.8 https://docs.microsoft.com/ja-jp/dotnet/framework/interop/copying-and-pinning?view=netframework-4.8
あるいはメモリをピン留めしておいて、該当メンバーの位置を Marshal.WriteInt16 等で直接書き換える手法もあるかも。
とりあえず大きな問題点はそれぐらいですが、他にも気になる点があるので 見つけたものを挙げておきます(直ちに問題とはならない軽微なミスも含みますが)。
・ No92179 では、ClosePrinter や Marshal.FreeHGlobal の呼び出しが見当たりませんでしたが、 大丈夫でしょうか。(恐らく Finally 句で処理されていると思いますが)
・PrinterInfo = CType(Marshal.PtrToStructure(pPrinterInfo, GetType(PRINTER_INFO_2)), PRINTER_INFO_2) を実行した直後に一時停止して、構造体の文字列メンバーに正しい値が入っているかを 確認してください。ここが文字化けしているようだと NG です。 さらに言えば、プリンター名に「Shift_JIS に無い Unicode 文字」を付けておいた状態で 正しく呼び出せるようになっていることが望ましいです。
・今回のケースでは PRINTER_ALL_ACCESS 権限でのフルアクセスが指定されていますが、 強い権限が必要な場合、管理者モードでの実行が必要になるかもしれません。 https://social.msdn.microsoft.com/Forums/ja-JP/c1fc9393-a707-4f7c-b801-0de340cd3888/1247912540125111249012523-12469125401249912473?forum=vbgeneralja
・OpenPrinter の第二引数が「ByRef hPrinter As IntPtr」であるのに対して、 No92179 ではそこに、「Dim PrinterHandle As Long」が渡されているのは誤りですね。 今回は x86 ビルドだそうなので、Long 型の出番はほぼ無いはず。 IntPtr 型の変数を渡すようにしましょう。※あるいは SafeHandle 派生クラスを使う手法もあり。
・OpenPrinter の戻り値を「As Boolean」としているにもかかわらず、 No92179 ではそれを「Dim Result As Long」で受けています。Boolean に揃えましょう。 (API を扱うなら、Option Strict On でコンパイルが通るようにコーディングすべき)
・GetPrinter の第三引数が「ByVal hPrinter As IntPtr」であるのに対して、 「GetPrinter(PrinterHandle, 2, 0, 0, Needed)」と、Integer の 0 を渡しています。 ここには IntPtr.Zero が渡されるべきかと。
・GetPrinter の第四引数である「_In_ DWORD cbBuf」を「ByVal cbBuf As Integer」に 翻訳したのに対し、PRINTER_INFO_2 構造体の「DWORD Attributes」以降のメンバーは 「As System.UInt32」で宣言されている点が気にかかりました。 DWORD を Integer / UInteger / Int32 / UInt32 のいずれで受けるかは任意ですが、 明確に使い分けているわけでは無さそうなので、コードに不揃い感があります。
・「If MyDevMode.dmFields And DM_COPIES Then」ではなく 「If (MyDevMode.dmFields And DM_COPIES) <> 0 Then」などとしましょう。 あるいは dmFields を <Flags> な Enum 型で宣言した上で、 MyDevMode.dmFields.HasFlags( ) でビットフラグを判定する手法もあります。 |
|