C# と VB.NET の質問掲示板

ASP.NET、C++/CLI、Java 何でもどうぞ

C# と VB.NET の入門サイト

Marshal.StructureToPtrで落ちる

[トピック内 6 記事 (1 - 6 表示)]  << 0 >>

■92179 / inTopicNo.1)  Marshal.StructureToPtrで落ちる
  
□投稿者/ いとこんにゃく (1回)-(2019/08/30(Fri) 15:16:49)

分類:[ASP.NET (VB)] 

2019/08/30(Fri) 15:17:34 編集(投稿者)
2019/08/30(Fri) 15:17:26 編集(投稿者)

OSをWindows7 32bit⇒ Windows10 64bitに変更するに伴いアプリのコンバートをしました。
印刷コマンドを実行→ CreateObjectにて InternetExplorer.Application を作成 → htmlをオープン → 印刷設定部でアプリが落ちてしまします。

開発環境は以下の通りです。
Windows10 64bit
Visual Studio 2019 Pro
ターゲットフレームワーク .NET Framework 4.7.2

 しかし、ターゲットフレームワーク .NET Framework 4にしてビルドすると問題なく印刷できます。

 4.7.2に変更するに伴い仕様が大きく変わったのでしょうか?
 


******** 以下ソース (印刷設定部のみ)**********
Dim PrinterHandle As Long 'プリンタオブジェクトハンドル
Dim Result As Long
Dim Needed As Integer 'バッファサイズ
Dim pPrinterInfo As IntPtr
Dim PrinterInfo As PRINTER_INFO_2 = Nothing
Dim pFullDevMode As IntPtr
Dim MyDevMode As DEVMODE = Nothing
Dim iExecRtn As Integer = 0 'モジュール戻値
Dim pDefault As New PRINTER_DEFAULTS 'プリンタデフォルト情報

Try
'プリンタアクセス権設定
With pDefault
.DesiredAccess = PRINTER_ALL_ACCESS '振るアクセス!!
End With

'プリンタオープン
Result = OpenPrinter(PrinterName, PrinterHandle, pDefault)
If Result <> 0 Then 'オープン成功?
'次処理へ
Else '失敗..
Throw New Exception(String.Format("プリンタオープン処理失敗"))
End If

'バッファサイズ取得
GetPrinter(PrinterHandle, 2, 0, 0, Needed)
If Needed > 0 Then '取得成功?
'次処理へ
Else '失敗..
Throw New Exception(String.Format("プリンタバッファ取得処理失敗"))
End If

'メモリ割当
pPrinterInfo = Marshal.AllocHGlobal(Needed)

'プリンタ情報読取り
Result = GetPrinter(PrinterHandle, 2, pPrinterInfo, Needed, Needed)
If Result <> 0 Then '読取り成功?
'次処理へ
Else '失敗..
Throw New Exception(String.Format("プリンタ情報取得処理失敗"))
End If

'構造体メモリデータコピー
PrinterInfo = CType(Marshal.PtrToStructure(pPrinterInfo, GetType(PRINTER_INFO_2)), PRINTER_INFO_2)
pFullDevMode = PrinterInfo.pDevMode

MyDevMode = CType(Marshal.PtrToStructure(pFullDevMode, GetType(DEVMODE)), DEVMODE)

'プリンタ詳細設定(ココで用紙枚数,サイズ,その他諸々設定)
If MyDevMode.dmFields And DM_COPIES Then 'プリンタ情報書込み可能か?
  MyDevMode.dmCopies = 1 '出力枚数設定

'メモリに枚数設定を行った情報構造体をデータコピー
Marshal.StructureToPtr(MyDevMode, pFullDevMode, True)
PrinterInfo.pDevMode = pFullDevMode
Marshal.StructureToPtr(PrinterInfo, pPrinterInfo, True) ←■■■■ここでアプリが落ちます!!

・・・・・・
引用返信 編集キー/
■92181 / inTopicNo.2)  Re[1]: Marshal.StructureToPtrで落ちる
□投稿者/ 魔界の仮面弁士 (2343回)-(2019/08/30(Fri) 15:40:42)
2019/08/30(Fri) 15:54:53 編集(投稿者)

No92179 (いとこんにゃく さん) に返信
掲示板への新規投稿時の注意事項に、
 『半角カナは使用しないでください。文字化けの原因になります。』
と記載されていたかと思います。次回以降、ご配慮いただけますようお願いします。


> 分類:[ASP.NET (VB)] 
> 印刷コマンドを実行

ASP.NET アプリケーションのサーバー側で印刷設定するのでしょうか。
それともクライアント側での印刷なのでしょうか。

サーバー側だとしたら、そのアプリケーションプールの実行ユーザーに
プリンターへのアクセス権が必要になりそうです。クライアント側だとしたら
高度な設定はできないので、ASP.NET は Web サービス化した上で
デスクトップ アプリケーションとして構築することをお奨めします。


> 印刷コマンドを実行→ CreateObjectにて InternetExplorer.Application を作成
サーバーサイドでの CreateObject("InternetExplorer.Application") の利用は推奨されません。
クライアントサイドでは、そもそも VB は使えず、使えたとしても VBScript ですし、
Internet Explorer であっても、VBScript の動作は廃止の方向に向かっています。

……本当に ASP.NET アプリなのですか?


> OSをWindows7 32bit⇒ Windows10 64bitに変更するに伴いアプリのコンバートをしました。
64bit 環境で動作させるに当たり、そのアプリケーションは
WOW64 の 32bit プロセスで動作させているのでしょうか。
それとも 64bit プロセスで動作させているのでしょうか。

API を使う場合、本来は 64bit/32bit 両対応のコードとしてコーディングすべきですが、
その切り替えに自信が無いのなら、64bit OS 上であっても、アプリケーションは
32bit モードで動作させることをお奨めしておきます。


> Dim PrinterHandle As Long
何故 Long なのですか? これが VBA なら、Long にすることもありますが、
Handle というからには、VB.NET なら普通は IntPtr であろうかと思うのですが…。


> Dim PrinterInfo As PRINTER_INFO_2 = Nothing
構造体として定義した場合と、クラスとして定義した場合とで
API の呼び方が微妙に変わってくるのでご注意あれ。



> With pDefault
>   .DesiredAccess = PRINTER_ALL_ACCESS
> End With
1 行だけなら、With を使わない方がスマートな気が。


> If MyDevMode.dmFields And DM_COPIES Then
ここのコードも不自然ですね…。

コードを移植する前に、元の Win7(32bit) 環境において
「Option Explicit On」でのコンパイルが通るように修正してから
Win10(64bit) への移植を行われることをお奨めします。


> Marshal.StructureToPtr(PrinterInfo, pPrinterInfo, True) ←■■■■ここでアプリが落ちます!!
PRINTER_INFO_2 および DEVMODE 構造体の定義が
どうなっているのかが気になります。

構造体の宣言を見せてもらうことはできますか?
(StructLayout 属性の指定も含めてください)
引用返信 編集キー/
■92183 / inTopicNo.3)  Re[2]: Marshal.StructureToPtrで落ちる
□投稿者/ いとこんにゃく (2回)-(2019/08/30(Fri) 16:06:01)
すみません ASP.net ではなく、VB.netでした。
引用返信 編集キー/
■92186 / inTopicNo.4)  Marshal.StructureToPtrで落ちる
□投稿者/ いとこんにゃく (3回)-(2019/08/30(Fri) 16:11:09)
2019/08/30(Fri) 17:15:09 編集(投稿者)

すみません 焦ってASP.netを選択してしまいました。 VB.net の誤りです。
ちなみにターゲット CPU は x86です。

以下、関数および構造体の宣言です。

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

<DllImport("winspool.drv", SetLastError:=True)>
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

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

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

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

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


<StructLayout(LayoutKind.Sequential, CharSet:=CharSet.Auto)>
Friend Structure DEVMODE
''' <summary> </summary>
<VBFixedString(32), MarshalAs(UnmanagedType.ByValTStr, SizeConst:=32)>
Dim dmDeviceName As String
Dim dmSpecVersion As Short
Dim dmDriverVersion As Short
Dim dmSize As Short
Dim dmDriverExtra As Short
Dim dmFields As Integer
Dim dmOrientation As Short
Dim dmPaperSize As Short
Dim dmPaperLength As Short
Dim dmPaperWidth As Short
Dim dmScale As Short
Dim dmCopies As Short
Dim dmDefaultSource As Short
Dim dmPrintQuality As Short
Dim dmColor As Short
Dim dmDuplex As Short
Dim dmYResolution As Short
Dim dmTTOption As Short
Dim dmCollate As Short
<VBFixedString(32), MarshalAs(UnmanagedType.ByValTStr, SizeConst:=32)>
Dim dmFormName As String
Dim dmLogPixels As Short
Dim dmBitsPerPel As Short
Dim dmPelsWidth As Integer
Dim dmPelsHeight As Integer
Dim dmDisplayFlags As Integer
Dim dmDisplayFrequency As Integer
End Structure
引用返信 編集キー/
■92208 / inTopicNo.5)  Re[4]: Marshal.StructureToPtrで落ちる
□投稿者/ 魔界の仮面弁士 (2346回)-(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( ) でビットフラグを判定する手法もあります。
引用返信 編集キー/
■92209 / inTopicNo.6)  Re[5]: Marshal.StructureToPtrで落ちる
□投稿者/ いとこんにゃく (4回)-(2019/09/02(Mon) 09:17:13)
魔界の仮面弁士 様
ありがとうございました。
指摘いただいた箇所をすべて修正しましたところ印刷されるようになりました。
細かい文法まで指摘して頂いて大変勉強になりました。
お忙しいところ本当にありがとうございました。

解決済み
引用返信 編集キー/

このトピックをツリーで一括表示


トピック内ページ移動 / << 0 >>

このトピックに書きこむ