Micorosft Agentの使い方 パート1

皆さんは、Microsoft Agentというものご存じでしょうか？
Microsoft Agentと聞くとイマイチ、思い浮かばない人も多いのではないでしょうか？
このMicrosoft AgentはOffice Assistantに使用されています。
このような、キャラクターがいました。
![FiFGCK-aMAIfbwh.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1191607/0c74af90-34c4-3c9b-cdf2-849a6d76178e.jpeg)
覚えてる人もいるのではないでしょうか？
これに使用されていた、プログラムのことです。

https://learn.microsoft.com/en-us/windows/win32/lwef/microsoft-agent

こちらのお話をしていこうと思います。
こちらは、Windows7以降からは、非推奨となっています。
そのため、実行はWindowsMEなどでやりましょう。

WindowsなのでコンパイラーはMSVC、Visual studioからそのままコンパイルしちゃいましょう。

まずは公式で書いてある基本的なインタラクションに関して学びましょう。

```:インタラクションの基本
自動的に実行または支援するキャラクター(BOTなど)に使用すること。
ユーザーによる制御を維持するという設計原則に反してはいけない。
このアプリケーションは拡張機能として使用し、キャラクターのみを必要とする機能や操作を実装することは避ける。
キャラクターと対話を選択できるようにする。ユーザーは、許可を得た場合にのみ、キャラクターを終了して、そして復帰できるようにする必要があります。ユーザーにキャラクターの対話を強制すると、重要な悪影響が生じる可能性があります。
Micorosft Agentには非表示コマンドと表示コマンドが自動的に含まれており、独自のインターフェイスにこれらの機能のサポートを含めることができます。
```
これは、ユーザーに「お前を消す方法」などと質問させないための部分です。
ユーザーはパソコン内で唐突にバスケやテニスなどをしたりして、明らかに彼らがしたいことを勝手にしてるような形で動くのではなく、そのアプリへの拡張としての機能であるべきと言っています。
そして、自動的で支援えをするキャラクターであり、自由意志を持つべきではないと言ってます。
キャラクターとの対話を選択でき、そのキャラクターが勝手に表示されたり、強制的に表れないようにするべきと言ってます。
```:インタラクションの基本2
品質、適切性、タイミングは、インターフェイスデザインでフィードバックを提供する際に考慮すべき重要な要素です。
インタラクティブなキャラクターを組み込むと、自然な形式のフィードバックの機械で増えるとともに、社会的インタラクションが成立していると思え、ユーザーは満足してくれます。
そのため、顔や非言語な機能などを利用して、その気分や意図を伝えるとよいでしょう。
人間には、環境の変化、特に動き、音量、コントラストの変化に注意を向ける定位反射があります。
そのため、ユーザーの気を散らさないように、キャラクターの動きが常に大胆に、そして大げさではない必要があります。
アイドリング動作を実装する場合、呼吸する、あたりを見回すなどのほうが良いとされるでしょう。
逆に、大げさ、大胆な行動は、ユーザーの注意を引きたい場合に行うとよいでしょう。
ユーザーは、ちゃんとした「スタンバイ」の位置を持つ必要があります。userの気を散らしたりしない場所に移動し、邪魔にならないようにしましょう。
```
これは、ユーザーの刺激に関するお話ですね。
人間は刺激を好みません。会社内で太鼓を叩き、みんなの前で歌を熱唱すると周りの人は嫌になりますよね。
これは、人間が強い刺激を受けることによる猛烈なストレスが関係しています。
ユーザーへのキャラクターの刺激も同様に、急に太鼓を叩く、大声で叫ぶなどはユーザーに強い不快感を引き起こすのです。
そして、ユーザーが邪魔と思う位置に勝手に表れないようにし、常に動き回るなどを避けるべきと書いてあります。
```:インタラクションの基本3
メニューやダイアログボックスなどの予測可能なインターフェイスになりますが、キャラクターの応答を適切に変化させることで、より自然なインターフェイスが実現します。キャラクターが杖にまったく同じようにユーザーに話しかける場合、例えば、常に同じ言葉を言う場合、ユーザーはそのキャラクターを退屈で、無関心で、失礼な人だと判断する可能性があります。
そのため、言葉遣い、身振り、表情を適切に変える必要があります。
```
これは、ユーザーがイライラするような会話にしようというお話です。

キャラ　「何か質問はありますか？」
ユーザー「シャットダウンの方法」
キャラ　「シャットダウンの方法は以下の通りです...」
ユーザー「ウイルスの消し方」
キャラ　「ファイルの削除の方法は以下の通りです...」
ユーザー「お前を消す方法」
キャラ　「ファイルの削除の方法は以下の通りです...」

を繰り返されると、人間はイライラする可能性があるということです。
そのため、

キャラ　「何か質問はありますか？」
ユーザー「シャットダウンの方法」
キャラ　「わかりました！シャットダウンの方法は以下の情報があります！！」
ユーザー「ウイルスの消し方」
キャラ　「調べてみました！ファイルの削除の方法は以下の通りです...」
ユーザー「お前を消す方法」
キャラ　「なるほど！ファイルの削除の方法は以下の通りですよ！」

のように、変化をもたらすことでストレスを減らせるということです。

では、キャラクターの仕組みを考えてみましょう。

![download.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1191607/c014a3fd-33f7-af3f-c04f-17c27c8614d7.png)
このような、流れを通して設計することにしました。

では、プログラムを書いていきましょう。

:::note alert
まず、こちらあまりにも古いものなのでいろいろと複雑で何が起きるかわかりません。
自分が何をしているかわからなければ実装しないでください。
自己責任で実装してください。
:::

まず、Microsoft Agent Software Development Kitを使用します。
https://archive.org/details/microsoftagentsoftwaredevelopmentkit
この中の
D:/SDK/Include/
内のヘッダーファイルをプロジェクトに組み込みます。
そしたら、プログラムのヘッダー内で
```cpp:agent.h
#include "AgtSvr.h"
#include "AgtSvr_i.c"
```
を定義します。
この時代のソフトですし、最初にCOMの対応のチェックもします。
```cpp
int InitCOM()
{
    if (FAILED(CoInitialize(NULL))) {
        MessageBox(NULL,
            _T("There was an error initializing COM."),
            L"Agent",
            MB_OK | MB_ICONERROR);
        return -1;
    }
}
```
combaseapi.hのcombaseapi.hをコールします。
```cpp:combaseapi.h
CoCreateInstance(
    _In_ REFCLSID rclsid,
    _In_opt_ LPUNKNOWN pUnkOuter,
    _In_ DWORD dwClsContext,
    _In_ REFIID riid,
    _COM_Outptr_ _At_(*ppv, _Post_readable_size_(_Inexpressible_(varies))) LPVOID  FAR * ppv
    );
```
ここで、公式docsでは以下のように設定するように言われています。このまま書きます。
```cpp:
hRes = CoCreateInstance(CLSID_AgentServer,
                     NULL,
                     CLSCTX_SERVER,
                     IID_IAgentEx,
                     (LPVOID *)&pAgentEx);
```
そしたら、お掃除もしていきましょう。
```cpp:
void CleanUp()
{
    if (pAgentEx)
    {
        pAgentEx->Release();
    }
}
```
そしたら、エラー処理を書いていきます。
```cpp
if (FAILED(hRes)) {
    HRESULT hRes = CoCreateInstance(CLSID_AgentServer,
    NULL,
    CLSCTX_SERVER,
    IID_IAgentEx,
    (LPVOID*)&pAgentEx);
    MessageBox(NULL,
        L"Failed to initialize agent.",
        L"Agent",
        MB_OK | MB_ICONERROR | MB_TOPMOST);

    CoUninitialize();

    return -1;
}
```
そしたら、キャラクターの読み込みを開始していきます。
まず、Loadメソッドを使用して読み込み、IAgentCharacterExインターフェイスを取得します。
キャラクターのファイルは、acsファイルです。

https://tmafe.com/classic-ms-agents/

ここから好きなagentをダウンロードしましょう。今回はDOLPHIN.ACSをダウンロードします。
まず、
LPWSTRで、キャラクターの文字列を書きます。
今回は絶対パスで読み込ませたいです。そのため、GetCurrentDirectoryA+"DOLPHIN.ACS"にします。
```cpp
#include "shlwapi.h"
 GetCurrentDirectoryA(MAX_PATH,characterPath);
 PathCombineA(characterPath, characterPath, "DOLPHIN.ACS");
```
そしたら、念のためにprintfしておきます。
```cpp
printf("Loading character in %8s", characterPath);
```
その後、
```cpp
HRESULT Load(
   VARIANT vLoadKey,  // data provider
   long * pdwCharID,  // address of a variable for character ID
   long * pdwReqID    // address of a variable for request ID
);
```
このvLoadKeyは、VARIANTなのでVariantInitでvPathを初期化します。
```cpp
VariantInit(&vPath);
vPath.bstrVal = SysAllocString(kpwzCharacter);
hRes = pAgentEx->Load(vPath, &lCharID, &lRequestID);
```
SysAllocStringは、Wide型しか読み込んでくれないのでWideにします。
```cpp
int iLen = MultiByteToWideChar(CP_ACP, 0, characterPath, -1, NULL,0);
LPWSTR kpwzCharacter = new wchar_t[iLen];
MultiByteToWideChar(CP_ACP,0, characterPath, -1, kpwzCharacter, iLen);
```
その後、エラー処理を書きます。
```cpp
if (FAILED(hRes)) {
    MessageBox(NULL,
        L"Failed to load agent.",
        L"Agent",
        MB_OK | MB_ICONERROR | MB_TOPMOST);
        CoUninitialize();
        return -1;
}
```
次にpAgentExからIAgentCharacterExを貰います。
```cpp
hRes = pAgentEx->GetCharacterEx(lCharID, &pCharacterEx)
```
そしたら次に、
pCharacterEx->Show
でいよいよ表示します。
```cpp
hRes = pCharacterEx->Show(FALSE, &lRequestID);
```
そしたら、喋らせてみましょう。
```cpp
bszSpeak = SysAllocString(L"Hello World!");
hRes = pCharacterEx->Speak(bszSpeak, NULL, &lRequestID);
SysFreeString(bszSpeak);
```
そしたら、ちゃんとお掃除もします。
```cpp
void CleanUp()
{
    if (pCharacterEx) {
        pCharacterEx->Release();
        pAgentEx->Unload(lCharID);
    }
    if (pAgentEx)
    {
        pAgentEx->Release();
    }
    VariantClear(&vPath);
}
```
ではエントリーポイントを書いていきます。
```cpp
int main()
{
    InitCOM();
    LoadAgent();
    CleanUp();
    return 0;
}
```
こんな感じに簡単に書けました。
全体はこんな感じです。
```cpp:agent.h
#pragma once
#include <stdio.h>
#include <windows.h>
#include <tchar.h>
#include "shlwapi.h"
#include "AgtSvr.h"
#include "AgtSvr_i.c"
LPSTR characterPath;
VARIANT vPath;
IAgentEx* pAgentEx;

IAgentCharacterEx* pCharacterEx = NULL;

BSTR bszSpeak;

long lCharID;
long lRequestID;

int InitCOM();
int LoadAgent();
void CleanUp();
```
```cpp:agent.cpp#include "agent.h"
int InitCOM()
{
    if (FAILED(CoInitialize(NULL))) {
        MessageBox(NULL,
            _T("There was an error initializing COM."),
            L"Agent",
            MB_OK | MB_ICONERROR);
        return -1;
    }
}
int LoadAgent() {
    HRESULT hRes = CoCreateInstance(CLSID_AgentServer,
        NULL,
        CLSCTX_SERVER,
        IID_IAgentEx,
        (LPVOID*)&pAgentEx);
    if (FAILED(hRes)) {
        MessageBox(NULL,
            L"Failed to initialize agent.",
            L"Agent",
            MB_OK | MB_ICONERROR | MB_TOPMOST);

        CoUninitialize();

        return -1;
    }
    char characterPath[MAX_PATH];
    GetCurrentDirectoryA(MAX_PATH,characterPath);
    PathCombineA(characterPath, characterPath, "DOLPHIN.ACS");
    printf("Loading character in %8s", characterPath);
    VariantInit(&vPath);
    vPath.vt = VT_BSTR;
    int iLen = MultiByteToWideChar(CP_ACP, 0, characterPath, -1, NULL,0);
    LPWSTR kpwzCharacter = new wchar_t[iLen];
    MultiByteToWideChar(CP_ACP,0, characterPath, -1, kpwzCharacter, iLen);
    vPath.bstrVal = SysAllocString(kpwzCharacter);
    hRes = pAgentEx->Load(vPath, &lCharID, &lRequestID);
    if (FAILED(hRes)) {
        MessageBox(NULL,
            L"Failed to load character.",
            L"Agent",
            MB_OK | MB_ICONERROR | MB_TOPMOST);

        CoUninitialize();

        return -1;
    }
    hRes = pAgentEx->GetCharacterEx(lCharID, &pCharacterEx);
    if (FAILED(hRes))
    {
        printf("Faild to get character");
    }
    hRes = pCharacterEx->Show(FALSE, &lRequestID);
    if (FAILED(hRes))
    {
        printf("Faild to show the character");
    }
    bszSpeak = SysAllocString(L"Hello World!");
    hRes = pCharacterEx->Speak(bszSpeak, NULL, &lRequestID);
    if (FAILED(hRes))
    {
        printf("Faild to speak the character");
    }
    printf("\nDone!");
    SysFreeString(bszSpeak);
    return 1;
}
void CleanUp()
{
    if (pCharacterEx) {
        pCharacterEx->Release();
        pAgentEx->Unload(lCharID);
    }
    if (pAgentEx)
    {
        pAgentEx->Release();
    }
    VariantClear(&vPath);
}
```
では、ビルドして起動してみましょう。
再定義に関するlnk2005が出たので
 (/FORCE:MULTIPLE)を追加しました。
 
実行してみると、何も表示されませんね。
そのため、すぐ終了しないように
```
Sleep(10000);
```
をLoadAgentの最後に追加しましょう。
すると、カイルくんが現れたことが分かると思います。
では次はカイルくんを動作させましょう。
通知シンクというのを作る必要があります。
以下のNotify.cppとNotify.hをコピペします。
```cpp:Nofiy.cpp
#include "Notify.h"


//==========================================================================
//
//  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
//  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
//  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
//  PURPOSE.
//
//  Copyright (C) 1997 - 2000 Microsoft Corporation.  All Rights Reserved.
//
//--------------------------------------------------------------------------
//
// AgentNotifySink
//
//		Boilerplate IDispatch implementation with stubs for all 
//		IAgentNotifySink methods except for RequestComplete.
//
//==========================================================================


extern long g_lDone;


// IUnknown methods

STDMETHODIMP AgentNotifySink::QueryInterface(REFIID riid, LPVOID* ppv) {

	*ppv = NULL;

	if ((riid == IID_IUnknown) || (riid == IID_IAgentNotifySink))
		*ppv = this;

	if (*ppv == NULL)
		return E_NOINTERFACE;

	((LPUNKNOWN)*ppv)->AddRef();

	return NOERROR;
}


STDMETHODIMP_(ULONG) AgentNotifySink::AddRef() {

	return ++m_cRefs;
}


STDMETHODIMP_(ULONG) AgentNotifySink::Release() {

	if (--m_cRefs != 0)
		return m_cRefs;

	delete this;
	return 0;
}


// IDispatch methods

STDMETHODIMP AgentNotifySink::GetTypeInfoCount(UINT* pctInfo) {

	*pctInfo = 1;
	return NOERROR;
}


STDMETHODIMP AgentNotifySink::GetTypeInfo(UINT itInfo, LCID lcid, ITypeInfo** ppTypeInfo) {

	HRESULT hRes;
	ITypeLib* pLib;

	*ppTypeInfo = NULL;

	if (itInfo != 0)
		return TYPE_E_ELEMENTNOTFOUND;

	if (ppTypeInfo == NULL)
		return E_POINTER;

	if ((PRIMARYLANGID(lcid) != LANG_NEUTRAL) &&
		(PRIMARYLANGID(lcid) != LANG_ENGLISH))
		return DISP_E_UNKNOWNLCID;

	hRes = LoadRegTypeLib(LIBID_AgentServerObjects,
		1,
		0,
		PRIMARYLANGID(lcid),
		&pLib);
	if (FAILED(hRes))
		return hRes;

	hRes = pLib->GetTypeInfoOfGuid(IID_IAgentNotifySink, ppTypeInfo);

	pLib->Release();

	if (FAILED(hRes))
		return hRes;

	(*ppTypeInfo)->AddRef();

	return NOERROR;
}


STDMETHODIMP AgentNotifySink::GetIDsOfNames(REFIID riid, OLECHAR** rgszNames, UINT cNames, LCID lcid, DISPID* rgDispID) {

	HRESULT hRes;
	ITypeInfo* pInfo;

	// REFIID must be NULL

	if (riid != IID_NULL)
		return ResultFromScode(DISP_E_UNKNOWNINTERFACE);

	// Get the TypeInfo for the specified lcid

	hRes = GetTypeInfo(0, lcid, &pInfo);

	if (FAILED(hRes))
		return hRes;

	// Use the TypeInfo to get the DISPIDs of the specified names.
	// That's the whole point here.  Let TypeInfo do the work so
	// we don't have to.

	hRes = pInfo->GetIDsOfNames(rgszNames, cNames, rgDispID);

	pInfo->Release();

	return hRes;
}


STDMETHODIMP AgentNotifySink::Invoke(DISPID dispID, REFIID riid, LCID lcid,
	unsigned short wFlags, DISPPARAMS* pDispParams,
	VARIANT* pVarResult, EXCEPINFO* pExcepInfo,
	UINT* puArgErr) {

	HRESULT hRes;
	ITypeInfo* pInfo;

	// The riid parameter is always supposed to be IID_NULL

	if (riid != IID_NULL)
		return DISP_E_UNKNOWNINTERFACE;

	// Get the type info for the specified lcid

	hRes = GetTypeInfo(0, lcid, &pInfo);

	if (FAILED(hRes))
		return hRes;

	// Clear exceptions

	SetErrorInfo(0L, NULL);

	hRes = pInfo->Invoke(this,
		dispID,
		wFlags,
		pDispParams,
		pVarResult,
		pExcepInfo,
		puArgErr);


	pInfo->Release();

	return hRes;
}


// IAgentNotifySink methods

STDMETHODIMP AgentNotifySink::Command(long dwCommandID, IUnknown* punkUserInput) {

	return NOERROR;
}


STDMETHODIMP AgentNotifySink::ActivateInputState(long dwCharID, long bActivated) {

	return NOERROR;
}


STDMETHODIMP AgentNotifySink::Restart() {

	return NOERROR;
}


STDMETHODIMP AgentNotifySink::Shutdown() {

	return NOERROR;
}


STDMETHODIMP AgentNotifySink::VisibleState(long dwCharID, long bVisible, long lCause) {

	return NOERROR;
}


STDMETHODIMP AgentNotifySink::Click(long dwCharID, short fwKeys, long X, long Y) {

	return NOERROR;
}


STDMETHODIMP AgentNotifySink::DblClick(long dwCharID, short fwKeys, long X, long Y) {

	return NOERROR;
}


STDMETHODIMP AgentNotifySink::DragStart(long dwCharID, short fwKeys, long X, long Y) {

	return NOERROR;
}


STDMETHODIMP AgentNotifySink::DragComplete(long dwCharID, short fwKeys, long X, long Y) {

	return NOERROR;
}


STDMETHODIMP AgentNotifySink::RequestStart(long dwRequestID) {

	return NOERROR;
}


STDMETHODIMP AgentNotifySink::RequestComplete(long dwRequestID, long hrStatus) {

	// When we get g_lDone exit the sample app

	if (dwRequestID == g_lDone)
		PostQuitMessage(0);

	return NOERROR;
}


STDMETHODIMP AgentNotifySink::BookMark(long dwBookMarkID) {

	return NOERROR;
}


STDMETHODIMP AgentNotifySink::Idle(long dwCharID, long bStart) {

	return NOERROR;
}


STDMETHODIMP AgentNotifySink::Move(long dwCharID, long X, long Y, long lCause) {

	return NOERROR;
}


STDMETHODIMP AgentNotifySink::Size(long dwCharID, long lWidth, long lHeight) {

	return NOERROR;
}


STDMETHODIMP AgentNotifySink::BalloonVisibleState(long dwCharID, long bVisible) {

	return NOERROR;
}


STDMETHODIMP AgentNotifySink::HelpComplete(long dwCharID, long dwCommandID, long dwCause) {

	return NOERROR;
}


STDMETHODIMP AgentNotifySink::ListeningState(long dwCharacterID, long bListenState, long dwCause) {

	return NOERROR;
}


STDMETHODIMP AgentNotifySink::DefaultCharacterChange(BSTR bszGUID) {

	return NOERROR;
}


STDMETHODIMP AgentNotifySink::AgentPropertyChange() {

	return NOERROR;
}


STDMETHODIMP AgentNotifySink::ActiveClientChange(long dwCharID, long lStatus) {

	return NOERROR;
}
```

```cpp:notify.h
#include "AgtSvr.h"

class MainDlg;


// Agent Notify Sink Implementation

class AgentNotifySink : public IAgentNotifySinkEx {
public:
	entNotifySink()	: m_cRefs(0) {}
~AgentNotifySink()	{}

	STDMETHODIMP QueryInterface(REFIID, LPVOID FAR*);
	STDMETHODIMP_(ULONG) AddRef();
	STDMETHODIMP_(ULONG) Release();

	STDMETHODIMP GetTypeInfoCount(UINT*);
	STDMETHODIMP GetTypeInfo(UINT, LCID, ITypeInfo**);
	STDMETHODIMP GetIDsOfNames(REFIID, OLECHAR**, UINT, LCID, DISPID*);
	STDMETHODIMP Invoke(DISPID, REFIID, LCID, WORD, DISPPARAMS*, VARIANT*, EXCEPINFO*, UINT*);

	STDMETHODIMP Command(long dwCommandID, IUnknown* punkUserInput);
	STDMETHODIMP ActivateInputState(long dwCharID, long bActivated);
	STDMETHODIMP Restart();
	STDMETHODIMP Shutdown();
	STDMETHODIMP VisibleState(long dwCharID, long bVisible, long lCause);
	STDMETHODIMP Click(long dwCharID, short fwKeys, long x, long y);
	STDMETHODIMP DblClick(long dwCharID, short fwKeys, long x, long y);
	STDMETHODIMP DragStart(long dwCharID, short fwKeys, long x, long y);
	STDMETHODIMP DragComplete(long dwCharID, short fwKeys, long x, long y);
	STDMETHODIMP RequestStart(long dwRequestID);
	STDMETHODIMP RequestComplete(long dwRequestID, long hrStatus);
	STDMETHODIMP BookMark(long dwBookMarkID);
	STDMETHODIMP Idle(long dwCharID, long bStart);
	STDMETHODIMP Move(long dwCharID, long x, long y, long lCause);
	STDMETHODIMP Size(long dwCharID, long lWidth, long lHeight);
	STDMETHODIMP BalloonVisibleState(long dwCharID, long bVisible);

	STDMETHODIMP HelpComplete(long dwCharID, long dwCommandID, long dwCause);
	STDMETHODIMP ListeningState(long dwCharID, long bListening, long dwCause);
	STDMETHODIMP Suspend();
	STDMETHODIMP DefaultCharacterChange(BSTR bszGUID);
	STDMETHODIMP AgentPropertyChange();
	STDMETHODIMP ActiveClientChange(long dwCharID, long bActive);

private:
	ULONG		 m_cRefs;
	MainDlg* m_pMainDlg;
};
```
そしたら、Agent.cppに行き、
```cpp
IAgentNotifySink* pSinkEx;
long lNotifySinkID = -1;
MSG　ｍｓｇ；
```
をヘッダーに書きます。
そして、
```cpp
pSinkEx = (AgentNotifySink*)malloc(sizeof(AgentNotifySink));
pSinkEx->AddRef();
hRes = pAgentEx->Register((IUnknown*)pSinkEx, &lNotifySinkID);
```
そしたら、リターンの前に
```
while (GetMessage(&msg, NULL, 0, 0) > 0)
    DispatchMessage(&msg);
```
を書いてループさせます。
をVariantInit(&vPath);の後らへんに書きます。
そしたら、お掃除コードを書きます。
```cpp
if (pSink) {

    if (lNotifySinkID != -1)
        pAgentEx->Unregister(lNotifySinkID);

    pSink->Release();
}
```
これでおしまいです。

そしたら、常に表示されるるのが分かります。
次はパート2でやります。