WPFの二重起動でコマンドを受け取る〜いまなら間に合う?WPF

  • 2013.10.30 Wednesday
  • 12:33

前回は二重起動について書いたが、そのソースにはコマンドを受け渡すという機能は実装されていない。

2回目の立ち上げ時にコマンド(引数)がある場合、そのデータを1回目のWPFに渡す処理を追加しなければならない。

そこで、ネットで調べると、IPCチャンネルという機能を使ってメッセージをやりとりする方法が見つかった。

なるほど、Channels.Ipcという機能を使って受け渡しをすれば良いわけだ。

ところが、見つかるのはC#のソースばかりで、VBのソースはないので、例によってC#のソースをお手本に翻訳してみた。

特に変換は難しくはないのだが、C#にはあって、VBにはない、ハンドラにいきなり匿名でdelegateで突っ込む部分が悩ましい。

参考URLのソース抜粋
message.handler = delegate(string[] args)
{
    foreach (string s in args)
        mainWindow.listBox1.Items.Add(s);
};

この部分はVBでいうならSub関数を突っ込みたいところだが、同じように書くとソース誤りでエラーとなってしまう。
したがって、ReadCommandというSub関数を別途作ってReadCommandHandler Delegateを初期化する際、
AddressOfで接続してあげることになる。


実際にゴリゴリ書くとこんな感じになる。

■コマンド取得を必要とする場合のソース

 
Public Class Application
‌ 
    ・・・
‌ 
    ''' <summary>開始処理</summary>
    Private Sub Application_Startup(ByVal sender As Object, _
                                    ByVal e As System.Windows.StartupEventArgs) Handles Me.Startup
‌ 
        mMutex = New System.Threading.Mutex(False, MyAssembly)
‌ 
        If mMutex.WaitOne(0I, False= True Then '既に起動している
‌ 
            'StartupUriを空(削除しておく)
            mWindows = New MainWindow 'ここで手動で作成
            'IPCサーバーチャンネルの実装
            Dim srvChan As Channels.Ipc.IpcServerChannel = New Channels.Ipc.IpcServerChannel(MyAssembly)
            Dim srvMsg As ReadCommand
‌ 
            Call Channels.ChannelServices.RegisterChannel(srvChan, True'チャンネルの登録
            srvMsg = New ReadCommand
            srvMsg.Dispatcher = MyBase.Dispatcher
‌ 
            'C#のように匿名ができないのでAddressOfでハンドラを接続
            srvMsg.Handler = New ReadCommandHandler(AddressOf ReadCommand)
            Call RemotingServices.Marshal(srvMsg, "ReadCommand"'メッセージの受け取りURIの作成
‌ 
            '最初の起動時で取得されたコマンドはMainWindow等で処理する
            '(この例ではReadCommandというサブルーチンがあらかじめあるとする)
            Call mWindows.ReadCommand(e.Args)
            Call mWindows.Show() 'メインウインドを起動
‌ 
        Else '二重起動
‌ 
            Call MessageBox.Show("多重起動はできません", "多重起動", _
                                 MessageBoxButton.OK, MessageBoxImage.Exclamation)
            'ここで破棄
            Call mMutex.Close()
            mMutex = Nothing
‌ 
            'Try
            'IPCクライアントチャンネルの作成
            Dim cltChan As Channels.Ipc.IpcClientChannel = New Channels.Ipc.IpcClientChannel
            Dim crtMsg As ReadCommand
            Call Channels.ChannelServices.RegisterChannel(cltChan, True'チャンネルの登録
            'メッセージの送信URIの作成
            crtMsg = RemotingServices.Connect(GetType(ReadCommand), _
                                              String.Format("ipc://{0}/ReadCommand", MyAssembly))
            Call crtMsg.RaiseHandler(e.Args) 'イベントへ渡す
            'プログラム起動を中止
            Call Me.Shutdown() 'ここで閉じる
            'Catch ex As Exception
            '    Call IO.File.AppendAllText("Error.log", ex.Message & vbLf)
            'End Try
‌ 
        End If
    End Sub
‌ 
    ''' <summary>コマンド</summary>
    Private Sub ReadCommand(ByVal SourceCommand() As String)
        Call mWindows.ReadCommand(SourceCommand) '2番目以降に取得したコマンドを処理する
    End Sub
‌ 
    ''' <summary>終了処理</summary>
    Private Sub Application_Exit(ByVal sender As Object, _
                                 ByVal e As System.Windows.ExitEventArgs) Handles Me.Exit
        ・・・
    End Sub
‌ 
End Class
‌ 
'※VBの場合Namespaceはプロパティで設定されているので、C#のように宣言なしでデリゲート等を書ける
''' <summary>コマンドハンドラーデリゲート</summary>
Friend Delegate Sub ReadCommandHandler(ByVal SourceCommand() As String)
‌ 
''' <summary>読み取りクラス</summary>
Public Class ReadCommand
    Inherits System.MarshalByRefObject
‌ 
    ''' <summary>スレッド管理</summary>
    Private pDisp As System.Windows.Threading.Dispatcher
    ''' <summary>コマンドハンドラー</summary>
    Private mHandler As ReadCommandHandler
‌ 
    ''' <summary>スレッド管理</summary>
    Friend Property Dispatcher() As System.Windows.Threading.Dispatcher
        Get
            Return pDisp
        End Get
        Set(ByVal value As System.Windows.Threading.Dispatcher)
            pDisp = value
        End Set
    End Property
‌ 
    ''' <summary>コマンドハンドラー</summary>
    Friend Property Handler() As ReadCommandHandler
        Get
            Return mHandler
        End Get
        Set(ByVal value As ReadCommandHandler)
            mHandler = value
        End Set
    End Property
‌ 
    ''' <summary>イベント発生</summary>
    Public Sub RaiseHandler(ByVal SourceCommand() As String)
        Call Application.Current.Dispatcher.BeginInvoke(mHandler, New Object() {SourceCommand})
    End Sub
‌ 
    'InitializeLifetimeServiceをオーバーライドして無制限に
    Public Overrides Function InitializeLifetimeService() As Object
        Return Nothing '有効期限を無制限
        'Return MyBase.InitializeLifetimeService() '自動生成されるMyBase〜は削除してNothingを返す
    End Function
End Class

※「・・・」は前回のソース確認と同じなので、省略
 MainWindowにはReadCommandというMainWindow側で引数を処理するためのSub関数が書かれているものとする。


とりあえず、こんな感じでソースを付け足して、デバッグして2つ目の起動から
メッセージが受け取れればOKである。

続きを読む >>

関連URL
WPFによる開発覚え書き
WPFアプリの二重起動防止ではまったこと

デリゲート入門

関連投稿
WPFの二重起動をVBで書く〜いまなら間に合う?WPF
 

ちなみに、こんな難しいソースを書かなくても良さそうな感じもします。
単一インスタンスの検出のサンプル
C#|二重起動を禁止してコマンドライン引数を取得する

簡単にいうと、Windows.FormのStartupNextInstance機能を使ってWPFを起動するという考え方である。

まず、上に習ってEntryPoint.vbというソースファイルと下のソースをガリガリと書く
 
Imports Microsoft.VisualBasic.ApplicationServices
Imports System.Windows
‌ 
Public Class EntryPoint
‌ 
    ''' <summary>起動</summary>
    <STAThread()> _
    Public Shared Sub Main(ByVal args As String())
        Dim manager As SingleInstanceManager = New SingleInstanceManager
        Call manager.Run(args)
    End Sub
‌ 
End Class
‌ 
''' <summary>単一インスタンスベース</summary>
Public Class SingleInstanceManager
    Inherits WindowsFormsApplicationBase
‌ 
    ''' <summary>単一インスタンス</summary>
    Private WorkApp As SingleInstanceApplication
‌ 
    ''' <summary>初期化</summary>
    Public Sub New()
        MyBase.IsSingleInstance = True '単一インスタンスを有効
    End Sub
‌ 
    ''' <summary>初起動</summary>
    Protected Overrides Function OnStartup(ByVal e As ApplicationServices.StartupEventArgs) As Boolean
        ' First time app is launched
        Me.WorkApp = New SingleInstanceApplication
        Call Me.WorkApp.Run() 'MainWindowを起動
        Return False
    End Function
‌ 
    ''' <summary>2回目の立ち上げ次の引数取得</summary>
    Protected Overrides Sub OnStartupNextInstance(ByVal e As StartupNextInstanceEventArgs)
        Call MyBase.OnStartupNextInstance(e)
        Call Me.WorkApp.Activate(e.CommandLine.ToArray) 'MainWindowをアクティブ
    End Sub
‌ 
End Class
‌ 
''' <summary>単一インスタンスクラス</summary>
Public Class SingleInstanceApplication
    Inherits Application
‌ 
    ''' <summary>MainWindow</summary>
    Private mWindows As MainWindow = Nothing
    'Private mWindows As MainWindow = New MainWindow 'MainWindowを初期化
‌ 
    'Public Sub New()
    '    mWindows = New MainWindow 'MainWindowを初期化
    'End Sub
‌ 
    ''' <summary>アクティブ</summary>
    Public Sub Activate(ByVal SourceCommand() As String)
        Call MyBase.MainWindow.Activate() 'ウインドウを最前面へ
        Call mWindows.ReadCommand(SourceCommand)
    End Sub
‌ 
    ''' <summary>インスタンス起動</summary>
    Protected Overrides Sub OnStartup(ByVal e As Windows.StartupEventArgs)
        Call MyBase.OnStartup(e)
        mWindows = New MainWindow 'MainWindowを初期化
‌ 
        Call mWindows.ReadCommand(e.Args)
        Call mWindows.Show() '起動
‌ 
    End Sub
‌ 
End Class

ソースを書いていくといろいろとエラーメッセージが表示されるので、修正していく

ソースを書けばEntryPointというのがスタートアップに上がってくるようですが、
その気配がありません・・・。
WPF 二重起動

どうやっても無理っぽいので(やり方が分かる方はコメントくださいm(_ _)m)、

一度VSを閉じて、プロジェクトファイル(.vbprojファイル)をエディタで開いて直書きしてみます。
※改造に失敗してもいいようにコピーしておきましょう
 (プロジェクトだけでもいいし、ソリューション丸ごとでもいいし、とにかく切り戻せる状態に)

WPF 二重起動
プロジェクトファイルの先頭にあるPropertyObject内に、
名前空間.EntryPointと言った感じで、StartupObjectを追加してみる。

保存して立ち上げると・・・

WPF 二重起動

なんかうまくいったようです。

※この状態になるとMainWindow.xamlが消えてしまって、あとで切り戻しができなくなることがあります。


■補足
Sub Mainが2回宣言されています。というメッセージが出ていてもとりあえず無視します。

WPF 二重起動 VB

どうしても気になる場合(軽くデバッグしたいなど)は、EntryPointの方を有効にしたいので、
隠しファイル(すべてのファイルを表示)のobj¥Debug¥Application.g.vbのSub Mainをコメントアウトする。
※¥は半角
 Application.g.vbはEntryPointを作る前に、あらかじめデバッグしておくと生成されるようです。
 既に作った場合は、EntryPointのSub Mainを一度コメントアウトしてデバッグ

WPF 二重起動 VB

Application クラスのSub Main部分(下図)をコメントアウトします。
WPF 二重起動

ちなみに、g.vbのヘッダにもあるとおり、

このファイルへの変更は、以下の状況下で不正な動作の原因になったり、
コードが再生成されるときに損失したりします。


編集すると戻せなくなる場合がありますので、注意です。
ちなみに、この記事を書いている最中にg.vbファイルが消えて復元できなくなってしまいました。
(何度もデバッグやコンパイルを繰り返していると不必要と見なされたファイルが削除される)


つづいて、EntryPointで作成したソースで下記が出た場合は、

WPF 二重起動 VB
ベース class のアクセスをアセンブリの外側に展開しているため、Class Applicationから継承できません。

この場合は、隠しファイルのMyWpfExtension.vbの
WPF 二重起動 VB

Class Applicationを丸ごとコメントアウトしてしまう。
場合によっては、上のApplication.g.vbのクラスも丸ごとコメントアウト。
(これをしてしまうとApplication.g.vbが削除されてしまう可能性がある)

WPF 二重起動 VB


途中でも、ちらっと書きましたが、いろいろソースを改造するので、
Mutexにやっぱり戻すといったときにうまく戻らないことがあるので注意が必要である。
開発する前に、どちらで書いていくかあらかじめ仕様を決めておいた方がよい。

改造する前は必ずバックアップを取って、切り戻しできるようにしておくことが大切である。


関連URL
WPFによる開発覚え書き
WPFアプリの二重起動防止ではまったこと

デリゲート入門

関連投稿
WPFの二重起動をVBで書く〜いまなら間に合う?WPF
コメント
コメントする








    
この記事のトラックバックURL
トラックバック

calendar

S M T W T F S
      1
2345678
9101112131415
16171819202122
23242526272829
30      
<< April 2017 >>

search this site.

よく使う、検索される投稿

categories

アマゾン

楽天

selected entries

archives

recent comment

recent trackback

profile


※当ブログはリンクフリーですが、 取材や雑誌等で掲載される場合は、事前にお知らせください

others

mobile

qrcode

powered

無料ブログ作成サービス JUGEM