実現したいこと
リモートサーバーのMySQLに接続し、Excelのシートにインポートするマクロを書いています。
リモートサーバーのMySQLポートには直接接続できないため、SSHトンネルを確立し、ポートフォワーディングを行い、トンネル確立中にODBC接続でデータを取得しようと考えています。
実際にそのマクロを利用することになる一般職員のITスキルは低く、CUIの操作でトンネルを確立させることも難しいため、コンソール画面を出さず、GUI上で「秘密鍵ファイルの指定」「パスフレーズの入力」の2つだけ行わせて、MySQLからのインポートを実現したいと考えています。
(有料ソフトで解決するはずですが、予算が出ず…)
発生している問題・分からないこと
コンソールを出さないために、Windows APIのCreatePseudoConsole関数を使い、疑似コンソールを生成しようとしていますが、引数が不正であることを表す E_INVALIDARG (0x80070057) を返してきて、疑似コンソールの生成に失敗します。
(エラーコードの意味は下記のページを参照)
https://learn.microsoft.com/en-us/windows/win32/seccrypto/common-hresult-values
エラーメッセージ
error
1One or more arguments are not valid
該当のソースコード
VBA
1Option Explicit 2Option Base 0 3 4Private Declare PtrSafe Function CloseHandle Lib "kernel32" (ByVal hObject As LongPtr) As Long 5Private Declare PtrSafe Function CreatePipe Lib "kernel32" (ByVal phReadPipe As LongPtr, ByVal phWritePipe As LongPtr, ByVal lpPipeAttributes As LongPtr, ByVal nSize As LongPtr) As Long 6Private Declare PtrSafe Function CreatePseudoConsole Lib "kernel32" (ByVal size As Long, ByVal hInput As LongPtr, ByVal hOutput As LongPtr, ByVal dwFlags As Long, ByVal phPC As LongPtr) As Long 7Private Declare PtrSafe Sub ClosePseudoConsole Lib "kernel32" (ByVal hPC As LongPtr) 8 9Private Const ssh_exe_path As String = "C:\Windows\System32\OpenSSH\ssh.exe" 10Private Const nullptr As LongPtr = 0 11 12Private pipe_to_child As LongPtr 13Private pipe_from_child As LongPtr 14Private hpcon As LongPtr 15Private ssh_process As LongPtr 16 17Private Sub Class_Initialize() 18 Dim stdin_for_child As LongPtr 19 Dim stdout_for_child As LongPtr 20 If CreatePipe(VarPtr(stdin_for_child), VarPtr(pipe_to_child), nullptr, 0) <> 0 Then 21 If CreatePipe(VarPtr(pipe_from_child), VarPtr(stdout_for_child), nullptr, 0) <> 0 Then 22 If CreatePseudoConsole(30 * 65536 + 80, stdin_for_child, stdout_for_child, 0, VarPtr(hpcon)) >= 0 Then 23 ' FIXME: ここに到達できない 24 CloseHandle stdout_for_child 25 CloseHandle stdin_for_child 26 Exit Sub 27 End If 28 CloseHandle pipe_from_child 29 CloseHandle stdout_for_child 30 End If 31 CloseHandle pipe_to_child 32 CloseHandle stdin_for_child 33 End If 34 Err.Raise 1004, , Err.LastDllError 35End Sub 36 37Private Sub Class_Terminate() 38 If ssh_process Then 39 Disconnect 40 End If 41 ClosePseudoConsole hpcon 42 CloseHandle pipe_from_child 43 CloseHandle pipe_to_child 44End Sub 45 46Public Sub Connect() 47 ' TODO: ssh.exeの起動処理を記入予定 48End Sub 49 50Public Sub Disconnect() 51 ' TODO: ssh.exeの終了処理を記入予定 52End Sub 53
C++
1// 参考:APIの挙動を確認するためにC++で書いたラピッドプロトタイプ 2 3#include <windows.h> 4#include <stdexcept> 5#include <string_view> 6 7HANDLE CreateProcessWOnPseudoConsole(HPCON hpcon, LPCWSTR lpCmdLine); 8bool CreateTunnel(HANDLE process, HANDLE pipe_from_child, HANDLE pipe_to_child); 9void DestroyTunnel(HANDLE process, HANDLE pipe_from_child, HANDLE pipe_to_child); 10 11bool isProcessAlive(HANDLE process); 12void readLineFromPipe(HANDLE pipe_from_child, char8_t* buf, size_t size); 13void writeLineToPipe(HANDLE pipe_to_child, const char8_t* str); 14void removeEscapeSequence(char8_t* str); 15 16constexpr LPCWSTR commandLine = L"" 17 "C:\\Windows\\System32\\OpenSSH\\ssh.exe " 18 "-L XXXXX:localhost:XXXXX (IPaddress) -p XXXXX " 19 "-i \"(key_path)\""; 20 21constexpr const char8_t* passPhrase = u8"xxxxxxxx"; 22 23int APIENTRY wWinMain(HINSTANCE, HINSTANCE, LPWSTR, int) 24{ 25 int ret = -1; 26 27 // sshの標準入力、標準出力と繋ぐパイプを生成 28 HANDLE stdin_for_child; 29 HANDLE pipe_to_child; 30 if(CreatePipe(&stdin_for_child, &pipe_to_child, nullptr, 0)){ 31 HANDLE stdout_for_child; 32 HANDLE pipe_from_child; 33 if(CreatePipe(&pipe_from_child, &stdout_for_child, nullptr, 0)){ 34 35 // sshを動かす疑似コンソールを生成 36 HPCON hpcon; 37 constexpr COORD size{80, 30}; 38 if(SUCCEEDED(CreatePseudoConsole(size, stdin_for_child, stdout_for_child, 0, &hpcon))){ 39 40 // sshを起動する 41 if(HANDLE process = CreateProcessWOnPseudoConsole(hpcon, commandLine)){ 42 43 // sshへ指示を送り、トンネルを確立する 44 if(CreateTunnel(process, pipe_from_child, pipe_to_child)){ 45 46 // TODO: 本番環境ではここでDB取得処理を書く 47 MessageBoxW(nullptr, L"Connecting.", L"", MB_OK); 48 ret = 0; 49 DestroyTunnel(process, pipe_from_child, pipe_to_child); 50 } 51 CloseHandle(process); 52 } 53 ClosePseudoConsole(hpcon); 54 } 55 CloseHandle(pipe_from_child); 56 CloseHandle(stdout_for_child); 57 } 58 CloseHandle(pipe_to_child); 59 CloseHandle(stdin_for_child); 60 } 61 return ret; 62} 63 64// 以下省略 65
試したこと・調べたこと
- teratailやGoogle等で検索した
- ソースコードを自分なりに変更した
- 知人に聞いた
- その他
上記の詳細・結果
元々私はC++が専門で、Windows APIを使ったプログラミングもC++言語で長く経験を積んできたので、まず、C++言語でAPIの挙動を確認するためのラピッドプロトタイプを作成しました。
ラピッドプロトタイプは無事に完成し、サーバーへの接続、接続中のDB更新も上手くいきました。そのため、VBA向けに移植して、目的のマクロを記述しようとしたのですが、VBAでCreatePseudoConsole関数を使うと、なぜかエラーが出る現象に直面しました。
エラーコードから、引数の指定に問題があると考え、CreatePipe関数も含めて、合計3回分のAPI呼び出しの引数とメモリ配置をログファイルにダンプしてみましたが(後述1)、ラピッドプロトタイプとVBA版どちらも、同じ対応関係で変数を渡しており、どこを間違えたのか自分では気が付くことができませんでした。
Geminiにも相談し、上手くいかない原因の候補を5つ挙げるようプロンプトを書いたのですが、引数の順番、構造体の値渡しのエミュレート、プロトタイプ宣言、パイプハンドルの継承設定、引数のアライメント(後述2)、どれも再確認すれど、原因の特定には至りませんでした。
※1:ログファイルは、次の手順で作成しました。
- 呼ばれた関数と全く同じプロトタイプ型を持ち、メモリアドレスと中身、ポインタならその中身をログファイルに書き出すダミー関数を用意する(本物の関数を呼び出し、入出力をspyする)。
- その関数をDLL化して、ラピッドプロトタイプとVBA版の両方に読み込ませる。
- それぞれの版で、ダミー関数を呼び出すよう切り替える。
※2:プロトタイプ版で逆アセンブルして、8バイト境界であることを確認、VBA版の引数をすべて8バイト単位になるよう書き換えたものの上手くいかず。
補足
使用環境は下記の通りです。
Windows 11 Home 25H2
Visual Studio Community 2026
Microsoft Office Home and Business 2021
お手数をおかけしますが、ご知見のあります方がいらしましたら、ご教示いただけますと幸いです。
回答1件
あなたの回答
tips
プレビュー