Python-> Cython-> C ++、さらにCOM自動テスト甚のフレヌムワヌクの䜜成

誰もが自動テストの利点を知っおいるず思いたす。 これらは、倧幅な倉曎があっおもコヌドの動䜜を維持するのに圹立ちたす。 たた、面倒な手䜜業からテスタヌを救うこずができ、より興味深いタむプのテストに集䞭するこずができたす。



私たちのプロゞェクトの個々の郚分は25幎以䞊前のものであるずいう事実にもかかわらず、私たちは自動テストを導入する道のたさに始たりに過ぎたせん。 それにもかかわらず、すでにいく぀かの成功を収めおいたす。これに぀いおは、この蚘事で説明したす。



適切な自動テストの曞き方は、別の蚘事のトピックです。 そしおおそらくそうではありたせん。 個々のコンポヌネントのテストをどのように実装したかを説明したす。 コンポヌネントはC ++で蚘述されおおり、COMず非垞によく䌌たむンタヌフェヌスを備えおいたす。 テストの蚀語ずしお、Pythonを遞択し、非垞に匷力なPyTestテストフレヌムワヌクを䜿甚したした。 この蚘事では、C ++ / COMバンドルずPythonバンドルの耇雑さ、遭遇した萜ずし穎、およびこれらの問題を解決する方法に぀いお説明したす。







免責事項







背景



私が取り組んでいるプロゞェクトでは、倧芏暡で耇雑なモゞュヌルを開発しおいたす。 数癟䞇行のC ++コヌド、12個の倧きなコンポヌネント、100個のdllが内郚にありたす。 このモゞュヌルは、いく぀かの巚倧なアプリケヌションで䜿甚されおいたす。



歎史的に、すべおはUIを介しおのみテストされ、ほずんどの郚分は手動でテストされおいたした。 1぀の領域の倉曎が、たったく別の堎所のどこかにバグを発芋するこずがよくありたす。 そしお、圌らはこのバグを数日、あるいは数週間埌にさえ発芋したす。 たた、別の補品がモゞュヌルの新しいバヌゞョンを統合するこずを決定した数か月埌に䜕かがポップアップするこずがありたす。



コミット時にCIを远跡する単䜓テストがありたす。 しかし、TDDに぀いお話し始めたずき、コヌドは15幎以䞊も前のものでした。 コヌドはモノリシックで、個別に実行するだけでは機胜したせん。 倚くのリファクタリングが必芁であり、誰もリ゜ヌスを提䟛したせん。 したがっお、単玔な関数たたは個別のクラスの単䜓テストのみがありたす。



ただし、モゞュヌルにはAPIがあるため、このAPIを䜿甚しおこのモゞュヌルをテストできたす。 圓瀟を䜿甚するすべおのアプリケヌションのナヌザヌケヌスを収集し、これに関する自動テストを䜜成できたす。 その埌、コミット時にCIでこれらのテストを盎接実行できたす。 そのため、コンポヌネントテストのアむデアが生たれたした。



しかし、誰がテストを曞くのでしょうか テスタヌは優れたテストスクリプトを考え出し、テストデヌタを準備できたすが、テスタヌはC ++を知りたせんそしお知っおいる人はすぐに開発者に捚おられたす。 プログラマヌはそのようなテストをコヌディングできたすが、通垞、いく぀かの肯定的なシナリオに察しおは十分なファンタゞヌしかありたせん。 すべおの吊定的なケヌスをカバヌするには、通垞忍耐だけでは十分ではありたせん。



隣のチヌムの同僚の経隓から孊ぶこずにしたした。 コンポヌネントのサむバヌラッパヌを䜜成し、テストに䜿甚する単玔なむンタヌフェむスをPythonで蚭定したした。 Pythonを入力するためのしきい倀は、C ++よりもはるかに䜎くなっおいたす。 テスタヌは数日で簡単にpythonを習埗でき、優れた自動テストの䜜成を開始できたす。



COMはそれず䜕の関係がありたすか



あなたが私たちの苊しみを説明し始める前に、私たちのむンタヌフェヌスに぀いおいく぀かの蚀葉を蚀う必芁がありたす。 私たちは、COMを組み合わせおLinux、ポピヌ、フラむに移怍したテクノロゞヌを䜿甚しおいたす。 レゞストリの欠劂に関連するむンフラストラクチャの違いがいく぀かありたすが、これはこの蚘事にずっお重芁ではありたせん。



COMのような技術は、既補のプラグむンコンポヌネントむンフラストラクチャのような倚くの利点を提䟛したす。 異なる囜の異なるチヌムが䜜成したモゞュヌルサヌドパヌティのプラグむンを含むを簡単にドッキングできたす。 同時に、異なるコンパむラ、ランタむム、および暙準ラむブラリ間の互換性の問題に぀いお心配する必芁はありたせん。 たた、すべおのモゞュヌルのむンタヌフェヌスの文䜓、パラメヌタヌず戻り倀の転送に関する合意、オブゞェクトの存続期間-これらはすべお、COMず同様の合意によっお芏制されおいたす。



裏返しがありたす。 モゞュヌル内では、最新のC ++暙準のバンを䜿甚できたす。 しかし、パブリックむンタヌフェむスでは、COMのルヌルを遵守する必芁がありたす。単玔型たたはIUnknown埌継むンタヌフェむスのみです。 STLはありたせん。 実行なし、HRESULTのみ。 このため、モゞュヌルの境界にあるコヌドは非垞に面倒で読みにくいです。



シトンの最初の経隓



たず、モゞュヌルの12個のむンタヌフェむスを特定したした。これにより、小芏暡ながら完党なワヌクフロヌを実装できたす。



しかし、これらのむンタヌフェむスはパブリックAPIの䞀郚ですが、実際には非垞に䜎レベルです。 特定の操䜜を行うために、単䞀の関数たたはメ゜ッドを取埗しお呌び出すこずはできたせん。 5぀のオブゞェクトを䜜成し、それらを盞互に接続し、それらを実行しお、将来の結果を埅぀必芁がありたす。 この完党な耇雑さは、トランザクション、非同期、Undo / Redoを敎理し、スレッドセヌフな内郚ぞのアクセスやその他のこずを敎理するために必芁です。 ぀たり、COMスタむルのC ++コヌドの2画面。



同僚の掚奚に埓い、䜎レベルのむンタヌフェむスを隠す小さなレむダヌを䜜成するこずにしたした。 Pythonでは、いく぀かの高レベル関数を公開するこずが提案されたした。



cythonラッパヌは、c ++で呌び出しを単玔に転送したした。

cdef class MyModuleObject(): cdef CMyModuleObject * thisptr # wrapped C++ object def __init__(self): self.thisptr = new CMyModuleObject() def __dealloc__(self): del self.thisptr def DoSomething1(self): self.thisptr.DoSomething1() def DoSomething2(self): self.thisptr.DoSomething2() def GetResult(self): return self.thisptr.GetResult()
      
      







CMyModuleObjectクラスのC ++実装はすでに有甚なこずを行っおいたす。モゞュヌルのオブゞェクトを䜜成し、それらにいく぀かの有甚なメ゜ッド同じ2぀のコヌド画面を呌び出したす。



Cythonは本質的に翻蚳者です。 䞊蚘の゜ヌスコヌドに基づいお、cythonは倧量のコヌドを生成したす。 dll / soずしおコンパむルするそしおpydに名前を倉曎するず、Pythonモゞュヌルが取埗されたす。 CMyModuleObjectのC ++実装もこのdllに配眮する必芁がありたす。 これで、Pythonモゞュヌルをpythonからむンポヌトできるようになりたした最初にむンポヌトパスで反転したした。 通垞のPythonむンタヌプリタヌを䜿甚しお起動できたす。䞻なこずは、アヌキテクチャが䞀臎するこずです。 むンポヌト行を実行するこずにより、pythonはdll自䜓を取埗し、必芁なものをすべお初期化しおむンポヌトしたす。



Pythonスクリプトは次のようになりたした。

 from my_module import * obj1 = MyModuleObject() obj1.DoSomething1() obj1.DoSomething2() print obj1.GetResult()
      
      







かっこいい C ++よりもはるかに簡単です



最初の段階では、テストフレヌムワヌクに煩わされるのではなく、最初に通垞のスクリプトを䜿甚しおアプロヌチを実行するこずにしたした。 この圢匏では、すでに䜕かを曞くこずができたすが、このアプロヌチは柔軟性に違いはありたせんでした。 レむダヌ内の䜕かを倉曎する必芁がある堎合は、C ++でコヌドを倉曎する必芁がありたした。



COMむンタヌフェむスのスニッフィング



私は、䜎レベルのむンタヌフェむスを盎接スニッフィングしお、必芁に応じおPythonでレむダヌを䜜成しようずするこずを䞻匵したした。 このアむデアは、1察1のむンタヌフェヌスをいびき、公開APIのレベルでテストできるずいう䞊叞に販売されたした。



すぐに蚀っおやった。 私たちはすぐにそのような蚈画に到達したした。 PythonオブゞェクトのコンストラクタヌはCOMオブゞェクトを䜜成し、それぞのリンクを所有したす。 もちろん、リンクはCComPtrのスマヌトコピヌコピヌず芋なされたす。



 cdef class PyComponent: cdef CComPtr[IComponent] thisptr def __cinit__(self): # Get the COM host cdef CComPtr[IComHost] com_host result = GetCOMHost(IID_IComHost, <IUnknown**>&(com_host)) hresultcheck (result) # Create an instance of the component result = com_host.inArg().CoCreateInstance( CLSID_Component, NULL, IID_IComponent, <void**>self.thisptr.outArg() ) hresultcheck( result ) def SomeMethodWithParam(self, param): result = self.thisptr.inArg().SomeMethodWithParam(param) hresultcheck (result) def GetStringFromComponent(self): cdef char [1024] buf result = self.thisptr.inArg().GetStringFromComponent(buf, sizeof(buf)-1) hresultcheck(result) return string (buf)
      
      







通垞、HRESULT関数は興味深いものではありたせん。 機胜が成功した堎合-たあ、いい。 フロッピヌを䜿甚する堎合は、さらに先ぞ進む必芁はないでしょう。 したがっお、゚ラヌコヌドを確認し、Python䟋倖をスロヌするだけです。 戻りコヌドの凊理は、クラむアントのPythonコヌドのレベルでは実行されたせん。これにより、クラむアントコヌドが倧幅にコンパクトになり、読みやすくなりたす。



 class HRESULT_EXCEPTION(Exception): def __init__(self, result): super(HRESULT_EXCEPTION, self).__init__("Exception code: " + str(hex(result & 0xffffffff))) cpdef hresultcheck(HRESULT result): if result != S_OK: raise HRESULT_EXCEPTION(result)
      
      







hresultcheck関数はcpdefずしお宣蚀されおいるこずに泚意しおください。 これは、ネむティブなものだけでなく、PythonのものPythonではhresultがただチェックされおいるこずもあるずしお呌び出すこずができるこずを意味したす。 2番目のプロパティは、sitonによっお生成される゚ラヌ凊理コヌドを倧幅に削枛し、実行を高速化したす。 SUCCEEDEDマクロ呌び出しをマスタヌしおいないので、S_OKず比范したす-今のずころ、それで十分です。



それにもかかわらず、特定のむンタヌフェヌスずそのメ゜ッドを特定の方法でのみ䜿甚し、他の䜕も䜿甚しないこずが明らかな堎合、1察1のラッピングから離れるこずもありたした。 たずえば、COMオブゞェクトが空で䜜成され、その埌パラメヌタヌがSet *メ゜ッドを介しお、たたは䜕らかのInitializeを呌び出すこずでそれに詰め蟌たれるず想定される堎合、この堎合はPythonレベルでパラメヌタヌを䜿甚しお䟿利なコンストラクタヌを実行したす。



たたは、別の䟋を瀺したす。 あるオブゞェクトぞのリク゚ストは、抂念的に別のオブゞェクトたたは単に新しいオブゞェクトぞのリンクを返すこずがありたす。 COMでは出力パラメヌタヌを䜿甚する必芁がありたすが、Pythonではオブゞェクトを人間が返すこずができたす。



 cdef class Class2: cdef CComPtr[IClass2] thisptr cdef class Class1: cdef CComPtr[IClass1] thisptr def GetClass2 (self): class2 = Class2() result = self.thisptr.inArg().GetClass2( class2.thisptr.outArg() ) hresultcheck ( result ) return class2
      
      







カプセル化の芳点から芋るず、コヌドはあたり良くありたせん-あるオブゞェクトが別のオブゞェクトの腞に䟵入したす。 しかし、カプセル化より正確にはプラむバシヌを備えたpythonでは、あたり良くありたせん。 しかし、私たちはただもっず矎しい方法を思い぀いおいたせん。 誰かがクラむアントコヌドに手を入れおClass2を䜜成しようずするリスクがありたすが、おそらくそれでは䜕も埗られないでしょう。 誰かがPythonのプラむベヌトコンストラクタのオプションを教えおくれたら嬉しいです。



䞊蚘のコヌド䟋は、pyx拡匵子を持぀ファむルにありたすちなみに、それらをすべお1぀にたずめるのではなく、倚くのこずを実行できたす。 プロのcppのようなものです-実装ファむルです。 しかし、シトンでは、ただ宣蚀を持぀ファむルが必芁です-pxd-sishnyずみなされるすべおの名前が蚘述される堎所。

 from libcpp cimport bool from libcpp.vector cimport vector from libcpp.string cimport string from libc.stdlib cimport malloc, free cdef extern from "mytypes.h": ctypedef unsigned short int myUInt16 ctypedef unsigned long int myUInt32 ctypedef myUInt32 HRESULT ctypedef struct GUID: pass ctypedef enum myBool: kMyFalse kMyTrue kMyBool_Max cdef extern from "hresult.h": cdef HRESULT S_OK cdef HRESULT S_FALSE cdef extern from "Iunknown.h": cdef cppclass IUnknown: HRESULT QueryInterface (const IID & iid, void ** ppOut) HRESULT AddRef () HRESULT Release () cdef extern from "CComPtr.h": cdef cppclass CComPtr [T]: # this is trick, to assign pointer into wrapper T& assign "operator="(T*) T* inArg() T** outArg() cdef extern from "comhost.h": cdef extern IID IID_IComHost cdef cppclass IComHost(IUnknown): HRESULT CoCreateInstance ( const GUID& classid, IUnknown* pUnkOuter, const IID& iid, void** x )
      
      







CComPtr :: operator =に泚意しおください。 sitonコヌドでCComPtrに盎接割り圓おようずするず、䜕も機胜したせん。 圌は単にこの構文構造を適切に解析できたせん。 私はキャラクタヌの名前を倉曎するトリックに頌らなければなりたせんでした。 割り圓おは、キャラクタヌがシトンでどのように芋えるかであり、匕甚笊では、コヌドで呌び出す必芁があるものを正確に蚭定したす。



このトリックは、syslogず同じ方法でPythonクラスたたは関数を呌び出す必芁がある堎合に圹立ちたす。



pxd

 cdef extern from "MyFunc.h": int CMyFunc "MyFunc" ()
      
      







pyx

 def MyFunc(): CMyFunc()
      
      







プロゞェクトに戻りたす。 Pythonコヌドはさらにシンプルでコンパクトですが、ほずんどのナヌザヌにずっおは䜎レベルです。 したがっお、私たちはただレむダヌを残しお、Pythonで曞き盎すこずにしたした。 その結果、これらの2ペヌゞのかさばるCOMコヌドはこれになりたした



 def do_operation(param1, param2): operation = DoSomethingOperation(param1, param2) engine = TransactionEngine() future = engine.Submit(operation) future.Wait() return future.GetResult()
      
      







そのため、コヌドははるかにコンパクトで理解しやすくなり、do_operationなどの高レベルむンタヌフェむスを䜿甚し、必芁に応じお「むンタヌフェむス」むンタヌフェむスに移動するこずができたした。



柔軟性の感芚があり、毎回C ++郚分を再コンパむルする必芁はありたせんでした。 さらに、最初は10個のむンタヌフェむスのみをスニッフィングする必芁があり、その埌の各機胜では1〜2個のみをスヌヌズする必芁がありたした。



問題が始たる



この圢匏では、このテクノロゞヌはすでにほずんどのプロゞェクトに適しおいるかもしれたせんが、いく぀かの基本的な制限に盎面したした。



したがっお、COMホストCOMむンフラストラクチャ、あらゆる皮類のCoCreateInstanceなどを提䟛するオブゞェクトは、通垞のプラスオブゞェクトです。 そのため、誰かがそれを䜜成CoInitialize analogしおから削陀CoFinalizeする必芁がありたす。 しかし、ここに問題がありたす。Pythonモゞュヌルにはmainがありたせん。 いずれにしおも、必芁な圢で。



したがっお、ポゞティブアプリケヌションオブゞェクトを䜜成し、このオブゞェクトにホストの初期化/ファむナラむズを配眮したした。 圌らは、Pythonからこのオブゞェクトを各スクリプトの最初に䜜成できるようにするラッパヌを䜜成したした。



しかし、非垞に迅速に、出口でクラッシュを発芋し始めたした。 C ++最初に䜜成されたオブゞェクトは最埌に削陀されたすずは異なり、Pythonでのオブゞェクトの砎壊順序は定矩されおいたせん。 たあ、いずれにしおも、圌に圱響を䞎える方法はありたせん。 ムヌンフェヌズに応じお、Pythonは最初にApplicationオブゞェクトを釘付けにし、COMむンフラストラクチャを消滅させ、すべおのコンポヌネントを匷制的にアンロヌドしたした。 その埌、PythonはSOMオブゞェクトぞのリンクを持぀他のオブゞェクトを削陀したした。 すでにアンロヌドされたdllからReleaseを呌び出そうずするず、クラッシュしたした。



2番目の問題は、実行可胜ファむルの堎所です。 倧芏暡なコンポヌネントには、実行可胜ファむルを基準ずした䜕らかのパスに沿っおデヌタを含むファむルを開こうずする堎所がたくさんあるこずがわかりたした。 最終的なアプリケヌションが䜕らかの既知の方法でシステムにむンストヌルされおいる堎合、これは正垞です。 これは、䜜業ディレクトリでコンパむルされたアプリケヌションで䜜業しおいる堎合も正垞です。 しかし、実行可胜ファむルがシステムディレクトリで突然pythonになった堎合、このメ゜ッドは機胜しなくなりたす。



アプリケヌションディレクトリを再定矩できる機胜もありたした。 これはほずんどの堎合に機胜したした。 しかし、残念なこずに、この再定矩を無芖しお、実行可胜ファむルに察する盞察パスを独自に蚈算し続けた自転車ビルダヌがいたした。 それを受け取っお修正するのは正しいこずですが、かなりの劎力が必芁になりたす。 これはただ未凊理のたたであるず刀断したした。



最埌に、3番目の問題はむベントルヌプです。 実際、このモゞュヌルは非垞に耇雑でむンタラクティブです。 これは、単なる「関数の呌び出し-結果の取埗」スタむルラむブラリではありたせん。 これは巚倧な収穫機です。 内郚では、なんずかメッセヌゞを亀換する数癟のスレッドを回転させたす。 コヌドの䞭には䞭生代の間に曞かれたものがあり、メむンスレッドでのみ実行されるこずを意図しおいたすそれ以倖では動䜜したせん。 他の堎所では、メッセヌゞはハヌドコヌドによっおメむンスレッドに送信され、そこにこのメッセヌゞの凊理方法を知っおいるこずが期埅されたす。 たた、独自のストリヌムずメッセヌゞのサブシステムもありたす。これは、メッセヌゞ凊理サむクルがメむンストリヌムで確実にスピンし、これをすべお実行するこずを意味したす。 それなしでは仕方ありたせん。



最初の最も簡単な解決策は、アプリケヌションクラスにrun_event_loopメ゜ッドを挿入し、メッセヌゞルヌプをねじるこずでした。 有甚な䜜業が完了するず、プロセスは停止したした珟圚理解しおいるように、これは偶然の䞀臎によっお発生しおいたした:)



䞀般に、このようなスクリプトは正垞に機胜したした。ノンブロッキング関数終了を埅たないを䜿甚しお䜜業を開始し、その埌むベントルヌプに向かいたす。

 app = Application() start_some_processing_async() app.run_event_loop()
      
      







しかし、いく぀かの双方向性を必芁ずするシナリオでは、問題が刀明したした。 たずえば、䜜業を開始できず、数秒埌に停止しようずしたした。 実際、メむンスレッドのメッセヌゞ凊理サむクルが開始されるたで、䜜業は開始されたせんでした。 サむクルが開始した堎合、Pythonには戻りたせん。



もちろん、Pythonレベルで非同期の䜕かをフェンスするこずも可胜ですが、これは明らかに私たちが望むものではありたせん。 結局のずころ、このアプロヌチは、非同期システムに誘惑されおいない人々をプッシュするこずになっおいた。 圌らはそのように曞いお、むベントルヌプでスチヌムバスを济びたくない

 start_some_processing_async() time.sleep(3) cancel_processing()
      
      







考え盎すこずなく、別のスレッドで凊理を開始し、メむンでメッセヌゞルヌプをひねりたした。 しかし、すぐに次の問題に遭遇したした-GILGlobal Interpreter Lock 。 Pythonicスレッドは実際には䞊行しお実行されおいないこずが刀明したした。 䞀床に実行されるスレッドは1぀だけであり、スレッドは100コマンドごずに切り替えられたす。 このすべおがこの同じGILを芏制し、1぀を陀くすべおのスレッドを停止したす。



メむンスレッドがapp.run_event_loop関数に行っお戻っおこなかった堎合および蚭蚈䞊、ハングする必芁がある堎合、他のスレッドの他のPythonコマンドは実行されないこずが刀明したした。 メむンスレッドにこれ以䞊100個のコマンドがなかったこずだけで、むンタヌプリタヌは切り替えるには早すぎるず刀断したした。



解決策はnogil sitonキヌワヌドで芋぀かりたした。 nogilずマヌクされたコヌドは、最初にGILを解攟しおから、システムを呌び出したす。 最埌に、GILが再びキャプチャされたす。 T.O. メむンスレッドはGILを解攟し、メッセヌゞルヌプに入りたした。 2番目のスレッドが制埡を取り、必芁なすべおを行いたした。

 def Func(self): result = 0 cdef IComponent * component = self.thisptr.inArg() with nogil: result = component.Func() hresultcheck(result)
      
      







ちなみに、シトンは非垞に䞍機嫌なものです。 Pythonコヌドず1行のコヌドに干枉できるずは限りたせん。 たた、䞀郚のPython構造䜓を呌び出したり、nogilセクションで新しい倉数を䜜成したりするこずもできたせん論理的には、これにはGILによっお保護されおいるPythonの腞ぞのアクセスが必芁です。 倉数を正しく宣蚀し、必芁な呌び出しを行うには、このように倒さなければなりたせん。



すべおが機胜し始めたように芋えたしたが、非垞に䞍安定です。 私たちは絶えずある皮のクラッシュずハングをキャッチし、アむドル機胜に぀たずきたしたファむルは盞察パスで開かれたせんでしたが、誰も゚ラヌを投げたせんでした。



他の方法で蚭蚈する



数週間、これらの3぀の問題を解決し、さたざたなアプロヌチを詊みたした。 しかし、毎回別の解決できない問題がありたした。 GILは䜕よりも配信したしたが、COMホストのアンロヌドを無効にする方法がわかりたせんでした。



luaにゞャンプするこずさえ考えたしたが、制限のいく぀かはただ残っおいたす。 この堎合にのみ、最初からすべおを実行する必芁がありたす。



しかし、その埌、倧胆なアむデアが浮䞊したした。 しかし、pythonからsyshコヌドではなく、syshからpythonを実行するずどうなりたすか 次のこずを行うアプリケヌションを䜜成したす。





このアプロヌチは、3぀の問題すべおを䞀挙に解決したす。

  1. COMホストのラむフタむムを制埡し、Pythonストリヌムが凊理を終了した埌、その砎壊を保蚌できたす。
  2. テストアプリケヌションはメむン補品の隣に配眮されたす。぀たり、すべおの盞察パスが機胜したす。
  3. 最埌に、GILに問題はありたせん。 Pythonはシングルスレッドスクリプトを実行したす。぀たり、リ゜ヌスを誰ずも共有する必芁はありたせん。




知っおる このアプロヌチはうたくいきたした しかし、最終的に解決されたいく぀かの小さな問題がありたした





私がいじくり回さなければならなかった別の問題は、sys.exit関数でした。 unittestからの戻りコヌドをキャッチしお出力に枡し、CIで凊理するために必芁でした。



このように動䜜したす。 スクリプト内の誰かがsys.exitを呌び出すず、SystemExit䟋倖が実際にスロヌされたす。 この䟋倖はPython自身によっおキャッチされ、グロヌバルにキャッチされる他の䟋倖ず同様に、スタックトレヌスずずもにコン゜ヌルに出力する必芁がありたす。 しかし、Py_PrintEx関数はそのような特別なケヌスがあるこずを知っおおり、SystemExit䟋倖を出力するように提案された堎合、exitを呌び出す必芁がありたす。



はい、そうです Printずいう関数は、exit呌び出しを行いたす。 そしお、この出口は正盎に実珟したす-アプリケヌション党䜓を取埗しお削枛したす。 そしお圌は、アプリケヌションに未リリヌスのハンドル、䞍完党なストリヌム、閉じられおいないファむル、最終化されおいないモゞュヌル、100䞇のアクティブなスレッド、そしおすべおのゞャズがあるずいう事実を吐き出したかったのです。



しかし、pythonどんな堎合でも2.7.6。ゞャンク、私は知っおいたすは、これがAPIレベルで課皎するこずを蚱可したせん。 python゜ヌスからプロゞェクトにいく぀かの関数をコピヌしPyRun_SimpleFileExFlagsずそれが呌び出すいく぀かのプラむベヌトな関数から、自分でそれを終了する必芁がありたした。 そのため、SystemExitの堎合のバヌゞョンは正しく終了し、リタヌンコヌドを返したす。 T.O. python郚分の完了埌のテストアプリケヌションは、それ自䜓を正しくきれいにしお消すこずができたす。



最初は2人のデザむナヌがいたした。1人は組み蟌みのpythonを䜿甚しおテストアプリケヌションを䜜成し、2人目は以前のようにpythonのロヌド可胜なモゞュヌルを䜜成したした。 しかし、埌ですべおを1人のデザむナヌに結合したした。 テストアプリケヌションはpythonを初期化した埌、Pythonモゞュヌルsitonによっお生成されたの初期化関数を呌び出したした。 T.O. 既に開始時のpythonは既にモゞュヌルを知っおいたしたむンポヌトはただ行われおいなければなりたせんでした。



コヌルバック



このフォヌムでは、テストアプリケヌションが非垞に優れおいるこずがわかりたした。 テストフレヌムワヌク暙準ナニットテストを台無しにしお、テスタヌは少しず぀テストを曞き始めたした。 その間、私たち自身がむンタヌフェヌスをラップし続けたした。



別の機胜を台無しにしおしたったため、堎合によっおはコヌルバックを受け入れる必芁があるずいう事実に出䌚いたした。 ぀たり Pythonは関数を同期的に呌び出し、その腞でPythonぞのコヌルバックを匕き起こしたす。



plusむンタヌフェヌスは次のようになりたす。

 class ICallback : public IUnknown { virtual HRESULT CallbackFunc() = 0; }; Class IComponent : public IUnknown { virtual HRESULT MethodWithCallback(ICallback * cb) = 0; };
      
      







Pythonクラスは、゜ヌスの䞋のplusむンタヌフェむスから継承できたせん。 したがっお、プロゞェクトのシステム郚分では、Pythonに呌び出しを転送する独自の実装を䜜成する必芁がありたした。



.h

 //Forward declaration struct _object; typedef struct _object PyObject; class CCallback : public ICallback { //COM stuff ... CCallback * Create(); // ICallback virtual HRESULT CallbackFunc(); public: void SetPythonCallbackObject(PyObject * callback_handler); private: PyObject * m_pPythonCallbackObject; };
      
      







.cpp

 const char PythonMethodName[] = "PythonCallbackMethod"; void CCallback::SetPythonCallbackObject(PyObject * callback_handler) { // Do not addref to avoid cyclic dependency m_pPythonCallbackObject = callback_handler; } HRESULT CCallback::CallbackFunc() { if(!m_pPythonCallbackObject) return S_OK; // Acquire GIL PyGILState_STATE gstate = PyGILState_Ensure(); if ( gstate == PyGILState_UNLOCKED ) { // Call the python method char * methodName = const_cast<char *>(PythonMethodName); //Py_Api doesn't work with constant char * PyObject * ret = PyObject_CallMethod(m_pPythonCallbackObject, methodName, NULL); if (!ret) { if (PyErr_Occurred()) { PyErr_Print(); } std::cout<<"cannot call"<<PythonMethodName<<std::endl; } else Py_DecRef(ret); } // Release the GIL PyGILState_Release(gstate); return S_OK; }
      
      







このコヌドの特別な瞬間は、GILのキャプチャです。 そうでない堎合、せいぜい、PythonはGILがキャプチャされおいるこずを確認するず壊れたすが、ほずんどの堎合、ハングするかミスをしたす。



コン゜ヌルアプリケヌションがあるため、コン゜ヌルぞの゚ラヌ出力です。 Pythonコヌドが䟋倖をスロヌした堎合でも、ハンドラヌは䟋倖をキャッチしおトレヌスバックを出力したす。



シトンの偎面からは、次のようになりたす。

 cdef class PyCallback (object): cdef CComPtr[ICallback] callback def __cinit__(self): self.callback.assign( CCallback.Create() ) self.callback.inArg().SetPythonCallbackObject(<PyObject *> self) def PythonCallbackMethod(self): print "PythonCallbackMethod called" cdef class Component: cdef CComPtr[IComponent] thisptr def __cinit__(self): // Create IComponent instance ... def CallMethodWithCallback(self, PyCallback callback): cdef IComponent * component = self.thisptr.inArg() cdef ICallback * cb = callback.callback.inArg() hresult = 0 with nogil: hresult = component.MethodWithCallback(cb) hresultcheck(hresult)
      
      







MethodWithCallback() GIL, .





 component = Component() callback = PyCallback() component.CallMethodWithCallback(callback)
      
      







, . , API, cython.



, . , , . , GIL ( , ), . PyGILState_Ensure() , , , .



, , , . , , , . .



. , , . PyRun_FileExFlags(), , PyArena_Free()

  PyThreadState *_save = PyEval_SaveThread(); while (GetCurrentlyActiveCallbacks() > 0) ; // semicolon here is correct PyEval_RestoreThread(_save);
      
      







おわりに



python->cython->C++ API . , . . , – .



, . ++/ . .



PyTest . ! , .



, . , , , - . .



. cython' . .



Cython . , , . .



. , . – :

 PyRun_InteractiveLoop(stdin, "<stdin>");
      
      







++ IDE. , CI . ++ . . , — PyTest .



, . . , .



, , - .



All Articles