マイコンピュータでファイルをダブルクリックし開く動作を実現するには

解決


ganchan  2009-04-14 11:20:16  No: 101636  IP: 192.*.*.*

OS:Windows2000
Ver:6.0 
ganchanといいます。よろしくお願いします。
AutoCADで作られたファイル「abc.dwg」を開きたいのです。コードは下のとうりです
    file_full = "d:\abc.dwg"  
    sInfo.cb = LenB(sInfo)
    ret = CreateProcess(vbNullString, file_full, ByVal 0&, ByVal 0&, _
                 1&, NORMAL_PRIORITY_CLASS, ByVal 0&, vbNullString, _
                 sInfo, pInfo)
実行するとretの値がゼロになります。
    file_full = "C:\Program Files\ACAD LT 2000\aclt.exe"
にして実行するとretの値が1になり、AutoCADが立ち上がります。
最初の引数vbNullString(lpApplicationName)と2番目の引数file_full
(lpCimmandline)の設定が間違っているのかも知れませんが、よろしくお願いします。

編集 削除
YuO  2009-04-14 11:35:43  No: 101637  IP: 192.*.*.*

ダブルクリックだと,CreateProcessではなくShellExecute(Ex)です。

で,Shell関数とコマンドプロセッサのStart命令を組み合わせて,
Shell """" & Environ$("COMSPEC") & """ /C START """" """ & file_full & """", vbHide
とやるのも手だと思います。
# Start命令かますのは,コマンドプロセッサを即座に閉じるため。

編集 削除
ganchan  2009-04-14 15:17:46  No: 101638  IP: 192.*.*.*

YuO様、登校ありがとうございます。ファイルを開くことができました。
実は、この先があり、Shell関数の戻り値を変数に保存後、EnumWindowsを使用して
開いたファイルのハンドルを取得しています。取得したハンドルでアプリケーションを閉じたいのです。実行するとShell関数から取得したProcessIDとEnumWindows
のProcessIDが一致しません。Shell関数からのProcessIDの取得がまずいのでしょうか。追加質問になりますが、よろしくお願いします。
Private Sub Command1_Click()
    Dim file_full As String
    
    Find_Process = 0
    Find_WinWnd = 0
    
    file_full = "d:\abc.dwg"
     
    Find_Process = Shell("""" & Environ$("COMSPEC") & """ _
             /C START """" """ & file_full & """", vbHide)

    MsgBox "アプリケーションを終了させます。"
    
    Call EnumWindows(AddressOf EnumWinProc, 0&)
    
    If (Find_WinWnd <> 0&) Then
        Call SendMessage(Find_WinWnd, WN_CLOSE, 0&, 0&)
    End If
  
End Sub

Public Function EnumWinProc(ByVal hwnd As Long, lParam As Long) _
                                                        As Boolean
    Dim lngTrd As Long
    Dim lngPrs As Long
    
    EnumWinProc = True
    
    If Not (GetParent(ByVal hwnd) = 0) Then GoTo PGMEND
    
    lngTrd = GetWindowThreadProcessId(hwnd, lngPrs)
    
    If lngPrs = Find_Process Then
        Find_WinWnd = hwnd
        EnumWinProc = False
    End If
    
PGMEND:

End Function

編集 削除
YuO  2009-04-14 15:38:58  No: 101639  IP: 192.*.*.*

プロセス列挙がウィンドウ作成より早いだけではないでしょうか。
・WaitForInputIdle APIで待ってみる
・時間を空けて複数回試みてみる
などの対策をする必要があると思います。

編集 削除
YuO  2009-04-14 15:50:45  No: 101640  IP: 192.*.*.*

勘違いがありました。

Shell 関数の戻り値は,「%COMSPEC%」 (NT系なら%WINDIR%\System32\CMD.exe だと思う) のプロセスの ID です。
このプロセスは,Start 内部命令でプログラムを起動した後,すぐさま終了します。

よって,ウィンドウを取得するとなると,
1. ShellExecuteEx API で起動して,プロセスハンドルを取得する
2. GetProcessId API でプロセスIDを取得する
3. 上記の方法で待つ
という手順が必要です。
# Required: Windows XP SP1 or later

編集 削除
ganchan  2009-04-14 18:36:25  No: 101641  IP: 192.*.*.*

YuO様、登校ありがとうございます。
早速ですが、kernel32.dllにGetProcessID関数があるようで
下のようにしました。
[モジュール1]
Public Declare Function GetProcessID Lib "kernel32" _
(ByVal hProcess As Long) As Long
[コマンドボタン1のShellexecutEx関数以降]
    lngRet = ShellExecuteEX(sdtSEXI)
    Find_Process = GetProcessID(sdtSEXI.hProcess)
GetProcessID関数を実行すると” kernel32”にGetProcessIDは
見つかりません”というメッセージを表示します。
色々調べるとウィルス感染とか出てきました。
現象がよくつかめません。何かあれば教えてください。

編集 削除
YuO  2009-04-15 00:44:48  No: 101642  IP: 192.*.*.*

よく見てください。
http://msdn.microsoft.com/en-us/library/ms683215.aspx
GetProcessId関数です。
GetProcessID関数ではありません。

Windows APIは識別子に対してCase-Sensitiveです。
いくらVBが識別子に対してCase-Insensitiveでも,Windows APIの呼び出し部分だけはCase-Sensitiveに書かないといけません。

編集 削除
ganchan  2009-04-15 10:32:44  No: 101643  IP: 192.*.*.*

YuO様、投稿ありがとうございます。
添付してありましたホームページを見ました。'GetProcessId'と'kernel32'で
検索し、GetProcessIdのDeclare宣言及び使用する時のコードを確認しましたが、
間違っていないようです。どこがおかしいのですか。また、'Case-Sensitive'の
意味がいまいち分かりません。ご指導の方よろしくお願いします。

編集 削除
YuO  2009-04-15 11:42:35  No: 101644  IP: 192.*.*.*

> どこがおかしいのですか。

関数名が間違っています。
前に書いた通り,GetProcessIDではなく,GetProcessIdです。

> また、'Case-Sensitive'の意味がいまいち分かりません。

# 「いまいち分からない」ってどういうことでしょうか。
Case-Sensitiveとは,大文字・小文字を区別する事を意味します。
Case-InsensitiveはCase-Sensitiveの対義語で,大文字・小文字を区別しない事を意味します。

編集 削除
ganchan  2009-04-15 12:49:43  No: 101645  IP: 192.*.*.*

YuO様、投稿ありがとうございます。
前回のレスに書いていませんでした。GetProcessIdで実行しても同じ現象なのです。'Case-Sensitive'の件ですが、「いまいち」と言う表現がよくないのであれば
申し訳ございません。m(_ _)m 
大文字・小文字を区別を指しているとは思わず、丁寧な(別の)表現があると思い
勘違いしていました。本題に戻り、英語版のホームページとか見たのですが、
(実際は英語版がほとんどですけど)同じコードなのにうまくいかない。

編集 削除
YuO  2009-04-15 13:50:27  No: 101646  IP: 192.*.*.*

あ,前提条件読み飛ばしていました。
> OS:Windows2000
ではだめです。
> # Required: Windows XP SP1 or later
なので。

となると,
FindExecutable http://msdn.microsoft.com/en-us/library/bb776419.aspx
で実行ファイルを探して,そのファイル名に開きたいファイルを引数として追加した後
CreateProcess http://msdn.microsoft.com/en-us/library/ms682425.aspx
でしょうか。
ただし,関連付けの引数指定を全く無視することになるので,注意が必要です。
# CreateProcessの莫大な情報が不要なら,Shell関数でよい気もします。

編集 削除
ganchan  2009-04-16 09:52:24  No: 101647  IP: 192.*.*.*

YuO様、いつもありがとうございます。
FindExecutable関数は分かりました。ダブルクリックでファイルで開いたままか確認するのに役立ちます。
関数の3番目の引数lpResultにファイルに関連付けしているアプリケーションファイル(フルパス)を設定しますが、
当初の質問に戻りますが、このアプリケーションファイルと開きたいファイルをCreateProcess関数にどのように設定するのか分からないのです。
何かいい方法があれば教えてください。
また、私がいまやりたいことは、MSHFlexGridにタイトルとファイル名の一覧表を表示し、ファイル名をダブルクリックすると
そのファイルを開き、プログラム終了時に放置されたものを閉じたいのです。開くのは
Shell関数、ShellExecuteEX関数及びCreateProcess関数でもいいのですが、閉じるときに開いたファイルが放置]
されているか確認し、放置状態であればそれを閉じたいのです。これを実現するにはどの関数を使うか検討しています。
色々ご迷惑をお掛けしているとは思いますが、よろしくお願いします。

編集 削除
YuO  2009-04-16 16:56:32  No: 101648  IP: 192.*.*.*

> このアプリケーションファイルと開きたいファイルをCreateProcess関数にどのように設定するのか分からないのです。

ref) http://msdn.microsoft.com/en-us/library/ms682425.aspx
・lpApplicationNameをvbNullStringに設定する
・lpCommandLineにコマンドラインをそのままいれる
でよいです。
コマンドプロンプトから起動する時と同じコマンドラインになります。
ex) C:\Windows\NotePad.exe test.txt

> プログラム終了時に放置されたものを閉じたいのです。
原則的に無謀です。
特定のアプリケーション相手である事が確定しているのであれば不可能ではないですが。
その場合は,そのアプリケーションに特化した方法を採る方がよいです。
OfficeなどではCOMによるオートメーションが使えます。
AutoCADもできるようですので,そちらによる制御ができないかを調べた方がよいでしょう。

編集 削除
YK  2009-04-17 10:14:12  No: 101649  IP: 192.*.*.*

こんにちは。

>また、私がいまやりたいことは、MSHFlexGridにタイトルとファイル名の
>一覧表を表示し、ファイル名をダブルクリックすると
>そのファイルを開き、プログラム終了時に放置されたものを閉じたいのです

APIの宣言は調べて下さい。
Command1_Clickで開き
Command2_Clickで閉じます。
又、フォームのクローズでも閉じるチェックをしています。

フォームモジュールに
Option Explicit
Private pID      As Long

Private Sub Command1_Click()
    Dim strEXE  As String
    Dim strData As String
    
    strEXE = "C:\Program Files\ACAD LT 2000\aclt.exe "
    strData = "D:\TEST.DWG"
    
    pID = Shell(strEXE & strData, vbNormalFocus)
    '    ' 起動待ち
    On Error Resume Next
    Do
        DoEvents
        Err.Clear
        AppActivate pID
    Loop Until Err.Number = 0
    On Error GoTo 0
End Sub

Private Sub Command2_Click()
    Dim lngHdl      As Long
    Dim lngExitCode As Long
    Dim lngRtn      As Long
    
    If pID = 0 Then Exit Sub
    lnghWnd = 0
    ' CAD終了時にウィンドウがあるかチェック(無くてもよさそうですが一応
    lngRtn = EnumWindows(AddressOf EnumWinProc, pID)
    If lnghWnd = 0 Then Exit Sub
    
    lngHdl = OpenProcess(PROCESS_QUERY_INFORMATION Or _
                         PROCESS_TERMINATE, 0, pID)
    lngRtn = GetExitCodeProcess(lngHdl, lngExitCode)
    lngRtn = TerminateProcess(lngHdl, lngExitCode)
    lngRtn = CloseHandle(lngHdl)
    pID = 0
End Sub

Private Sub Form_QueryUnload(Cancel As Integer, UnloadMode As Integer)
    Command2_Click
End Sub


標準モジュールに
Public lnghWnd  As Long
Public Function EnumWinProc(ByVal hWnd As Long, lParam As Long) As Boolean
    Dim lngThreadId As Long
    Dim lngProcesID As Long
    
    lngThreadId = GetWindowThreadProcessId(hWnd, lngProcesID)
    If lParam = lngProcesID Then
        lnghWnd = hWnd
        EnumWinProc = False
        Exit Function
    End If
EnumPass:
    EnumWinProc = True
End Function

編集 削除
ganchan  2009-04-17 12:02:43  No: 101650  IP: 192.*.*.*

YuO様及びYK様、投稿ありがとうございます。
YK様のサンプルを実行したらうまくいきました。自分のイメージどうりにならました。どうもありがとうございます。
これで解決とします。今回のことで色々勉強になりました。ありがとうございます。

編集 削除