MarketSpeed2のxllファイルを解析したら、PowerShellで株価を音声読み上げできた話

目次

ある日、MarketSpeed2_RSS_64bit.xll を解析していた時のこと

いつものように興味本位でアドインファイルを調べていた。
マーケットスピード II に含まれる MarketSpeed2_RSS_64bit.xll は、Excel 上で =RssMarket("7203.T","現在値") のような形式で株価を取得できる便利な関数を提供している。

ただ、私はふと思った。

「この関数、Excel を通さずに呼べないだろうか?」

何かの自動化に使いたいわけではない。
ただの興味、ただの研究目的だ。
それでも気づけば、この .xll を解析して動作の仕組みを探るという “深めの沼” に片足を突っ込んでいた。

実は中身は DLL だった

.xll の正体は、簡単に言えば 「Excel 向けに特化した DLL」 である。
その内部には .NET CLR で書かれた MsRss.dll という中核の DLL が埋め込まれており、そこに CallRTDRssFunction といったクラス・関数が見つかった。

これは大きな発見だった。

Excel を直接操作しなくても、この DLL を呼び出せば RSS 関数相当の動作が再現できるかもしれない――。

そう思って .dll 単体を抽出し、PowerShell や C# から読み込んで実験を進めたところ、確かに関数を呼び出すことはできた。

が、そこに思わぬ壁があった。

RSS の仕組みは Excel の “奥” にあった

表面的には DLL を呼び出しているだけに見えるが、実際には XlCall.RTD("MsRss.Server", null, param) のように、
Excel 内部に登録された RTD COM サーバーに処理を丸ごと委ねているという構造だった。

つまり MsRss.dll はラッパーにすぎず、実体は Excel の内部でしか起動できない RTD 機構にあったのだ。

しかもこの COM サーバーは、Microsoft 独自の COM インターフェース(IRtdServer)と XlCall32.dll を用いた Excel の専用実装であり、
Excel 外から直接呼ぶのは 技術的には可能でも極めて面倒 だとわかった。

第1章 DLLを見つけるまで

この .xll の中に何が入っているかを調べるには、ちょっとした工夫が必要だった。

当然ながら MarketSpeed2_RSS_64bit.xll は通常の ZIP ではないし、拡張子を .dll に変えても Visual Studio がうまく解析してくれるわけではない。

具体的な手順についてはここでは書かない
(ご興味ある方は「DLLの展開について」とだけコメントをいただければ、後ほど別の形でご案内するかもしれません。)

ここではあくまで 「自力で DLL を取り出して構造を確認できた」 という事実だけを共有しておきたい。

以下のスクリーンショットは、実際に私が抽出した MsRss.dll を確認した際のものだ。

見ての通り、この DLL は .NET 2.0 ベースの完全な 32-bit アーキテクチャであり、Excel 64-bit では直接使用できない。
そのため、AnyCPU化や32→64 bit ブリッジのような追加手順も必要となってくる。

第2章 MsRss.dll の構造を探る

DLLを取り出せたら、次に行うのは当然 「中に何が入っているか」を調べることである。

Visual Studio や ILSpy、dnSpy などを使えば、.NET CLR アセンブリであれば中身のコードを(ある程度)読み解くことができる。

しかし今回の MsRss.dll は、単なるクラスライブラリではなかった。

まず構造としては以下のような特徴がある:

  • ファイル種別:PE32(=32-bit DLL)
  • CLR ランタイム:v2.0.50727(.NET 2.0系)
  • セクション構成.text, .rsrc, .reloc の3つ
  • Export Table:無し(ネイティブ関数のエクスポートはされていない)
  • CLR Header:存在(= マネージコード)

つまりこの DLL は、ネイティブ DLL ではなく .NET で書かれており、直接関数を叩くのではなくリフレクションやP/Invokeを介して使うべき設計だった。

代表的な内部関数

静的解析ツールを通して DLL に含まれる文字列・シンボル情報を確認した結果、いくつかの重要な関数が浮かび上がってきた:

  • MsRss.RssFunction.CallRTD(string[] param)
  • MsRss.RssFunction.CallRTDAsList(string cellId)
  • get_RTD()
  • RefreshRTD()

中でも注目すべきは CallRTD メソッドである。

この関数の中身は、実にシンプルだった。

if (param.Length > 38) return 43;
return XlCall.RTD("MsRss.Server", null, param);

要するに:

  • 引数が多すぎればエラー(コード43)を返す
  • そうでなければ XlCall.RTD をそのまま呼ぶ

という ラッパー関数に過ぎなかったのだ。

XlCall.RTD の正体

ここで現れる XlCall.RTD は、Excelの内部関数を叩くためのAPIであり、Microsoft の XLCALL32.DLL に定義されている。

この関数は次のような構造をしている:

  • XlCall.RTD(string progID, string server, params string[] topicStrings)

この呼び出しにより、Excel は progID(たとえば MsRss.Server)に該当する RTD COM サーバーを起動し、topicStrings に指定された内容(例:「7203.T」「現在値」など)を元にリアルタイムデータの購読を開始する。

つまり、MsRss.dll というのは:

RTD サーバーを呼び出すための、超軽量の.NET ラッパーである

ということがわかった。

なぜこれが難しいのか?

CallRTD を呼べば一見データが取れそうに見える。
しかし、実際には以下の全てが揃っていないと、常に #N/A やエラーが返るという罠がある:

  • Excel が起動済みで、かつ RTD のセッションが初期化されていること
  • MsRss.Server という COM サーバーが Excel の内部に登録されていること
  • XLCALL32.DLL を介して、Excel の プロセス空間内から呼び出していること

この最後の点が特に重要で、PowerShell や外部の C# プログラムから DLL を単体でロードしても、Excel のプロセス空間外からでは RTD 呼び出しは失敗するという制約にぶつかる。

Excel の中でしか完結しない構造

まとめると、以下のような “密結合された世界”になっている:

[ MsRss.dll (CallRTD) ]

[ XlCall.RTD(...) ]

[ Excelプロセス内の RTD Server (MsRss.Server) ]

[ MarketSpeed II 経由でデータ取得 ]

この仕組みがある限り、DLL 単体で完結させることはできず、「Excelを起動した状態で、そこに接続して値を取得する」しかないという結論に至った。

第3章 PowerShellで値を安定して取得するには

前章で見たように、MsRss.dll 単体ではどう頑張っても「外部から直接リアルタイム株価を取得する」ことはできない。
Excel という巨大な生態系の中で初めて機能するように設計されている。

そこで次に考えるのは、「じゃあExcelが起動している状態で、そこから値だけ引っ張るにはどうするか?」という、
ある意味もっともシンプルで、最も堅実なアプローチである。

前提:Excelはすでに起動済み+接続済み

この手法で重要なのは、「あくまで人間がExcelを起動・接続しておくこと」が前提という点だ。

  • Excelを手動で起動
  • MarketSpeed2_RSS_64bit.xll を読み込み済み(スタートアップでも可)
  • 任意のシートに =RssMarket("7203.T", "現在値") のような関数を手動入力
  • MarketSpeed II にログイン → RSS接続ボタンをON

この状態であれば、PowerShellはただ値をつまみにいくだけでいい。

最小構成のPowerShellスクリプト

以下のスクリプトは、ExcelのA1セルにある =RssMarket(...) の値を1行だけで取得するものだ。

# Windows PowerShell 5.1 で実行(Core 版では動作しません)
$xl = [Runtime.InteropServices.Marshal]::GetActiveObject('Excel.Application')
$wb = $xl.ActiveWorkbook
$ws = $wb.Worksheets.Item('Sheet1')
$val = $ws.Range('A1').Value2
"現在値: $val"

このたった5行のコードが、実はExcelの中でだけ意味を持つ巨大な仕組みを通して、
MarketSpeed II から株価をリアルタイムで取得する一連の流れを完成させている。

それだけに、この方法は信じられないほど安定している

なぜこの方法が一番いいのか

前述のように、COM経由で新しくExcelを起動したり、ブックを開いたり、関数を挿入したりする方法は一見スマートに見える。

だが、こと MarketSpeed2_RSS_64bit.xll に関しては以下の問題がある:

操作問題点
PowerShell から Excel を新規起動アドインが読み込まれない、または RTD が未接続になることがある
PowerShell から関数を挿入=RssMarket(...) が #N/A のまま更新されないことがある
Excelを非表示で使うRTD の内部スレッドが回らないことがある(実際に UI が必要)

つまり、Excelは人間が開き、UI経由でアドイン・関数・接続を確認済みにしておくことで最も安定する。

この方法の魅力は「非破壊」

最も大きな利点は、Excelのデータを壊さず、見るだけで済むということだ。

PowerShell はただセルの値を読んでいるだけなので:

  • ファイルを保存する必要がない
  • Excel を閉じる必要もない
  • 関数を書き換えることもない

つまり、Excelをリアルタイムダッシュボードとして使いながら、裏でPowerShellがデータを拾いにくるという設計ができる。

応用例(複数銘柄を扱う)

仮に Sheet1 の A列に以下のように複数銘柄が並んでいるとする:

A1: =RssMarket("7203.T","現在値")
A2: =RssMarket("9984.T","現在値")
A3: =RssMarket("6758.T","現在値")

これをPowerShellで一気に取得するには、次のようにすればよい:

# Excel に接続
$xl = [Runtime.InteropServices.Marshal]::GetActiveObject('Excel.Application')
$ws = $xl.ActiveWorkbook.Worksheets.Item('Sheet1')

# A列の1〜3行目を読み込む
for ($i = 1; $i -le 3; $i++) {
$val = $ws.Cells.Item($i, 1).Value2
"行${i}: 現在値 = ${val}"
}

これだけで、リアルタイム株価のロギング・監視・通知などの自動処理の基盤が完成する。

応用例:音声でのリアルタイム株価読み上げ

PowerShellで取得した株価データを、さらに音声で読み上げることも可能です。これにより、画面を見ずとも株価の変動を把握できます。

# 音声合成の準備
Add-Type -AssemblyName System.Speech
$synth = New-Object System.Speech.Synthesis.SpeechSynthesizer

# Excel接続(前述の方法と同じ)
$xl = [Runtime.InteropServices.Marshal]::GetActiveObject('Excel.Application')
$ws = $xl.ActiveWorkbook.Worksheets.Item('Sheet1')

# 変動時のみ読み上げ
$prevVal = 0
while ($true) {
$val = $ws.Range('A1').Value2
if ($val -ne $prevVal) {
$synth.Speak("株価が${val}円に変動しました")
$prevVal = $val
}
Start-Sleep -Seconds 2
}

この方法なら、MarketSpeed IIの画面とPowerShellを併用して、手を触れることなく自動で株価変動を音声通知するシステムが完成します。

応用例:Windowsの通知ポップアップ

$xl = [Runtime.InteropServices.Marshal]::GetActiveObject('Excel.Application')
$ws = $xl.ActiveWorkbook.Worksheets.Item('Sheet1')

$val = $ws.Range('A1').Value2
# バルーンチップ通知
[System.Windows.Forms.MessageBox]::Show("株価が${val}円になりました!", "株価アラート", "OK", "Information")

PowerShellのCOMオブジェクト経由でのExcel操作は、マーケットスピード2に限らず、Excelで開いているあらゆるワークブックやデータに対して使用できます。

対応可能な例:

  • 各種株価・FXツール(マーケットスピード、MT4/MT5など)
  • Excelで開いた通常のファイル(.xlsx、.xlsなど)
  • Excel上でリアルタイム更新されるデータ(DDE、RTD、Web取得データなど)
  • VBAマクロで動的に更新されるデータ
  • 外部データソースと連携したExcelシート

重要なポイント:

  • Excelアプリケーションが起動していて、対象のワークブックが開いている必要があります
  • GetActiveObjectは現在アクティブなExcelインスタンスに接続するため、複数のExcelが開いている場合は注意が必要です
  • リアルタイムデータの場合、取得タイミングによって値が変わります

業務レベルの奇策応用例

1. 株価アラートでTeams/Slackに自動投稿

# 設定した閾値を超えたら自動でチーム通知
if ($val -gt 45000) {
$webhook = "https://hooks.slack.com/services/YOUR/WEBHOOK/URL"
$message = @{
text = "🚨緊急:株価が${val}円突破!売却検討を推奨"
channel = "#investment-alerts"
} | ConvertTo-Json
Invoke-RestMethod -Uri $webhook -Method Post -Body $message -ContentType 'application/json'
}

2. 複数銘柄の損益を自動計算してメール送信

# 保有株の時価総額を自動計算
$holdings = @{
"7203.T" = 100 # トヨタ100株
"9984.T" = 50 # ソフトバンク50株
}

$totalValue = 0
foreach ($stock in $holdings.Keys) {
$price = $ws.Range("A1").Value2 # 実際は銘柄ごとに取得
$totalValue += $price * $holdings[$stock]
}

# 1000万円を下回ったら緊急メール
if ($totalValue -lt 10000000) {
Send-MailMessage -To "boss@company.com" -Subject "ポートフォリオ緊急警告" -Body "時価総額が${totalValue}円まで下落"
}

3. 株価データをリアルタイムでExcelレポート自動生成

powershell# 新しいシートに時系列データを蓄積
$timestamp = Get-Date -Format "yyyy/MM/dd HH:mm:ss"
$newRow = $ws.UsedRange.Rows.Count + 1
$ws.Cells.Item($newRow, 1) = $timestamp
$ws.Cells.Item($newRow, 2) = $val
$ws.Cells.Item($newRow, 3) = if($val -gt $prevVal){"↑"}else{"↓"}

# 自動でグラフ更新
$chart = $ws.ChartObjects.Add(300, 50, 400, 300)
$chart.Chart.SetSourceData($ws.Range("A:B"))

4. 業務システムとの連携

# 社内APIに株価データを送信
$apiData = @{
symbol = "7203.T"
price = $val
timestamp = (Get-Date).ToString("o")
} | ConvertTo-Json

Invoke-RestMethod -Uri "https://company-api.com/stock-data" -Method Post -Body $apiData -ContentType 'application/json'

PowerShell の文字列補間で注意すべき点

PowerShell では $変数 をダブルクォート内で展開できますが、変数の後ろに :(コロン). を続けると、
構文として認識できずエラーになります。

# ❌ これはエラーになる
"行$i: 現在値 = $val"

この場合、以下のように ${変数} という形式で明示的に囲むことで安全に展開できます:

# ✅ 正しい形式
"行${i}: 現在値 = ${val}"

✅ このPowerShellスクリプトは「Excelファイルのあるフォルダ」で実行する必要はありません

なぜなら:

  • $xl = [Runtime.InteropServices.Marshal]::GetActiveObject('Excel.Application')
     → これは 既に起動している Excel に接続するだけで、「カレントディレクトリ」は無関係です。
  • $xl.ActiveWorkbook
     → 現在アクティブなブック(開いているファイル)を指すので、どこに保存されていてもOK
  • $ws.Range(...)
     → アクティブブックにあるシートやセルを指定しているだけで、ファイルパスや作業ディレクトリは無関係です。

✅ 実行場所に依存するケースは?

唯一「実行場所が関係する」のは、以下のようにPowerShellからファイルを明示的に開こうとした場合です:

$wb = $xl.Workbooks.Open(".\test.xlsx")  # ← これはカレントフォルダ依存

この形式であれば、当然その PowerShell を開いたディレクトリに test.xlsx が存在していなければ開けません。

✅ 実務でのおすすめ運用

  • Excel ファイルは手動で開いておく(MarketSpeed RSS アドイン付きで)
  • PowerShell はどこからでも実行できる(VSCode でも、任意のターミナルでもOK)
  • ディレクトリに依存しないコードを書きたいときは、常に GetActiveObject 経由で接続

✔ まとめ

実行場所影響
GetActiveObject 経由で接続❌ フォルダに依存しない
Workbooks.Open(“.\test.xlsx”) で開く✅ カレントディレクトリに依存する

❗ なぜ VSCode 上の PowerShell は動かないのか?

環境$xl = [Runtime.InteropServices.Marshal]::GetActiveObject(…) の動作
Windows PowerShell 5.1powershell.exe✅ 動作する
PowerShell 7+(Core / pwsh.exe❌ 動作しない(.NET Core に COM サポートが限定的)

✅ 解決策

方法①:VSCode をあきらめて Windows PowerShell を使う

  • スタートメニューから Windows PowerShell を右クリック → 念のため管理者として実行
  • $PSEdition"Desktop" であることを確認(=5.1)

最も安定・確実な方法です。

方法②:VSCode に Windows PowerShell 5.1 を追加で使う

実は、VSCodeでも PowerShell 5.1 を実行エンジンとして登録できます。

🔧 手順(簡単にできます)

  1. Ctrl + Shift + P → 「PowerShell: Show Session Menu」
  2. 表示される選択肢の中に Windows PowerShell (x64) があれば、それを選ぶ
  3. 出なければ、以下のように "powershell.powerShellAdditionalExePaths" に追加
"powershell.powerShellAdditionalExePaths": [
{
"exePath": "C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe",
"versionName": "Windows PowerShell 5.1 (x64)"
}
]

追加後は、セッション切り替えで 5.1 を選べるようになります。

✅ おすすめの判断基準

利用目的環境
Excel COM を使って値を取得したいWindows PowerShell 5.1(VSCodeか単体)
普通のスクリプト(ファイル処理やAPI通信など)PowerShell 7+ でもOK

補足:本当に使える DLL解析ツール(.NET 含む)

よく出てくるけど、使えないもの:dumpbin

DLLの解析ツールをネットで検索すると、決まって最初に出てくるのが dumpbin という名前である。

これは Visual Studio に付属する古典的なツールだが、使えるのは “ネイティブ DLL” 限定であり、今回のような .NET DLL(マネージドコード) を解析するにはまったく歯が立たない。

実際、MsRss.dll に対して dumpbin /exports を実行しても、何の関数名も表示されなかった

本当に使えるDLL解析ツール(.NET DLL対応)

ツール名特徴適性
ILSpy.NET DLL の中身を GUI で確認可能★★★
dnSpyILSpy の上位互換。逆アセンブル+書き換えも可★★★★
GhidraネイティブDLLの解析に最強。NSA製の逆解析ツール★★★
dotPeekJetBrains製。Visual Studioに近いUI★★☆

マネージドDLLであれば、まずは ILSpy または dnSpy で開いてみるのが王道だ。

MarketSpeed2 RSS アーキテクチャ解析

🔍 MarketSpeed2 RSS アーキテクチャ解析

🎯 解析の目標

Excel を起動せずに =RssMarket() 関数相当の機能を使いたい

→ 独立したサーバーやAPIとして株価データを取得できないか?

👤 ユーザーレイヤー
PowerShell スクリプト
C# アプリケーション
独自ツール
理想:Excel なしで直接 MarketSpeed2 データにアクセスしたい
⬇️
📊 Excel レイヤー(現実の壁)
Excel.Application
MarketSpeed2_RSS_64bit.xll
=RssMarket(“7203.T”,”現在値”)
現実:Excel プロセス内でのみ動作する設計
⬇️
🔧 DLL レイヤー(ラッパー層)
MsRss.dll
CallRTD() メソッド
XlCall.RTD() 呼び出し
発見:DLL は単なるラッパー。実体は RTD サーバーにある
⬇️
🏠 RTD サーバーレイヤー(核心)
MsRss.Server (COM)
IRtdServer インターフェース
XLCALL32.DLL
核心:Excel 内部でのみ起動可能な RTD COM サーバー
⬇️
📈 MarketSpeed2 本体
RSS 接続エンジン
楽天証券サーバー
リアルタイム株価データ
データソース:最終的な株価データの提供元
⚠️ 現在判明している制約
RTD サーバーは Excel プロセス空間内でのみ動作する設計

🔬 解析アプローチと結果

❌ DLL 直接呼び出し

失敗

MsRss.dll を PowerShell から直接ロード

結果: RTD サーバーに接続できず #N/A エラー

❌ COM サーバー直接起動

失敗

MsRss.Server を Excel 外から CoCreateInstance

結果: Excel プロセス外では初期化できず

✅ Excel 経由アクセス

成功

GetActiveObject で起動済み Excel に接続

結果: 安定してリアルタイムデータ取得可能

🔍 dnSpy 解析中

調査中

MsRss.dll 内部構造の詳細解析

発見: RecList, ComLibrary 等の重要クラス

🔑 重要な発见

密結合アーキテクチャ:

MarketSpeed2 RSS は Excel エコシステムとの密結合を前提とした設計。RTD サーバーは Excel の IRtdServer インターフェースと XLCALL32.DLL に完全依存している。


回避の可能性:

dnSpy 解析により、ComLibrary や RecList クラスで Excel 依存を回避する実装が見つかる可能性がある。ただし、相当な解析時間が必要。

📊 現在の結論

Excel 起動は現時点では必須
ただし、DLL 内部解析により突破口が見つかる可能性は残されている

タイトルとURLをコピーしました