So-net無料ブログ作成
  • ブログをはじめる
  • ログイン

.NETのCOMクラスライブラリからVBSに配列を返す [program]

.NETのCOMクラスライブラリに配列を返す関数を作成して
VBScriptからコールしたらエラーが発生した



仕事でVBS(WSH)を使っています。
稀に.NETのCOMクラスライブラリを作成してVBSからコールしなければならない事があります。
先日VB.NETのCOMクラスライブラリから文字列の配列を返そうとしたところハマッテしまいました。

まずこんなコードを書きました。

Public Function test03() As String()
        Dim TestArray As String() = {"A", "B", "C"}
        Return TestArray
End Function

この関数をVBSから呼び出すと


(ArrayTestがクラスライブラリ内のCOMクラスのオブジェクト)

Dim TestAry03
TestAry03 = ArrayTest.test03()
msgbox VarType(TestAry03)
msgbox UBound(TestAry03)
msgbox TestAry03(0)

VarTypeが8200 (8192(配列)+8(文字列))
UBoundが2
文字列の配列であること、要素が3であることは認識できていますが、
TestAry03(0)と配列の要素にアクセスしようとすると、
「型が一致しません。」というエラーになってしまいます。

あれ、COMの型になっていないのかな。
マーシャルとかいろいろ試してみますが動きません。
試しにVB6から呼び出してみると、要素にアクセスできましたので、
VBSの場合だけの問題だと特定できました。

マイクロソフトのドキュメントやネットを調べても有益な情報はありません。
別に配列でなくても、Dictionaryで返す方法がありますが、
わざわざキーを設定するほどの事はないし、
.NetからWSHのオブジェクトを返すのは迂遠です。
それならば、ArrayListを返すか、
クラスライブラリに型定義をしたコレクションを作ってしまえばいい。

ちなみに、ArrayListは.NET 4.5からVBSでは使えなくなってしまいました。
正確にいうと、.NET 4.5がしかインストールされていない環境では、
.NET 3.5かアクティベーション構成ファイルの設定が必要になります。
しかしこれは、ArrayListをクリエイトできないだけで、
.NET 4.5で作成したCOMクラスライブラリから返されたArrayListにアクセスする事が可能です。

ActiveX(VB6)ではどうだったか?


ここで、VB6の頃を思い出してみました。


Public Function Test03() As String()
    Dim TestArray(2) As String
    TestArray(0) = "A"
    TestArray(1) = "B"
    TestArray(2) = "C"
    
    Test03 = TestArray
End Function


このようなコードをVBSから呼び出し配列の要素にアクセスすると、
やはりエラーになってしまいます。

VB6ではバリアント型の配列として返す必要がありました。

Public Function Test04() As Variant()
    Dim TestArray(2) As Variant
    TestArray(0) = "A"
    TestArray(1) = "B"
    TestArray(2) = "C"
    
    Test04 = TestArray
End Function
※戻り値の宣言は As Variant()でもAs Variantでも動作する


VBScriptの配列はバリアント型の配列


そうです、VBSの配列はバリアント型である事を忘れていました。
これでVB.NETではどうすればいいのかが分かりました。


Public Function test04() As Object()
        Dim TestArray As Object() = {"A", "B", "C"}
        Return TestArray
End Function


Objectの配列として返せばよかったのです。
このようにすると、VarTypeは8204、
要素にアクセスすると、文字列を取得することができるようになりました。



nice!(15)  コメント(0) 

.Net Framework 4.5では VBSからArrayListが使用できない [program]

Windows10で実行したスクリプトでArrayListの作成がエラーになった



WSHには、連想配列(Dictionary)は存在しますが、
単純なコレクションは存在しません。
そこで、.NetのSystem.Collection.ArrayListを使用する事があります。

先日、インストール直後のWindows10でArrayListを使用したスクリプトを実行したところ、
ArrayListのクリエイトでエラーが発生しました。
同時に.Net Framework 3.5をインストールするように求めるメッセージが表示されました。

ネットワークに問題があったのか、
このメッセージで「この機能をダウンロードしてインストールをする」を選択したところ、
ダウンロードがエラーになってしまいました。

プレインストールの場合などは異なるかもしれませんが、
クリーンインストールしたWindows10では、
.Net Framework 4.5はインストールされていますが、
.Net Framework 3.5はインストールされていないようです。

.Net Framework 3.5のインストールは「Windows機能の有効化または無効化から.Net Framework 3.5の有効化」から行うことができます。

このとき、私の場合と同じようにエラーになる場合があります。
このあたりは検索すれば同じような記事がいくらでも出てきますが、
まずは、マイクロソフトの情報に目を通しておくとよいでしょう。
Windows 8、Windows 8.1、および Windows 10 への .NET Framework 3.5 のインストール
.NET Framework 3.5 インストール時のエラー: 0x800F0906、0x800F081F、0x800F0907

インストールメディアを利用してNET Framework 3.5をインストールしたことろ、
スクリプトが動作するようになりました。

.Net Framework 4.5では、
VBSからのArrayListの使用がサポートされなくなったのか、
ネットを検索しても
「ArrayListが使えるよ」という同じような記事はいくらでもあるのに、
ArrayListが使えないという記事が見つかりませんでした。

唯一見つけたのはMSDNのブログの記事
Usage of .NET Collections types in VBScript is not supported after .NET 4.5

この記事によると、新規インストールしたWindows Server 2012では動作しないとありますので、
.Net Framework 4.5からサポートされなくなったと言えそうです。
.Net Framework 4で動作するかどうかは環境がないので確かめられませんでした。

ちなみに、
OSと.Net Frameworkの関係についても、検索をすればいくらでも同じような記事が見つかりますが、
まず参考にするべきはマイクロソフトの情報でしょう。
.NET Framework のバージョンおよび依存関係

それにしても、
Windows8からは.Net Framework 4.5になっているので、
ネット上にあれだけの「ArrayListが使えるよ!」という記事があるのに、
「ArrayListが使えななくなったよ!」という記事がない事が解せません。
そもそもVBSがかなり特殊な環境になっている事はたしかです。
Windows8、8.1を回避してWindows7を使い続けている企業が多いので、
まだ顕在化していないのかもしれません。

しかし、Windows7のサポート切れを前に、Windows10対応が急務となっているので、
これから問題に直面することはあるでしょう。


「.Net Framework 3.5を入れればいいだけ」だが



確かにそれだけの事なのですが、
手間を惜しんだりPCの環境を変えたくないとして、それだけの事を嫌がる企業はわりと多く、
ユーザーに手間や条件をつけると嫌な顔をされたり拒否されるので、
それだけの事を動作環境として上げられないベンダーはかなり多いと思います。

もしこれからスクリプトを作成するなら、ArrayListを使わないか、
前述のMSDNのブログの記事
にあるように、ラッパーを作るのが良いかもしれません。

アクティベーション構成ファイル


も一つ、
前述のMSDNのブログの記事が挙げていたのは、アクティベーション構成ファイルを使う方法。

詳細はMSDNの記事を見ていただくとして、
簡単に言うと、
Net Framework 4.0以降を指定した[プログラム名].exe.activation_configというファイルを作成し任意のフォルダに置き、
環境変数COMPLUS_ApplicationMigrationRuntimeActivationConfigPathにそのフォルダを設定してから
プログラムを起動する。

VBSの場合、
MSDNのブログ
にあるとおり、cscript.exeもしくはwscript.exeを指定する事になります。

両記事とも、起動バッチで環境変数を指定していますが、
ユーザー環境変数およびシステム環境変数でも動作します。

また、.Net Framework 3.5をターゲットとしたアセンブリも、
同じ方法で動作させることができます。


nice!(9)  コメント(0) 

モーダルで表示したフォームをタスクバーに表示する TaskBarList.Lib [program]

VB6ではモーダルで表示したフォームはタスクバーに表示されません。

外部プログラムから起動されるプログラムをVBで作成する場合、
引数を処理する関係で、
スタートアップをMain関数にして、フォームをモーダルで表示することがあります。

以前、その様なコーディングを行ったところ、
フォームが表示されているのに、タスクバーに表示されない事が問題になりました。
フォームが別のプログラムのフォームの裏に隠れてしまうと、
タスクバーで切り替えできないからです。

このような事例は多かったはずなのですが、今まで対応した記録がなく、
私が対応方法を調べる事になりました。

比較的容易にITaskBarListインターフェイスを使用すれば実現できる事が分かり、
そのためのタイプライブラリがネットで無償公開されている事が分かりましたが、
仕事で使用するには、由来がはっきりしないモジュールは使用できません。

そこで、タイプライブラリを作成することにしました。

海外のサイトで公開されていたODL関係のサンプルソースを参考に、
ソースを作成したのだったと思います。
当時はODL言語についても調べたのですが、
時間がたっていることと、
何しろ突貫工事だったので、記憶もあいまいで、記録もまとまっていません。

それでも、今のところ動いています。

もし誤り等がありましたら、ご指摘ください。



TaskBarList.odl(ソース)
タイプライブラリ自身のUIDをGUIDGen.EXE等で作成して設定してください。

[
   uuid(タイプライブラリ自身のUID),
   helpstring("LibTaskbarList"),
   version(1.00)
]
library LibTaskbarList {
	importlib("stdole2.tlb");
	
	interface ITaskbarList;

	
	[
		odl,
		uuid(56FDF342-FD6D-11d0-958A-006097C9A090),
		helpstring("ITaskbarList interface")
	]
	
	interface ITaskbarList : IUnknown {
		
		HRESULT HrInit();
		
		HRESULT AddTab([in] LONG hwnd);
		
		HRESULT DeleteTab([in] LONG hwnd);
		
		HRESULT ActivateTab([in] LONG hwnd);
		
		HRESULT SetActiveAlt([in] LONG hwnd);
	};
	
	[
		uuid(56FDF344-FD6D-11D0-958A-006097C9A090),
		helpstring("TaskbarList class")
	]
		coclass TaskbarList {
		[default] interface ITaskbarList;
	};

};


MKtyplib.exeでタイプライブラリを作成します。
MKtyplib.exeはVisual StudioのVCフォルダのBinフォルダにあるはずです。

MKTYPLIB.EXE TaskBarList.odl /nocpp /tlb TaskBarList.tlb


VBからTaskBarList.tlbを参照設定すれば使用できます。

リファレンス
HrInit()
	クラスの初期化を行う。
	TaskBarListクラスのインスタンスを作成した後、一度だけ行ってください。

AddTab(hWnd)
	hWnd・・・ウインドウハンドル
	指定したウインドウをタスクバーに追加する

DeleteTab(hWnd)	
	hWnd・・・ウインドウハンドル
	指定したウインドウをタスクバーから削除する

ActivateTab(hWnd)	
	hWnd・・・ウインドウハンドル
	指定したウインドウをアクティブにするタブもアクティブになる




サンプル
'--Module1
Sub Main()
	Dim frm1 as Form1
	Set frm1 = New Form1
	frm1.Show vbModel
End Sub

'--Form1
Private TaskBarList As TaskBarList

Private Sub Form_Load()
	Set TaskBarList = New TaskBarList
	'初期化
	TaskBarList.HrInit
	'タスクバーに追加
	TaskBarList.AddTab Me.hWnd
End Sub

Private Sub Form_QueryUnload(Cancel As Integer, UnloadMode As Integer)
    	'タスクバーから削除
	TaskBarList.DeleteTab Me.hWnd
	Set TaskBarList = Nothing
End Sub


※プログラム関係の記事がたまったら別ブログにしようと思います。

DataGridViewのコンボボックスをDELキーでクリアしたい [program]

カスタマイズしていたパッケージソフトのコントールが不具合で使用できず、
急遽標準のグリッドに置き換えることになった。
なるべく動作を合わせなくてはならない。

「コンボボックス表示されるセルの値をDELキーでクリアする」という仕様で行き詰った。

DataGridViewのEditingControlShowingイベントでDataGridViewComboBoxEditingControlを取得して、
そのKeyDownイベントでSelectedIndex = -1を設定するところまでは、すぐに思いついた。

しかし、すでにセルに値が設定されていると、
フォーカスが離れた後に元の値に戻ってしまうのだ。
これには頭を抱えてしまった。

結局、編集終了後のCellEndEditイベントで、
SelectedIndex = -1ならセルに空文字を設定することにした。
その後DataGridViewComboBoxEditingControlへの参照をクリアしてしまうので、
CellEndEditイベントでは、DataGridViewComboBoxEditingControlへの参照が存在するかどうかをチェックすることにした。

サンプルはVB2008Exp、グリッドはアンバウンドです。
仕事でExpて悲しいでしょ・・・

''' DataGridViewに表示されるDataGridViewComboBox
Private WithEvents DataGridViewComboBox As DataGridViewComboBoxEditingControl = Nothing

'''カラムの列挙
Private Enum GridCol As Integer
	Text = 0
	Value = 1
End Enum

''' DataGridViewにDataGridViewComboBoxが表示されたら取得する
Private Sub DataGridView1_EditingControlShowing(ByVal sender As Object, _
	ByVal e As System.Windows.Forms.DataGridViewEditingControlShowingEventArgs) _
	Handles DataGridView1.EditingControlShowing
	
	If TypeOf e.Control Is DataGridViewComboBoxEditingControl Then
		Dim DGView As DataGridView = DirectCast(sender, DataGridView)

		Me.DataGridViewComboBox = _
		DirectCast(e.Control,DataGridViewComboBoxEditingControl)
	End If
End Sub


''' DataGridViewComboBoxのイベントでDELキーを受け、リストをクリアする
Private Sub DataGridViewComboBox_KeyDown(ByVal sender As Object, _
	ByVal e As System.Windows.Forms.KeyEventArgs) _
	Handles DataGridViewComboBox.KeyDown
	
	If e.Alt = False AndAlso e.Control = False AndAlso e.KeyCode = Keys.Delete Then
		Dim DGVComboBox As DataGridViewComboBoxEditingControl = _
		DirectCast(sender, DataGridViewComboBoxEditingControl)
		DGVComboBox.SelectedIndex = -1
	End If
End Sub


'''編集終了時にリストが選択されていなければ、
'''セルに空文字を入れて、 DataGridViewComboBoxを初期化
Private Sub DataGridView1_CellEndEdit(ByVal sender As Object, _
	ByVal e As System.Windows.Forms.DataGridViewCellEventArgs) _
	Handles DataGridView1.CellEndEdit
	
	If Not Nothing Is Me.DataGridViewComboBox Then
		If Me.DataGridViewComboBox.SelectedIndex = -1 Then
			Dim DGView As DataGridView = _
			DirectCast(sender, DataGridView)
			DGView.Rows(e.RowIndex).Cells(GridCol.Value).Value = ""
		End If

		Me.DataGridViewComboBox = Nothing
	End If
End Sub


※プログラム関係の記事がたまったら別ブログにしようと思います。