分享

ExcelのRTDサーバーを、VC++で書いてみた。 - しがないプログラマ の日記

 weicat 2010-03-02

ExcelのRTDサーバーを、VC++で書いてみた。Add Star

12/30追記:interface IControllerのidl定義が抜けていたのを追加。

ref: ExcelのRTD用DCOMサーバの作り方 - しがないプログラマ の日記

昔にVBでっち上げたCOMサーバーを、VC++で書き換えてみようと思い立ち挑戦してみました。この辺りの情報は、英語ではある程度見つかるんですが日本語情報に乏しいので色々とはまりました。。。

以下は、とりあえずRTDサーバーとしては簡単に渡された値を返すだけのものの作り方です。

ExcelからはRTDワークシート関数から「=RTD("RtdServerSample.Controller","","test value")」と呼び出せます。

プロジェクトの作成

「RtdServerSample」という名前で、VisualStudioのATLプロジェクトを作成します*1オプションで作成するファイルの種類をDLLから普通のExeに変更します。デフォルトプロジェクト名がCOMとしての名前になるため、注意が必要です。

COMクラスの作成

「Controller」という名前で、ATLシンプルオブジェクトを作成します。スレッドの種類はアパートメントにします。

idlファイル編集

RtdServerSample.idlが生成されているので、以下の記述をimport文の後に追加する。

※下記のuuidは Excel2003向けです。他のバージョン用にする場合には Excelのタイプライブラリから「IRTDUpdateEvent 」と「IRtdServer」の定義コピーしてくる必要があります。

[
uuid(A43788C1-D91B-11D3-8F39-00C04F3651B8),
dual,
oleautomation
]
interface IRTDUpdateEvent : IDispatch {
[id(0x0000000a)] HRESULT UpdateNotify();
[id(0x0000000b), propget] HRESULT HeartbeatInterval([out, retval] long* value);
[id(0x0000000b), propput] HRESULT HeartbeatInterval([in] long value);
[id(0x0000000c)] HRESULT Disconnect();
};
[
uuid(EC0E6191-DB51-11D3-8F3E-00C04F3651B8),
dual,
oleautomation
]
interface IRtdServer : IDispatch {
[id(0x0000000a)]
HRESULT ServerStart([in] IRTDUpdateEvent* callback,
[out, retval] long* result);
[id(0x0000000b)]
HRESULT ConnectData([in] long topicId,
[in] SAFEARRAY(VARIANT)* strings,
[in, out] VARIANT_BOOL* newValues,
[out, retval] VARIANT* values);
[id(0x0000000c)]
HRESULT RefreshData([in, out] long* topicCount,
[out, retval] SAFEARRAY(VARIANT)* data);
[id(0x0000000d)]
HRESULT DisconnectData([in] long topicId);
[id(0x0000000e)]
HRESULT Heartbeat([out, retval] long* result);
[id(0x0000000f)]
HRESULT ServerTerminate();
};

interface IController の定義に IRtdServerのメソッドを追加する。

	interface IController : IDispatch{
[id(0x0000000a), helpcontext(0x0007a125)]
HRESULT ServerStart(
[in] IRTDUpdateEvent* CallbackObject,
[out, retval] long* pfRes);
[id(0x0000000b), helpcontext(0x0007a126)]
HRESULT ConnectData(
[in] long TopicID,
[in] SAFEARRAY(VARIANT)* Strings,
[in, out] VARIANT_BOOL* GetNewValues,
[out, retval] VARIANT* pvarOut);
[id(0x0000000c), helpcontext(0x0007a127)]
HRESULT RefreshData(
[in, out] long* TopicCount,
[out, retval] SAFEARRAY(VARIANT)* parrayOut);
[id(0x0000000d), helpcontext(0x0007a128)]
HRESULT DisconnectData([in] long TopicID);
[id(0x0000000e), helpcontext(0x0007a129)]
HRESULT Heartbeat([out, retval] long* pfRes);
[id(0x0000000f), helpcontext(0x0007a12a)]
HRESULT ServerTerminate();
};

さらに、idlファイル内に coclass Controller の定義があるので以下の1文を追加する。

interface IRtdServer;

ここまでの作業が終わったら、一旦ビルドを行い問題ないことを確認する。

Controllerクラスの実装

Controller.hを開き以下の作業を行う。

継承クラスに以下を追加する。

public IDispatchImpl<IRtdServer, &__uuidof(IRtdServer)>

BEGIN_COM_MAP~END_COM_MAPの間に以下を追加する*2

//COM_INTERFACE_ENTRY(IDispatch)
COM_INTERFACE_ENTRY2(IDispatch, IRtdServer)
COM_INTERFACE_ENTRY(IRtdServer)

継承したメソッドの定義を追加するため、クラス定義の末尾に以下の記述を追加する。

private:
IRTDUpdateEvent* m_pCallback;
// IRtdServer Methods
public:
STDMETHOD(ServerStart)(IRTDUpdateEvent * CallbackObject, long * pfRes);
STDMETHOD(ConnectData)(long TopicID, SAFEARRAY * * Strings, VARIANT_BOOL * GetNewValues, VARIANT * pvarOut);
STDMETHOD(RefreshData)(long * TopicCount, SAFEARRAY * * parrayOut);
STDMETHOD(DisconnectData)(long TopicID);
STDMETHOD(Heartbeat)(long * pfRes);
STDMETHOD(ServerTerminate)();

最後に、Controller.cppを開き以下の実装を追加する。

#include <atlsafe.h>
// CController
STDMETHODIMP CController::ServerStart(IRTDUpdateEvent* CallbackObject, long* pfRes)
{
m_pCallback = CallbackObject;
*pfRes = 1;
return S_OK;
}
STDMETHODIMP CController::ServerTerminate()
{
m_pCallback = NULL;
return S_OK;
}
STDMETHODIMP CController::ConnectData(long TopicID, SAFEARRAY** Strings, VARIANT_BOOL* GetNewValues, VARIANT* pvarOut)
{
CComBSTR result;
CComSafeArray<VARIANT> input(*Strings);
for (long index = input.GetLowerBound(), last = input.GetUpperBound(); index <= last; ++index) {
const CComVariant& value = input.GetAt(index);
value.CopyTo(&result);
}
CComVariant variant(result);
HRESULT hr = variant.Detach(pvarOut);
if ( FAILED(hr) ) {
return E_FAIL;
}
return S_OK;
}
STDMETHODIMP CController::DisconnectData(long TopicID)
{
return S_OK;
}
STDMETHODIMP CController::RefreshData(long* TopicCount, SAFEARRAY** parrayOut)
{
CComSafeArray<VARIANT> result;
*TopicCount = result.GetCount();
*parrayOut = result.Detach();
return S_OK;
}
STDMETHODIMP CController::Heartbeat(long* pfRes)
{
*pfRes = 1;
return S_OK;
}

以上で実装は完了です。ビルドを行えば RTDサーバーとして動きます。

後は、Controllerの実装を変えていけば、色々な値を返すサーバーを作ることができるかと思います。

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约