2011年11月26日土曜日

DELPHIでInterfaceを使ってみる(3)

DELPHIのInterfaceはクラスと同じように継承・派生することが可能です。

たとえば、エラ呼吸かどうかを確認するインターフェイスをITypeからの派生Interfaceとして作ります。
  IIsBranchial = interface(IType)
    function IsBranchial() : Boolean ; // True or False
  end;
前回までのクラスをIIsBranchialから派生するようにします。
  THuman = class(TNonRefInterfacedObject,IIsBranchial)
  public
    function GetTypeName() : string ; // human
    function IsBranchial() : Boolean ; // false
  end;

  TAnimal = class(TNonRefInterfacedObject,IIsBranchial)
  public
    function GetTypeName() : string ; // animal
    function IsBranchial() : Boolean ; // false
  end;

  TFish = class(TNonRefInterfacedObject,IIsBranchial)
  public
    function GetTypeName() : string ; // fish
    function IsBranchial() : Boolean ; // true
  end;
この場合、各クラスはITypeとIIsBranchial両方のInterfaceを実装している必要があります。実装が足らない場合、コンパイルエラーとなります。
また、IIsBranchialはITypeからの派生とはいえ、明示的に継承していないクラスからのキャストはできません。たとえば、上記THumanからITypeへはキャストできません。キャストが必要な場合は明示的に継承します。


THuman = class(TNonRefInterfacedObject,IIsBranchial,IType)


DELPHIにはチェック付きキャストとしてas演算子が用意されています。これはキャストが不正な場合、例外投げられるというものです。as演算子を用いてInterfaceにキャストする場合、このままでは実行時例外が投げられてしまいます。

キャストできるようにするにはInterfaceの宣言時にGUIDを追加してあげます。今回の場合、以下のようになります。
  IType = interface(IUnKnown)
  ['{5CDFEE96-1683-4A40-A490-34D064DCAA18}']
    function GetTypeName() : string ;
  end;

  IIsBranchial = interface(IType)
  ['{B034BE0E-4087-4C45-B78A-CCF6FB7BF9B5}']
    function IsBranchial() : Boolean ;
  end;
DELPHIではCtrl + Shift + Gで簡単に追加することができます。



  • インターフェースでの as 演算子の使い方





  • 次回ももう少しInterfaceを使ってみたいと思います。


    ソースコードは自由にご使用ください。ただし問題が起きても責任はとれません。

    2011年11月25日金曜日

    DELPHIでOverloadを使って簡単なCASTを作ってみる(2)


    前回でImplicitを使ってみました。結局Implicit演算子関数のデフォルトで呼ばれる関数がどのように決まるか分りませんでした・・・(知っている方、教えてください(_ _))
    なので、使われる型に関しては実装しておくのが一番ではないしょうか?
    また、引数のオーバーロードも戻りのオーバーロード(のようなもの)もコンパイル時点で型のチェックがされ、実装してしまうようなので、コンパイル時点で推測できるようしないといけません。

    たとえばTCastの場合次のような使い方はコンパイルエラーとなります。

    Format('Integer:%d',[TCast('250')]) ; //<- "E2250 There is no overloaded version of 'Format' that can be called with these arguments"


    これはコンパイル時にはTCast(...)が何を返すのか分らないからです。第一引数には%dを含んでいますが、この時点ではただの文字列です。明示的なキャストをすれば、型の推測が可能になりコンパイルできます。

    Format('Integer:%d',[Integer(TCast('250'))]) ; //<- OK


    TCastのソースコードを以下に示します。






    unit Thorny.NumericCast;
    
    interface
    uses SysUtils;
    
    type
      TCast = record
      private
        pS : string ;
    
      public
        class operator Explicit(const v : string) : TCast ; inline ;
    
      public
        class operator Implicit(const v : TCast) : Integer ; inline ;
        class operator Implicit(const v : TCast) : Int64 ; inline ;
        class operator Implicit(const v : TCast) : Extended ; inline  ;
        class operator Implicit(const v : TCast) : Double ; inline  ;
        class operator Implicit(const v : TCast) : Single ; inline  ;
    
        function TryTo(var v : Integer) : Boolean ; overload ;
        function TryTo(var v : Int64) : Boolean ; overload ;
        function TryTo(var v : Extended) : Boolean ; overload ;
        function TryTo(var v : Double) : Boolean ; overload ;
        function TryTo(var v : Single) : Boolean ; overload ;
      end;
    
    
    implementation
    
    
    { TCast }
    
    class operator TCast.Explicit(const v: string): TCast;
    begin
      result.pS := v ;
    end;
    
    class operator TCast.Implicit(const v: TCast): Extended;
    begin
      result := StrToFloat(v.pS) ;
    end;
    
    class operator TCast.Implicit(const v: TCast): Integer;
    begin
      result := StrToInt(v.pS) ;
    end;
    
    class operator TCast.Implicit(const v: TCast): Double;
    begin
      result := StrToFloat(v.pS) ;
    end;
    
    
    class operator TCast.Implicit(const v: TCast): Single;
    begin
      result := StrToFloat(v.pS) ;
    end;
    
    class operator TCast.Implicit(const v: TCast): Int64;
    begin
      result := StrToInt64(v.pS) ;
    end;
    
    function TCast.TryTo(var v: Single): Boolean;
    begin
      result := TryStrToFloat(pS,v) ;
    end;
    
    function TCast.TryTo(var v: Int64): Boolean;
    begin
      result := TryStrToInt64(pS,v) ;
    end;
    
    function TCast.TryTo(var v: Integer): Boolean;
    begin
      result := TryStrToInt(pS,v) ;
    end;
    
    function TCast.TryTo(var v: Extended): Boolean;
    begin
      result := TryStrToFloat(pS,v) ;
    end;
    
    function TCast.TryTo(var v: Double): Boolean;
    begin
      result := TryStrToFloat(pS,v) ;
    end;
    
    end.
    
    
    TryTo関数を追加してみました。これは見てわかるようにTryStrTo...関数を呼んでいるだけです(汗)。 varを付けた引数の場合、型が一致している必要があるようです。共変性などあるといいと思うのですが。

     ソースコードは自由にご使用ください。ただし問題が起きても責任はとれません。

    DELPHIでOverloadを使って簡単なCASTを作ってみる(1)

    DELPHI2007から、演算子のオーバーロードができるようになりました。

    演算子のオーバーロードについて


    演算子のオーバーロードと構造体を使うことで新しい型などを作ることができるようになります。今回は演算子のExplicitImplicitを使って文字列から数字にキャストする構造体と作ってみたいと思います。行う処理は変換関数をラップしているだけですが、見た目がカッコ良くなります。

    通常、文字列からIntegerに変換する場合、

    intg := StrToInt('218') ;

    と書きますが、これが

    intg := TCast('218') ;

    と書けるように工夫してみます。また、演算子のオーバーロードを利用することで、Double型にも同じ形で使えるようにしたいと思います。

    doub := TCast('3.14') ;

    Explicitは明示的なキャストと言われ、所謂"()"を使ったキャストです。Implicitは暗黙的なキャストで代入処理で暗黙的に行われるキャストです。

    TCastではExplicit演算子関数をあたかも(C言語でいう)コンストラクタに見えるよう使っています。

    TCastを以下に示します。

      TCast = record
      private
        pS : string ;
    
      public
        class operator Explicit(const v : string) : TCast ; inline ;
    
      public
        class operator Implicit(const v : TCast) : Integer ; inline ;
        class operator Implicit(const v : TCast) : Extended ; inline  ;
    
      end;
    
    class operator TCast.Explicit(const v: string): TCast;
    begin
      result.pS := v ;
    end;
    
    class operator TCast.Implicit(const v: TCast): Integer;
    begin
      inherited;
      result := StrToInt(v.pS) ;
    end;
    
    class operator TCast.Implicit(const v: TCast): Extended;
    begin
      result := StrToFloat(v.pS) ;
    end;
    
    Implicit":="の演算子関数で、複数宣言可能です(Explicitも複数宣言できます)

    A := TFoo.... (A:変数型)

    の場合は
    class operator TFoo.Implicit(const v: TFoo): A;

    と書き、

    TFoo... := A (A:変数型)

    の場合は

    class operator TFoo.Implicit(const v: A): TFoo;

    と書きます。

    これを使って戻り値のオーバーロードのようなものを実現します。引数のオーバーロードと同様、型は一致していないとデフォルトの関数が呼ばれるようなので注意が必要です。

    次回はオーバーロードに関してもう少し考えてみます。

    ソースコードは自由にご使用ください。ただし問題が起きても責任はとれません。




    2011年11月14日月曜日

    BOOST::ASIOを使ってみる(5)

    非同期処理であるASIOをマルチスレッド上で動かしてみます。
    その前に前回までのソースコードを少し変更しています。簡単に関数オブジェクトを実装できるboost::format<T>ですが、前回まではSessionのメンバ変数の値として持っていましたが、ServerModuleが先に解放された場合、クリアできなくなるので参照として持つようにしました。その代わりServerModuleのメンバ変数として存在するようにしました。値型と参照やスコープなどを考えないといけないところが私にとってC++が難しく感じるところかもしれません。
    boost::format<T>などの使い方も勉強していきたいと思います。Lamdaもあって便利だと思いますので。

    今回、boost::thread_groupを使いたいと思います。Threadをグループとして扱うことができます。boost::io_service::ioはハンドラーを効率よくスレッドに振り分けるようで、何も考えずにスレッドの中でio.run()を実行します。スレッドを開始するクラスをtemplateを使って少し汎用的にして別ファイルにしました。

    前回少し説明したio_service::workクラスを使っています。今回、マルチスレッドの各スレッドでio.run()を実行してもキューが空だと終了してしまうからです。io_service::workを代入して空ではない状態にします。逆に終了するときはio_service::workを破棄します。

    以下、ソースコードです。

    ModuleThread.hpp
    #include <iostream>
    #include <boost/asio.hpp>
    #include <boost/bind.hpp>
    #include <boost/thread.hpp>
    #include <boost/shared_ptr.hpp>
    
    namespace thorny_road{
    
    // T is necessary 'stop', 'constructor(io,short port)'
    template<typename T>
    class ModuleThread : private boost::noncopyable
    {
    private:
        boost::thread_group tg ;
        boost::shared_ptr<T> _module ; // Target Module
        boost::shared_ptr<boost::asio::io_service> io_ptr ; // for creating service io
        boost::asio::io_service& io ; // service IO
        boost::shared_ptr<boost::asio::io_service::work> work;
        short port ;
    public:
        ModuleThread(short Port, int NoThreads)
            : port(Port),
            io_ptr(new boost::asio::io_service()),
            io(*io_ptr),
            work(new boost::asio::io_service::work(io)) // add waiting work in io loop
        {
            io.reset() ;
            // Create some threads. and then run io.
            for (int i = 0 ; i < NoThreads; i++)
                tg.create_thread( boost::bind(&boost::asio::io_service::run, &io) ) ;
    
            // Create module
            _module.reset(new T(io,Port)) ;
        }
    
        // Destructor. reset IO work and waiting by stopping threads
        ~ModuleThread()
        {
            stop() ;
        }
    
        void stop()
        {
            work.reset() ; // destroy work object which is the waiting object.
            tg.join_all() ; // waiting by stopping threads.
    
            _module.reset() ;
        }
    
        T& getModule() { return *_module ; }
        T& getModule() const { return static_cast<const T&>(getModule()); }
    } ;
    
    } // thorny_road
    
    #endif  //__ASIO_MODULE_THREAD__
    

    ServerModule.hpp

    #pragma once
    
    #ifndef __ASIO_SERVER_MODULE__
    #define __ASIO_SERVER_MODULE__
    
    #include <iostream>
    #include <boost/asio.hpp>
    #include <boost/bind.hpp>
    #include <boost/function.hpp>
    #include <boost/shared_ptr.hpp>
    
    namespace thorny_road{
    
    using namespace boost::asio ;
    
    // For calling event function handler easily.
    typedef boost::function<void ()> SimpleProcedure ;
    
    class ServerSession : private boost::noncopyable
    {
    private:
        io_service& io ;
        ip::tcp::socket socket ;
        boost::asio::streambuf buf ;
        SimpleProcedure& acc_stop ;
    public:
        ServerSession(io_service& io,SimpleProcedure& acc_stop)
            : io(io),socket(io),acc_stop(acc_stop)    {}
    
        ~ServerSession(){}
    
        void start()
        {
            // Enqueue Read Handler
            async_read_until(
                socket,
                buf,
                '\n',
                boost::bind(&ServerSession::read_ok,this,_1) ) ;
        }
    
    private:
        void read_ok(const boost::system::error_code e)
        {
            if (!e)
            {
                std::iostream ios(&buf) ;
    
                std::string tmp ;
                ios >> tmp ; // get input stream
                std::cout << tmp ;
                if (tmp == "end") { delete this; return; }
                else if (tmp == "bye")
                {
                    acc_stop() ;
                    delete this;
                    return;
                }
    
                ios << tmp <<std::endl ; // retrun as it is
    
                // Enqueue Write Handler
                async_write(
                    socket,
                    buf,
                    boost::bind(&ServerSession::write_ok,this,_1) ) ;
            }
            else if (e == boost::asio::error::operation_aborted)
            {
                // abort
                std::cout << "connection abort" ;
                delete this;
                return ;
            }
            else
            {
                std::cout << "error" ;
                delete this;
                return ;
            }
        }
    
        void write_ok(const boost::system::error_code e)
        {
            if (!e)
            {
                start() ; // Restart
            }
            else if (e == boost::asio::error::operation_aborted)
            {
                // abort
                std::cout << "connection abort" ;
                delete this;
                return ;
            }
            else
            {
                std::cout << "error" ;
                delete this;
                return ;
            }
        }
    
    public:
        ip::tcp::socket& getSocket()
        {
            return (socket) ;
        }
    
    } ;
    
    class ServerModule : private boost::noncopyable
    {
    private:
        io_service& io ;   
        ip::tcp::acceptor accept ;
        ServerSession* session ;
        SimpleProcedure stopEvent ;
    public:
        ServerModule(io_service& io, const short port)
            :io(io),accept(io,ip::tcp::endpoint(ip::tcp::v4(),port))
        {
            // Initialize Function Object
            stopEvent = boost::bind(&ServerModule::stop,this) ;
            start_accept() ;
        }
    
        ~ServerModule() {}
    
        void start_accept()
        {
            session = new ServerSession(io,stopEvent) ;
            // Enqueue Accept Handler
            accept.async_accept(
                session->getSocket(),
                boost::bind(&ServerModule::accept_ok,this,_1) ) ;
        }
    
        // Accept service will be closed. Then IOServce throw operation_aborted.
        // After queue get be empty, the io.run() will stop.
        void stop()
        {
            accept.close() ;
        }
    private:
    
        void accept_ok(const boost::system::error_code e)
        {
            if (!e)
            {
                session->start() ;
                start_accept() ;
            }
            else if (e == boost::asio::error::operation_aborted)
            {
                // abort
                std::cout << "connection abort" ;
                delete session ;
                session = NULL ;
                return ;
            }
            else
            {
                // error
                std::cout << "error" ;
                delete session ;
                session = NULL ;
                return ;
            }
        }
    
    } ;
    
    } // namespace thorny_road
    #endif //__ASIO_SERVER_MODULE__
    
    Main

    #include "stdafx.h"
    #include "ServerModule.hpp"
    #include "ModuleThread.hpp" // for threading
    
    using namespace thorny_road ;
    
    int _tmain(int argc, _TCHAR* argv[])
    {
        ModuleThread<ServerModule> module(2085,2) ; // create module.
    
        return 0;
    }
    
    ModuleThreadの引数でポート番号とスレッドの数を指定します。"bye"と入力があると、ハンドラーが破棄され、アプリケーションが終了します。

    次回は送信側を勉強してみます。

    ソースコードは自由にご使用ください。ただし問題が起きても責任はとれません。また、ソースコードに対する著作権は放棄していません。

    DELPHIでInterfaceを使ってみる(4)

    前回はInterfaceを継承した場合を見てましたが、今回は実体であるクラスから派生したクラスでどうなるか見てみます。

    前回までのソースコードの抜粋です。
      IType = interface(IUnKnown)
      ['{5CDFEE96-1683-4A40-A490-34D064DCAA18}']
        function GetTypeName() : string ;
      end;
    
      IIsBranchial = interface(IType)
      ['{B034BE0E-4087-4C45-B78A-CCF6FB7BF9B5}']
        function IsBranchial() : Boolean ;
      end;
    
      TNonRefInterfacedObject = class(TObject, IInterface)
      protected
        function QueryInterface(const IID: TGUID; out Obj): HResult; stdcall;
        function _AddRef: Integer; stdcall;
        function _Release: Integer; stdcall;
      end;
    
      THuman = class(TNonRefInterfacedObject,IIsBranchial,IType)
      public
        function GetTypeName() : string ; // return 'Human'
        function IsBranchial() : Boolean ; // false
      end;
    
    THumanクラスではIType,IIsBranchialの両方明示的に継承しています。ですので、以下のような関数で使うとHumanと出力されます。

    function GetTypeName(intf : IType) : String ;
    begin
      exit(intf.GetTypeName()) ;
    end;
    ...
        human := THuman.Create() ;
        Writeln(GetTypeName(human)) ;
    
    出力
    Human
    
    ではTHumanから派生したTNewHumanではどうなるか試してみました。
      TNewHuman = class(THuman)
    
      end;
    ...
        human := TNewHuman.Create() ;
        Writeln(GetTypeName(human)) ;
    
    出力
    Human
    
    派生元のクラスにおけるITypeインターフェイス実装が呼ばれました。
    Intefaceの継承(IIsBranchial = Interface(IType)...)は場合は明示的に継承していないと呼ばれず、クラスの継承の場合は派生元のクラスで継承していれば呼ばれます。
    その理由としてはvtable,VMTや仮想クラスなど勉強すれば分りそうです。後々の課題とします。また、インタフェースを継承するとその分インスタンスサイズが増えるので使う際には考えて設計する方がいいです。

    次に、TNewHumanで各関数をオーバーライドした場合を考えます。
      THuman = class(TNonRefInterfacedObject,IIsBranchial,IType)
      public
        function GetTypeName() : string ;  virtual ; // return 'Human'
        function IsBranchial() : Boolean ; virtual ; // false
      end;
    
      TNewHuman = class(THuman)
      public
        function GetTypeName() : string ; override ; // return 'NewHuman'
        function IsBranchial() : Boolean ; override ; // false
      end;
    
    出力
    NewHuman
    
    結果としてオーバーライドした関数の結果が返ってきました。Interface関数の実体における実装は仮想関数と同じ扱いだと考えてよさそうです。


    ソースコードは自由にご使用ください。ただし問題が起きても責任はとれません。

    DELPHIでInterfaceを使ってみる(2)

    前回ではInterfaceの持つ参照カウンター機能について書きました。他にもIntefaceには「特定のメソッド」を持つオブジェクトをグループ分けする使い方があります。たとえば以下のようなInterfaceを用意します。




  • インターフェースオブジェクトのメモリ管理




  •   IType = interface(IUnKnown)
        function GetTypeName() : string ;
      end;
    
    これを実装したクラスを用意します。
      THuman = class(TInterfacedObject,IType)
        ....
        function GetTypeName() : string ; // get 'Human'
      end ;
    
      TAnimal = class(TInterfacedObject,IType)
        ....
        function GetTypeName() : string ; // get 'Animal'
      end ;
    
      TFish = class(TInterfacedObject,IType)
        ....
        function GetTypeName() : string ; // get 'Fish'
      end ;
    
    これらを利用して次のようなTypeNameを取得する関数を作りたいと考えます。
    function GetTypeName(intf : IType) : String ;
    begin
      exit(intf.GetTypeName()) ;
    end;
    
    GetTypeName(...)関数は引数のintfからGetTypeName()を実行し、TypeNameを取得したいと考えいます。たとえば、以下のようにTHumanのインスタンスオブジェクトを引数に入力すると、'Human'と返すなどです。しかし、ここで問題が分ります。
    human := THuman.Create() ;
    Writeln(GetTypeName(human)) ; // print out 'Human'
    ......
    
    ここで使っているInterfaceは参照カウンタ付きのInterfaceのため、GetTypeName(...)関数の中でhumanオブジェクトは解放されてしまいます。となると、GetTypeName(...)を抜けると、humanは解放されているため、混乱や問題が生じる可能性があります。
    このような場合は参照カウンターを無効にしたInterfaceを使えば解放される心配はありません。

    DELPHIには参照カウンターが無効になるよう実装しているクラスがありませんので、自分で実装します。
    (2011/11/24 参照カウンターが無効になるように実装しているクラスがDELPHIには用意されていました - TSingletonImplementation というクラスでGenerics.Defautsにあります)

    次のような感じでしょうか。
      TNonRefInterfacedObject = class(TObject, IInterface)
      protected
        function QueryInterface(const IID: TGUID; out Obj): HResult; stdcall;
        function _AddRef: Integer; stdcall;
        function _Release: Integer; stdcall;
      end;
    
    ...
    
    function TNonRefInterfacedObject.QueryInterface(const IID: TGUID;
      out Obj): HResult;
    begin
      if GetInterface(IID, Obj) then
        Result := 0
      else
        Result := E_NOINTERFACE;
    end;
    
    function TNonRefInterfacedObject._AddRef: Integer;
    begin
      exit(-1) ;
    end;
    
    function TNonRefInterfacedObject._Release: Integer;
    begin
      exit(-1) ;
    end;
    

    この場合、_AddRefが呼ばれても_Releaseが呼ばれても参照カウンターは変化しません。

    次ももう少しだけDELPHIのInterfaceを使ってみます。


    ソースコードは自由にご使用ください。ただし問題が起きても責任はとれません。

    DELPHIでInterfaceを使ってみる(1)

    (※以下の話はDELPHI2010を用いていることが前提になっています)
    DELPHIIntefaceを実装するための簡単なメモを書いていきたいと思います。InterfaceはもともとCOM(Common Object Model)に対応するためにある機能です。DELPHIではIntefaceオブジェクトが無効(?)になるとき自動的に'_Release'が呼ばれ、コピーされるときに'_AddRef'が呼ばれます。この挙動は非常に便利で、いわゆる参照カウンターを持つオブジェクトとして扱うことができます。私は解放のタイミングが把握しにくいオブジェクトを扱う時などに使います。Interfaceを持つオーバーヘッドを考慮しても、メリットの方が大きいと考えます。

    まずは以下のようにIntefaceを書いてみます。
      IType = interface
        procedure DoTest() ;
      end;
    
      TTestType = class(IType)
      public
        procedure DoTest() ;
      end;
    
    DELPHIではクラスを継承するようにInterfaceを追加できます。Interfaceはあくまでインターフェイス(部品と部品を繋ぐ接点)なので、扱うにはどこかに実体が無いといけません。通常、実体はクラスの方で実装します。
    実際に上記の例をビルドすると、QueryInterface,_AddRef,_Releaseが無いとエラーが出ます。DELPHIでは、どのInterfaceIInterfaceを継承しています。そのIInterfaceではQueryInterface,_AddRef,_Releaseが宣言されているため、クラスにこの3つのメソッドが実装されていないとコンパイルエラーがでます。
    DELPHIには便利なTInterfacedObjectというクラスが用意されています。これは、QueryInterface,_AddRef,_Releaseがすでに実装されているクラスです。
    次のようにTTestTypeを書き換えます。
      TTestType = class(TInterfacedObject,IType)
      public
        procedure DoTest() ;
      end;
    
    使用する際は以下のようにします。
    var
      intf : IType ;
      entity : TTestType ;
    begin
      entity := TTestType.Create() ;
      intf := entity ;
      intf.DoTest() ;  // <- run
    end ;
    
    見て分るように単純にITypeの変数にTTestTypeのオブジェクトをコピーするだけで使えるようになります。
    ここでオブジェクトの破棄について考えます。
    1)上記のような参照カウンターを持つInterfaceを使用する際は、実体側のオブジェクト(上の例だと、entity)でオブジェクトの解放(free())やコピー・複製はしてはいけません。Intefaceの参照カウンターでオブジェクトのライフタイム制御しているので、別の次元でライフタイム操作を行うのは賢明とは思えません。
    2)1)を踏まえて上記の例を考えます。Inteface変数は破棄される際に_Releaseが呼ばれ、参照カウンターが減りますので、intf変数は関数を抜ける際に参照カウンターはゼロとなり、実体であるentityオブジェクトは破棄されます。entity.free()など呼ぶと、実行時エラーが起きます。

    class functionを使って以下のように書くと、うまく実体が隠せます。(ただし、これだけでは完全には隠せません・・・何か良い方法があるのでしょうか?コンストラクタを隠せばいいように感じますが・・・)
      TTestType = class(TInterfacedObject,IType)
      public
        procedure DoTest() ;
      public class
        function GetIntrf() : IType ;
      end;
    
    ...
     
    class function TTestType.GetIntrf: IType;
    begin
      exit(TTestType.Create()) ;
    end;
    
    次はもう少しだけDELPHIInterfaceを使ってみます。

    ソースコードは自由にご使用ください。ただし問題が起きても責任はとれません。