MASATOの開発日記


前の開発日記 次の開発日記 一覧

2004/04/25

Vectorという多数のフリー/シェアソフトを紹介するサイトがあります。 Vetorは登録制になっており、私もいくつかのソフトをVectorに登録しています。 現時点までにこのサイトにアクセスした人の約50%はVectorから来ていますので、 窓口としてはとてもありがたいサイトです。

しかし、Vectorは登録願いを提出してから実際に登録されてサイト上からダウンロードできるようになるまで 3週間くらいかかります。3週間といったら開発が早いソフトであれば何回かバージョンアップしていても おかしくありません。Vectorにはソフトウォッチというソフトのバージョンアップを教えてくれるサービスがありますが、 3週間遅れて教えて貰ってもちょっと辛いんじゃないかと思います。

ソフトのバージョンアップをすぐ知りたいのならばソフトアンテナくまかわ堂が良いでしょう。ソフトアンテナは 定期的にソフトを公開しているページを巡回し、ページの内容が変更されたことを検出して差分を集めているサイトです。 定期的に巡回しているわけですからソフトの更新に応じて素早くソフトアンテナも更新されています。
それに対し、くまかわ堂の更新タイミングは不思議の一言。ソフトが更新されてもくまかわ堂は更新されないことがあります。 しかし、アーカイブに含まれるreadme.txtを見ないと分からないような更新情報が紹介されていたりすることもあるので、 恐らくどなたかが手動で更新しているんじゃないかと思われます。すげぇ。

ISequentialStreamインターフェースでCFileにアクセスする方法

環境Visual Studio.NET 2003
使用ライブラリMFC

MSXMLは、MSXML2::IXMLDOMDocument::loadを使うことによりXMLファイルを読み込むことができます。
また、MFCのCInternetSession::OpenURLを使用することにより、Webサーバから簡単にデータを取得できます。
しかし、CInternetSession::OpenURLの戻り値はCStdioFile*です。 MSXML2::IXMLDOMDocument::loadは、IStreamやISequentialStreamインターフェースを使ってデータを読み込むことはできますが、 CStdioFile*からデータを読み込むことはできません。
今回紹介する方法は、MSXML2::IXMLDOMDocument::loadとCInternetSession::OpenURLをつなぐ方法です。

突っ込まれる前に説明しておきますが、MSXML2::IXMLDOMDocument::loadはURLを指定してXMLファイルを取得することができます。 よって、単純にWebサーバからXMLファイルを取得したいときは今回の方法は必要ありません。MSXML2::IXMLDOMDocument::loadで事足ります。
また、CInternetSessionではなく、MSXMLのMSXML2::IXMLHTTPRequestを使う方法もあります。 しかし、これはIEのプロキシ設定を使用してWebサーバにアクセスすることができません(CInternetSessionはできます)。 逆に言えば、IEのプロキシ設定を使用する必要がなければMSXML2::IXMLHTTPRequestで十分かもしれません。

さてMSXML2::IXMLDOMDocument::loadとCInternetSession::OpenURLをつなぐ話に戻ります。 もうちょっと汎用的にして、ISequentialStreamとCFileをつなぐ話にします。 これをつなげてくれるのが次のCSequentialStreamFromFileクラスです。 まずはヘッダファイルから見ていきます。

#pragma once

class CSequentialStreamFromFile : public ISequentialStream
{
public:
  CSequentialStreamFromFile(CFile* pFile, bool bDeleteOnRelease = false);
  virtual ~CSequentialStreamFromFile(void);
  virtual HRESULT __stdcall QueryInterface(REFIID riid, void** ppvObject);
  virtual ULONG __stdcall AddRef(void);
  virtual ULONG __stdcall Release(void);
    virtual HRESULT __stdcall Read(void *pv, ULONG cb, ULONG *pcbRead);
    virtual HRESULT __stdcall Write(const void *pv, ULONG cb, ULONG *pcbWritten);
private:
  LONG m_lRef;
  bool m_bDeleteOnRelease;
  CFile* m_pFile;
};

ISequentialStreamが備えているメソッドを全部備えています。 さて次はソースファイルの方です。

#include <afx.h>
#include <afxext.h>
#include "SequentialStreamFromFile.h"

CSequentialStreamFromFile::CSequentialStreamFromFile(CFile* pFile, bool bDeleteOnRelease) : m_lRef(0), m_pFile(pFile), m_bDeleteOnRelease(bDeleteOnRelease)
{
}

CSequentialStreamFromFile::~CSequentialStreamFromFile(void)
{
  if (m_bDeleteOnRelease) {
    m_pFile->Close();
    delete m_pFile;
  }
}

HRESULT __stdcall CSequentialStreamFromFile::QueryInterface(REFIID riid, void** ppvObject)
{
  if (riid == IID_IUnknown) {
    *ppvObject = static_cast<IUnknown*>(this);
  }else if (riid == IID_ISequentialStream) {
    *ppvObject = static_cast<ISequentialStream*>(this);
  }else {
    return E_NOINTERFACE;
  }
  AddRef();
  return S_OK;
}

ULONG __stdcall CSequentialStreamFromFile::AddRef(void)
{
  return ::InterlockedIncrement(&m_lRef);
}

ULONG __stdcall CSequentialStreamFromFile::Release(void)
{
  LONG ref = ::InterlockedDecrement(&m_lRef);
  if (ref == 0) {
    delete this;
  }
  return ref;
}

HRESULT __stdcall CSequentialStreamFromFile::Read(void *pv, ULONG cb, ULONG *pcbRead)
{
  UINT nRead;
  nRead = m_pFile->Read(pv, cb);
  if (pcbRead) {
    *pcbRead = nRead;
  }
  return S_OK;
}

HRESULT __stdcall CSequentialStreamFromFile::Write(const void *pv, ULONG cb, ULONG *pcbWritten)
{
  m_pFile->Write(pv, cb);
  if (pcbWritten) {
    *pcbWritten = cb;
  }
  return S_OK;
}

解説しようかと思いましたが、特に芸が無いのでやめておきます。 COMのIUnknownの実装にCFile周りの処理をちょろっと足しただけです。

そして最後に簡単な使い方です。

#include <afx.h>
#include <afxinet.h>
#include <afxext.h>
#include <comdef.h>
#include "SequentialStreamFromFile.h"

#import "msxml3.dll" named_guids

void Func(void)
{
  CInternetSession Session;
  ISequentialStreamPtr pStream = new CSequentialStreamFromFile(
    Session.OpenURL("http://www.sutosoft.com/room/devdiary/rss.rdf", 1, INTERNET_FLAG_TRANSFER_BINARY)
    , true);

  MSXML2::IXMLDOMDocument2Ptr pDoc;
  pDoc.CreateInstance(MSXML2::CLSID_DOMDocument);
  pDoc->async = VARIANT_FALSE;
  if (pDoc->load(pStream.GetInterfacePtr())) {
    TRACE("Load Success\n");
    TRACE("%s\n", static_cast<LPCTSTR>(pDoc->xml));
  }else {
    TRACE("Load Failed\n");
    TRACE("%s\n", static_cast<LPCTSTR>(pDoc->parseError->reason));
  }
}

CFileを扱えるので、例えば実行ファイルのリソースにXMLファイルを含ませておいて、 CMemFileでアクセスできるようにして CSequentialStreamFromFileでISequentialStreamに変換してMSXML2::IXMLDOMDocument::loadで読み込む、ということもできます。 また、応用方法はMSXMLに限らず色々とあるでしょう。

なお、このクラスを作っての一番の感想は、COMコンポーネントってATLやらIDLを使わなくても作れるんだなぁ、ということでした。 COMって奥が深いですね。

前の開発日記 次の開発日記 一覧