トップ «前の日記(2007-12-01) 最新 次の日記(2007-12-03)» 編集

日々の破片

Subscribe with livedoor Reader
著作一覧

2007-12-02

_ Win32OLE for Ruby.NETの準備段階

Win32OLEのRuby.NET版を作ろうと思ったものの、Win32OLEが生なCOMの使いかたをしているので結構難しい。あと、Ruby.NETが内部クラスを外に見せてないので、MRIのData_Get_Structみたいなことができないってのもある。

後者から、Ruby.NETの.NET Interopを利用するC#のDLLと考えたわけだが、前者からC#では無理っぽいということがわかった。すごく頑張ればやれるかも知れないけど、そんなところで頑張らなくても、C++があるじゃん。

で、やり始めたらいきなりWIN32OLE.newが実装できないということに愕然とする。キーワードだから、staticメソッドにできない。

そこで、しょうがないので、RubyでWIN32OLEクラスを実装して、C++(といってもC++/CLIになるわけだが)でCOMをいじらせることを考える。

ところが、結構、引っかかった。しかも、サンプルコードなんて全然ないし。

というわけで、C++/CLIでCreateInstance呼んでいるところと、Invokeを呼んでいるところのコードを公開しておくことにする。

#include <ole2.h>
using namespace System;
using namespace System::Runtime::InteropServices;  // 使いまくり
 
public ref class WIN32OLE // 内部用クラスでネームスペースで隔離してある
{
    LPDISPATCH pDisp;  // これはinterior_ptrとなるので移動対象
protected:
    WIN32OLE(IDispatch* p)
    {
        pDisp = p;
    }
public:
    WIN32OLE(String^ name)
    {
        pin_ptr<IDispatch> pvoid;  // pin_ptr(アドレスが取れる)はスタック上に取る。
        CLSID clsid; // スタック上に取ったオブジェクトはアドレスが取れる(pin_ptrと同じことだ)
        LPOLESTR lpolestr = Marshal::StringToHGlobalUni(name).ToPointer();
        HRESULT hr = CLSIDFromProgID((LPCOLESTR)lpolestr, &clsid);
        Marshal::FreeHGlobal((IntPtr)lpolestr);   // 必要
        if (hr == S_OK)
        {
            hr = CoCreateInstance(clsid, NULL,
                                CLSCTX_INPROC_SERVER | CLSCTX_LOCAL_SERVER,
                                IID_IDispatch, (void**)&pvoid); // &でアドレスをとるにはpin_ptrでなければならない
            if (hr != S_OK)
            {
                throwException(hr);
            }
            else
            {
                pDisp = (LPDISPATCH)pvoid; // pin_ptrはinterior_ptrに代入可能
            }
        }
    }
    ~WIN32OLE() {    // C++/CLIではIDisposable::Disposeから呼ばれる
        if (!pDisp)
            pDisp->Release();
    }
    Object^ Invoke(String^ name, array<Object^>^ args) // object[]の書き方でえらく悩んだ
    {
        return invoke(name, args, DISPATCH_METHOD | DISPATCH_PROPERTYGET);
    }
private:
    Object^ invoke(String^ name, array<Object^>^ args, WORD wFlag)
    {
        DISPID id;
        LPOLESTR szName = (LPOLESTR)Marshal::StringToHGlobalUni(name).ToPointer(); // 後で解放する必要がある
        HRESULT hr = pDisp->GetIDsOfNames(IID_NULL, &szName, 1, LOCALE_SYSTEM_DEFAULT, &id);
        Marshal::FreeHGlobal((IntPTr)szName);
        if (hr != S_OK)
        {
            throwException(hr);
        }
        ::EXCEPINFO excepinfo; // このあたりはグローバルネームスペースにしないと、InteropServices::ComTypes下と衝突エラーになる
        memset(&excepinfo, 0, sizeof(excepinfo));
        UINT argerr(0);
        DISPID propput;
        ::DISPPARAMS params = {NULL, NULL, args->Length, 0, };
        if (args->Length > 0)
        {
            params.rgvarg = new ::VARIANT[args->Length];
            for (int i = 0; i < args->Length; i++)
            {
                Marshal::GetNativeVariantForObject(args[i], (IntPtr)(params.rgvarg + args->Length - i - 1)); // これは便利だ
            }
        }
        if (wFlag & DISPATCH_PROPERTYPUT) // これは結構忘れてDISP_E_PARAMNOTFOUNDを食らう
        {
            params.cNamedArgs = 1;
            params.rgdispidNamedArgs = &propput;
            propput = DISPID_PROPERTYPUT;
        }
        ::VARIANT result;
        VariantInit(&result);
        HRESULT hr = pDisp->Invoke(id, IID_NULL, LOCALE_SYSTEM_DEFAULT, wFlag,
                ¶ms, &result, &excepinfo, &argerr);
        if (args->Length > 0)
        {
            for (int i = 0; i < args->Length; i++)
            {
                VariantClear(params.rgvarg + i); // Marshalのメソッドを呼ぶべきかも。そのうち試されることになるが、今は不明。
            }
            delete[] params.rgvarg;
        }
        if (hr != S_OK)
        {
            throwException(hr);
        }
        if ((V_VT(&result) & ~VT_BYREF) == VT_DISPATCH)
        {   // 全部自分でやるしかなかった。
            return gcnew WIN32OLE((V_ISBYREF(&result)) ? *V_DISPATCHREF(&result) : V_DISPATCH(&result));
        }
        Object^ o = Marshal::GetObjectForNativeVariant((IntPtr)&result);
        VariantClear(&result);   // MarshalがGetObjectForNativeVariant内でReleaseしないと思うのだが、実は不明。
        return o;
    }
private:
    void throwException(HRESULT hr)
    {
        pin_ptr<IErrorInfo> peinfo;   // pin_ptrを使う
        Exception^ excep;
        if (pDisp->QueryInterface(IID_IErrorInfo, (void**)&peinfo)  == S_OK)
        {
            excep = Marshal::GetExceptionForHR((int)hr, (IntPtr)peinfo);  // このメソッドは良い
            peinfo->Release();    // Marshalが中でReleaseしていないことを祈る。今のところはSEGVしないが。
        }
        else
        {
            excep = Marshal::GetExceptionForHR((int)hr);
        }
        throw excep;
    }
}

追記:アンマネージドヒープへ確保した文字列の解放が必要だとわかったので修正。

追記2:呼び出し結果がIDispatch*だったらCOM呼び出し可能ラッパではなく、WIN32OLEの別インスタンスを作成。

_ 10+1

角谷さんのところに、10+1に江渡さんが書かれた「Wiki的都市は構想可能か?」の紹介が出ている。

10+1 No.48 特集=アルゴリズム的思考と建築(柄沢祐輔)

同じものを読んでも読み手が異なると違うところに着目する。

象を撫でるには群盲が必要な道理だ。

僕が、なるほどなぁ、と思ったのは、

認知負荷低減に与えた影響

という考察だ。

この考察は、akrさんの「使いやすいライブラリ API デザイン」

使いやすいライブラリとはプログラマが意図したことを簡単に実現できるライブラリである

という定義と多分同じことをさしているのだろう。

そしてそこから生まれる、感動が人を動かすという結論。

でも、この結論が正しければ正しいほど、多分、暗黒面があるのだろうな、と考えて愕然とする。

誤った信念というのは、感動から生まれてくるのではなかろうか? たとえば、修行をした結果、宙を浮くということに感動してしまった人たちは、実際、動いてしまった、とか。

世界中で、感動が人を動かしているのだ。それは、たとえば、自分を認めてくれた人がいるというささやかな感動(殺し屋1とかわかりやすい)でさえ良い。共同体に自分が属するという実感こそは感動的だ。そして、その駆動原理にあるのが「認知負荷低減」だとしたら、それは恐ろしいことだ。水にやさしい言葉をかけていますか? なんとわかりやすく(認知負荷が限りなくゼロだ)、そして感動的なことか。

僕がかくあって欲しいと考える世界は、人が理性によって動く世界だ。そして理性によって動くためには、自然に対する一定の知識と知恵の集積が必要に違いない。つまり、認知負荷の軽減といっても、そこには一定の水準が必要だということなのではなかろうか。だが、おそらく、水準が低ければ低いほど、認知負荷軽減を得るのは難しいことだろう。かくして、逆に感動を得やすくさせやすいのかも知れない。

結局、自分が可能なところで地道に見聞を広げられる環境を作っていくしかないのだろうな、と思う。


2003|06|07|08|09|10|11|12|
2004|01|02|03|04|05|06|07|08|09|10|11|12|
2005|01|02|03|04|05|06|07|08|09|10|11|12|
2006|01|02|03|04|05|06|07|08|09|10|11|12|
2007|01|02|03|04|05|06|07|08|09|10|11|12|
2008|01|02|03|04|05|06|07|08|09|10|11|12|
2009|01|02|03|04|05|06|07|08|09|10|11|12|
2010|01|02|03|04|05|06|07|08|09|10|11|12|
2011|01|02|03|04|05|06|07|08|09|10|11|12|
2012|01|02|03|04|05|06|07|08|09|10|11|12|
2013|01|02|03|04|05|06|07|08|09|10|11|12|
2014|01|02|03|04|05|06|07|08|09|10|11|12|
2015|01|02|03|04|05|06|07|08|09|10|11|12|
2016|01|02|03|04|05|06|07|08|09|10|11|12|
2017|01|02|03|04|05|06|07|08|09|10|11|12|
2018|01|02|03|04|05|06|07|08|09|10|11|12|
2019|01|02|03|04|05|06|07|08|09|10|11|12|
2020|01|02|03|04|05|06|07|08|09|10|11|

ジェズイットを見習え