2009年2月18日水曜日

うわー

微熱が37.4度でずっと続いている。何かぼんやりすると思ったら。

HDCが_RemotableHandleになっちゃう

ので、idlの定義で
HRESULT Render([in] TimedLevel *pLevels, [in] int hdc, [in] RECT *prc);
とし、実際のRenderメソッドの実装で
IntPtr hdc_=(IntPtr)hdc;
としてみた。これ、unsafeコードじゃないんだねえ。

実際のIWMPEffectsはEffectMainPanelってユーザコントロールを作っておいて

[Guid("f6add2d1-c4c0-4e4a-99df-49ac5a90d23b")]
[ClassInterface(ClassInterfaceType.None)]
[ProgId("MyEffect")]
public class MyEffect : IWMPEffects
{
[DllImport("user32.dll")]
static extern IntPtr WindowFromDC(IntPtr hDC);
EffectMainPanel effectMainPanel;
IntPtr hwnd = IntPtr.Zero;
public MyEffect()
{
effectMainPanel = new EffectMainPanel();
}
#region IWMPEffects メンバ
...
public void Render(ref tagTimedLevel pLevels, int hdc, ref tagRECT prc)
{
IntPtr hdc_ = (IntPtr)hdc;
HwndSource.FromHwnd(WindowFromDC(hdc_)).RootVisual = effectMainPanel;
effectMainPanel.HorizontalAlignment = System.Windows.HorizontalAlignment.Stretch;
effectMainPanel.VerticalAlignment = System.Windows.VerticalAlignment.Stretch;
}
...
}

としてみた。で、上手くいかねー。もうちょっと調べてみよう。

C#+WPFのWindows Media Player視覚エフェクトのスケルトン

ここにWMPWPFTest.zipの名前で置いておいた。

解決

直前のエントリの問題は解決。
クラスをpublicにしていないだけだった。良かった良かった。

後は、
http://blogs.msdn.com/adam_nathan/archive/2005/10/24/using-com-interop-to-create-a-wpf-visualization-for-windows-media-player.aspx
にある。
Although IWMPEffects has several members, the most important one is Render. Windows Media Player calls your Render method regularly, giving you data about the audio, a handle to the device context (HDC) on which you need to draw, and a rectangle defining the bounds. Since WPF supports interoperability with HWNDs, I first needed to get an HWND from the HDC by PInvoking to WindowFromDC. From there, I could set my UserControl as the RootVisual for the HwndSource and update the UI appropriately. I think the obvious next step is to use data binding, but for now I've left that as an exercise for the reader. :)

に従ってWPFで視覚エフェクトを実装できる。

それから、その視覚エフェクトの登録の仕方は直前のエントリで書いた方法でregasmに作ってもらった.regファイルを(多分管理者権限が必要)登録した後、さらに
http://blogs.wankuma.com/melt/archive/2008/03/12/127425.aspx
に書いてある
この Effects キーのサブキーとしてアプリ名(今回の場合は WmpTest という名前にした)があり、そのサブキーに Property というキーがあります。

HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\MediaPlayer\Objects\Effects\WmpTest\Property

このキーの値として classid というのがあり、ここにプラグイン本体の CLSID を記述します。

WMP はこの CLSID を使って ::CoCreateInstance をするので、CLSID に対応する dll や exe のパスもレジストリに記述しておく必要があります。

もレジストリファイルで用意しておいて登録する。これで万事OKのはず。

ところで、regasmを/regfileオプションで使ったり、HKLM\...\Effects以下への登録もregファイルを用いているのは、開発環境以外でも登録が容易であるようにするため。

なまじIDEに``COM相互運用機能の登録''なんて項目がある物だから、開発環境を使わずに登録する方法が分からずに苦労してしまった。

しかしこの方法で生成されたレジストリファイルには

[HKEY_CLASSES_ROOT\CLSID\{F6ADD2D1-C4C0-4E4A-99DF-49AC5A90D23B}\InprocServer32]
@="mscoree.dll"
"ThreadingModel"="Both"
"Class"="WMPWPFTest.MyEffect"
"Assembly"="WMPWPFTest, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"
"RuntimeVersion"="v2.0.50727"

こんな事が書いてあるんだが、これ、GACに登録しないといけないなんて事ないよな?Assembly=の部分をフルパスにすれば良いんだよな?

いずれにせよ、インストーラは簡単な物を自作しなければいけなそうだなあ。

追記:
regasm /regfile /codebase ビルドされたdll
でフルパスでアセンブリを参照するレジストリファイルが作れる。
そうやって作ったレジストリファイルを管理者権限で登録して、上に書いたようにWindows Media Playerの視覚エフェクトに関するレジストリキーも登録して、コードの方でメソッドにとりあえずMessageBox.Show("Called!")とか書いておいたら、ちゃんと呼ばれた。良かった、良かった。

regasmって再配布してよいのだろうか・・・。

Windows Media Player 10 視覚エフェクトをC#+WPFで作る実験

自力でトライしてみたが、COMについて何も知らないので躓いている。
1.http://msdn.microsoft.com/en-us/windowsmedia/bb190309.aspxからWMP 10のSDKを落とす。
2.SDKのインクルードディレクトリにあるeffects.idlをWmpEffectsLib.idlにコピーして、

[
object,
uuid(D3984C13-C3CB-48e2-8BE5-5168340B4F35),
helpstring("IWMPEffects: Public interface for Windows Media Player SDK."),
pointer_default(unique),
oleautomation
]
interface IWMPEffects : IUnknown
{

から

interface IWMPEffects2 : IWMPEffects
{
HRESULT SetCore( [in] IWMPCore * pPlayer );
HRESULT Create( [in] HWND hwndParent );
HRESULT Destroy();
HRESULT NotifyNewMedia( [in] IWMPMedia * pMedia );
HRESULT OnWindowMessage( [in] UINT msg, [in] WPARAM WParam, [in] LPARAM LParam, [in] LRESULT * plResultParam );
HRESULT RenderWindowed( [in] TimedLevel * pData, [in] BOOL fRequiredRender );
}

までを

[
uuid(d282e51a-5bb9-46c2-aa73-decf29cba6f2),
version(1.0)
]
library WmpEffectsLib{
...中身...
}

で囲む。なお、uuidの生成にはuuidgenまたはguidgenを使う。
2.midl /newtlb WmpEffectsLib.idlしてWmpEffectsLib.tlbを生成
3.tlbimp WmpEffectsLib.tlbしてWmpEffectsLib.dllを生成
4.WmpEffectsLib.idlを見ながらconstやenumの対応する定義をするC#コードを作成。
後有用そうなコメントもコピー。

namespace WmpEffectsLib
{
static class WmpEffectsLibUtils
{
// These flags are to be returned in the GetCapabilities method. They
// indicate to the effects host what the effect supports
public const uint EFFECT_CANGOFULLSCREEN = 0x00000001; // can the effect go full screen?
public const uint EFFECT_HASPROPERTYPAGE = 0x00000002; // does the effect have a property page?
public const uint EFFECT_VARIABLEFREQSTEP = 0x00000004; // should effect return frequency data with variable size steps?
public const uint EFFECT_WINDOWEDONLY = 0x00000008; // can the effect render only in windowed mode?

public const uint EFFECT2_FULLSCREENEXCLUSIVE = 0x00000010;


public const int SA_BUFFER_SIZE = 1024; // number of frequency/waveform samples

enum PlayerState // audio playback states
{
stop_state = 0, // audio is currently stopped
pause_state = 1, // audio is currently paused
play_state = 2 // audio is currently playing
};

// The tagTimedLevel structure is passed to the Render() method of the effect. It holds the frequency,
// waveform and state data need to render the effect.
//
// The first dimension of each array corresponds to the channel: 0-left/mono 1-right(stereo only)
// The second dimension contains the sampled levels. The frequency data ranges
// from 0..255. The wave form data represents -128..127 but is stored as
// 0..255. The state contains the current audio playback state. The time stamp provides
// the relative time of these samples in the audio stream.
}
}

5.MyEffects.csに以下を記述

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using WmpEffectsLib;
using System.Runtime.InteropServices;

namespace WMPWPFTest
{
[Guid("f6add2d1-c4c0-4e4a-99df-49ac5a90d23b")]
[ClassInterface(ClassInterfaceType.None)]
[ProgId("MyEffect")]
class MyEffect:IWMPEffects
{
#region IWMPEffects メンバ

public void DisplayPropertyPage(ref _RemotableHandle hwndOwner)
{
return;
}

public void GetCapabilities(out uint pdwCapabilities)
{
pdwCapabilities = 0;
}

public void GetCurrentPreset(out int pnPreset)
{
pnPreset = 0;
}

public void GetPresetCount(out int pnPresetCount)
{
pnPresetCount = 1;
}

public void GetPresetTitle(int nPreset, out string bstrPresetTitle)
{
switch (nPreset) {
case 0: bstrPresetTitle = "test";
break;
default:
bstrPresetTitle = "test";
break;
}
}

public void GetTitle(out string bstrTitle)
{
bstrTitle = "WMPEPFTest";
}

public void GoFullscreen(int fFullScreen)
{
return;
}

public void MediaInfo(int lChannelCount, int lSampleRate, string bstrTitle)
{
return;
}

public void Render(ref tagTimedLevel pLevels, ref _RemotableHandle hdc, ref tagRECT prc)
{
return;
}

public void RenderFullScreen(ref tagTimedLevel pLevels)
{
return;
}

public void SetCurrentPreset(int nPreset)
{
return;
}

#endregion
}
}

6.プロジェクトのプロパティのアセンブリ情報で``アセンブリをCOM参照可能にする''をONにする。
7.ビルド
8.regasm /regfile bin/releaseに生成されたdll でHKCR\CLSID\にクラス情報を登録するレジストリファイルを生成

この8.で「登録する型がないため、登録スクリプトは何も作成されません」と非情なお言葉。

もうちょっとCOM参照可能なクラスを作る方法を調べてみよう。