プロフィール

髭山髭人(ひげひと)

自分の書いた記事が、一人でも誰かの役に立てば...
活動信条の一つとして「貴方のメモは、誰かのヒント」というのがあります。

このサイトについて

本家HP packetroom.net から切り離した いわゆる技術メモ用のブログで、無料レンタルサーバーにて運用しています。広告表示はその義務なのでご容赦。
XREA さんには長年お世話になっています

C# 配信系のキャプチャソフトで、子ウィンドウ(アプリ)とかを捕らえてもらう

経緯とか

C# で WindowsFormApplicationを作成していた。(アプリ名を Hoge.exe とする)
同じexe内で Form1Form2 をそれぞれ作り、 .show() で表示させており、
PC上からのデスクトップ映像配信で、そのアプリのウィンドウをキャプチャさせることを目的の一つとしていた。

OBSN Air といった配信ソフトには、アプリケーション(の領域)単位でキャプチャしてくれる機能がある。

試しに N Air で Hoge.exe を拾わせてみると、Hoge.exe の Form1 しか認識できなかった。
正確には、 Form2 も認識はするが、[Hoge.exe]: (null) のようになり、選択しても黒い矩形が表示されない。
(本来は form1 に加え、 form2 もHoge.exeから表示されているのだから、どちらも取り込んでほしい)

子ウィンドウ化 + 別スレッド起動

  • 内部で、Form2の .Owner を Form1 に指定する
  • Form1 の子となった Form2 を、別スレッド内で .Show() させる

といった方法で自分は解決した。

(※素の状態で作れば、別スレッド等しなくても多分認識する筈だけど、自分のケースだどゴチャゴチャした状態だったので、メモがてら書いています)

ざっとこんなイメージ

//using 等諸々省略
static class Program
{

    public static Form1 form1 = null;
    public static Form2 form2 = null;

    //よく見かけるエントリポイントってやつ
    [STAThread]
    static void Main()
    {
        Application.EnableVisualStyles();
        Application.SetCompatibleTextRenderingDefault(false);

        form1 = new Form1();    //親
        //別スレッドで云々
        new Thread(new ThreadStart(anotherThread)).Start();

        Application.Run(form1);
    }
    //別スレッド用に切り分けた処理
    public static void anotherThread()
    {
        form2 = new Form2();    //子を作成
        form2.Owner = form1;    //←ここで親に form1 を指定した
        form1.Invoke((MethodInvoker)delegate () {
            //親であるform1として、子のform2を立ち上げ。
            //これは行儀が良い方法との事。
            form2.Show();
        });
    }
}

フォームTextが空だと取り込めない問題

Formの .Text 文字列が空だと、取り込めない模様。

大抵のケースだと、フォームに何かしらのタイトルを付けていると思うけど、例えば

form1.ControlBox = false;
form1.Text = "";

のようなテクニックを用いて 敢えてタイトルバー(キャプションバー)を外したり、逆に普通に表示させたままだとしても、
タイトル..すなわち form1.Text に対して何も文字列を与えて居なければ、キャプチャソフトはそのフォームを認識してくれない。

冒頭でも書いたが、タイトルを与えていないフォームを N Air で取り込もうとすると
[Hoge.exe]: (null) のようになり、選択しても黒い矩形が表示されるのみであった。

ゆえに、下記のように何かテキストを設定して キャプチャソフトで Hoge.exe 内の form1 が引っかかるようにする必要がある。

//フォームタイトルを与えれば、正しく認識(キャプチャ)される
form1.Text = "hoge";

普段のケースならこれでも構わないだろう。 しかし、先述の通り 敢えてタイトルバー(キャプション)を狙って非表示にさせている 場合、
.Text に文字列を入れていると、結局タイトルバー(キャプションバー)が出てしまい、元も子もなくなる。

WM_SETTEXT を使って解決

WindowsAPIの力を借りて、SendMessage() を使い、 WM_SETTEXT 系のメッセージを与えることで解決した。

こうすれば、フォーム(ウィンドウ)にテキストを関連付けられるが、
form1.Text = ""; に文字列を与える訳では無いので、タイトルバー(キャプションバー)は出現しなくなる

( 無理矢理タイトルバーを消す方法は存在するけど、副作用があったり、かなり泥臭い作業になるのでカツアイ!します )

//上のコードから更に抜粋、いろいろ省略
//form1のタイトルバーを非表示にしつつ、配信キャプチャに拾ってもらえるようにする。

form1.Invoke((MethodInvoker)delegate () {
    //従来通り、タイトルバーは非表示の方向で。
    form1.ControlBox = false;
    form1.Text = "";
    form1.Show();
    //APIつこうて、フォームに文字列を紐づける
    System.Text.StringBuilder sb = new System.Text.StringBuilder("親だよ~");
    WindowsAPIClass.SendMessage(form1.Handle, 0x0C, IntPtr.Zero , sb);
});

//とりあえず適当なstaticクラス作って、そこでSendMessageを使えるようにした
public static class WindowsAPIClass
{
    [DllImport("user32.dll", CharSet = CharSet.Auto)]
    public static extern Int32 SendMessage(IntPtr hWnd, UInt32 Msg, IntPtr ptr, System.Text.StringBuilder lParam);
}

こうする事で、form1 にタイトルバーを表示させる事なく、映像配信ソフトにてウィンドウ単位でのキャプチャに引っ掛けることができた。

もし子ウィンドウも同様タイトルバー非表示にしたままキャプチャに引っ掛けたければ、
SendMessage のハンドルを差し替えて同じメッセージを送れば取り込めるようになる。

なんかもっと、form1.うんちゃら みたいなところに書き込む(setする)だけで済む、 スマートな方法ないかなぁ...(´ `*)

今回のケース「タイトルバー(キャプションバー)を出したくない」ってところが結構特殊だったから、 このくらい回りくどい方法取らないとダメなのかも?

この記事の本筋とは関係ないけど、Chromeなんかでもハードウェアアクセラレーションを切らないと
ウィンドウ単位でのキャプチャに失敗するケースもあるようなので、
描画回りで凄くコアな処理とかさせている人は、もっと別の問題に突き当たったりするんだろうなァ...


ヒントになったページ