[VB.Net] gsdll32.dllを使用する

大量のPDFをJpegなどの画像を変換するプログラムを書いた時に、VB.Net(VB2008)からC言語で書かれたgsdll32.dllに引数を渡す際には、文字コードの変換やら、.Netの文字列を文字型のバイト列に変える必要があるようです。
まだまだ、GChandle構造体とか「アンマネージドメモリ」とか、理解できているわけではないのですが、一応思い通りの動作をしてくれるところまで至ったのでメモを兼ねてコードを貼り付けさせて頂きます。

まずは、
“DLL Import” や「GCHandle構造体」を使うために

Imports System.Runtime.InteropServices

が冒頭に書かれているとして、次にDLL中の関数を使うために、

<DllImport("gsdll32.dll", EntryPoint:="gsapi_new_instance")> _
Private Shared Function CreateAPIInstance(ByRef pinstance As IntPtr, ByVal caller_handle As IntPtr) As Integer
End Function
<DllImport("gsdll32.dll", EntryPoint:="gsapi_init_with_args")> _
Private Shared Function InitAPI(ByVal instance As IntPtr, ByVal argc As Integer, ByVal argv As IntPtr) As Integer
End Function
<DllImport("gsdll32.dll", EntryPoint:="gsapi_exit")> _
Private Shared Function ExitAPI(ByVal instance As IntPtr) As Integer
End Function
<DllImport("gsdll32.dll", EntryPoint:="gsapi_delete_instance")> _
Private Shared Sub DeleteAPIInstance(ByVal instance As IntPtr)
End Sub

を書いておきます。

さて、gsdll32.dllは
1.gsapi_new_instance (CreateAPIInstance)
2.gsapi_init_with_args (InitAPI)
3.gsapi_exit (ExitAPI)
4.gsapi_delete_instance (DeleteAPIInstance)
の順番で呼び出す必要があるのですが、gsapi_init_with_args の引数で出力するファイル名などを文字列で渡してやらないといけません。
この文字列というのがくせ者で、C言語には文字列は文字の配列として扱っています。
さらに、VB.netでは文字コードがUnicode、CではANSIとなっているので文字コードを変換してあげないと行けません。

と、途中をすっとばして、こんな感じで変換してみたら動いてくれました。

	Private Sub Hoge()
		Dim sArgv(10) As String
		Dim ipGs As IntPtr
		Dim oAnsiArgs() As Object
		Dim ipArgs() As IntPtr
		Dim ipArg As IntPtr
		Dim gch() As GCHandle
		Dim gchArgs As GCHandle

		sArgv(0) = "pdf2img"
		sArgv(1) = "-dNOPAUSE"
		sArgv(2) = "-dBATCH"
		sArgv(3) = "-dSAFER"
		sArgv(4) = "-r200"
		sArgv(5) = "-sDEVICE=pngmono"
		sArgv(6) = "-sOutputFile= 出力する画像ファイル "
		sArgv(7) = "入力するファイル"

		ReDim oAnsiArgs(sArgv.Length - 1)
		ReDim ipArgs(sArgv.Length - 1)
		ReDim gch(sArgv.Length - 1)

		For i As Integer = 0 To sArgv.Length - 1
			'①文字コード変換
			oAnsiArgs(i) = System.Text.Encoding.GetEncoding(932).GetBytes(sArgv(i))
			'②変換した文字列をひとかたまりに集める
			gch(i) = GCHandle.Alloc(oAnsiArgs(i), GCHandleType.Pinned)
			'③gchの先頭のアドレス(ポインタ)
			ipArgs(i) = gch(i).AddrOfPinnedObject()
		Next i
		gchArgs = GCHandle.Alloc(ipArgs, GCHandleType.Pinned)
		ipArg = gchArgs.AddrOfPinnedObject()

		CreateAPIInstance(ipGs, IntPtr.Zero)
		InitAPI(ipGs, sArgv.Length, ipArg)
		ExitAPI(ipGs)
		DeleteAPIInstance(ipGs)
	End Sub

参考:
C言語で作成されたDLLをVB.NETにて呼び出す方法
GhostScript 公式の解説(英語)
GCHandle 構造体
http://www.experts-exchange.com/Programming/Languages/C_Sharp/Q_20762995.html


2013/4/22 追記
突然、上記のコードが動かなくなってしまいました。
GhostScript Ver9.0.7 のgsdll32.dllだと動かないようです。
Ver9.0.5のgsdll32.dllだったら問題なく動くので、引数の書式 or gsdll32.dllの依存関係が変わったのでしょうか?
時間が無くて調べられていないのですが、ご存じの方がいらっしゃいましたらお知らせください。


2014/1/16 追記
もしかしたら、この関係かも!?
http://bugs.ghostscript.com/show_bug.cgi?id=694720