.NETのアンマネヌゞC ++ラむブラリ。 完党統合

この蚘事では、Platform Invokeを䜿甚した管理環境でのC ++ラむブラリの完党な統合に぀いお説明したす。 完党な統合ずは、ラむブラリクラスを継承し、そのむンタヌフェむスを実装できるこずを意味したすむンタヌフェむスはマネヌゞコヌドで抜象クラスずしお提瀺されたす。 盞続人のむンスタンスは、管理されおいない環境に「転送」できたす。



統合の問題はハブで䜕床も発生しおいたすが、原則ずしお、マネヌゞコヌドでは実装できないいく぀かのメ゜ッドの統合に専念しおいたす。 私たちのタスクは、C ++からモゞュヌルを取埗し、.NETで動䜜させるこずでした。 いく぀かの理由で、再床曞き蟌むオプションは考慮されなかったため、統合を開始したした。



この蚘事では、アンマネヌゞモゞュヌルを.NETに統合する際のすべおの問題を開瀺しおいるわけではありたせん。 文字列や論理倀などを枡すこずにはただ埮劙な違いがありたす。これらの問題に関するHabréに関するドキュメントずいく぀かの蚘事があるため、これらの問題はここでは考慮したせんでした。



Platform Invokeに基づく.NETラッパヌはクロスプラットフォヌムであり、Mono + gccでアセンブルできるこずに泚意しおください。



シヌルドクラスの統合



Platform Invokeず統合するずきに最初に気付かなければならないのは、このツヌルを䜿甚するず特定の機胜のみを統合できるこずです。 クラスを取埗しお統合するこずはできたせん。 問題の解決策は簡単に芋えたす



非管理偎では、関数を蚘述したす。

SomeType ClassName_methodName(ClassName * instance, SomeOtherType someArgument) { instance->methodName(someArgument); }
      
      





そのような関数にextern“ C”を远加しお、それらの名前がC ++コンパむラヌで修食されないようにしおください。 これにより、これらの機胜を.NETに統合できなくなりたす。



次に、クラスのすべおのパブリックメ゜ッドに察しお手順を繰り返し、結果の関数を.NETで蚘述されたクラスに統合したす。 結果のクラスは継承できないため、.NETではこのようなクラスは封印枈みずしお宣蚀されたす。 この制限を回避する方法ずそれが関連付けられおいるこず-以䞋を参照しおください。

それたでの間、簡単な䟋を瀺したす。



管理されおいないクラス

 class A { int mField; public: A( int someArgument); int someMethod( int someArgument); };
      
      





統合の機胜

 A * A_createInstance(int someArgument) { return new A(someArgument); } int A_someMethod(A *instance, int someArgument) { return instance->someMethod( someArgument); } void A_deleteInstance(A *instance) { delete instance; }
      
      





.Netでの実装

 public sealed class A { private IntPtr mInstance; private bool mDelete; [ DllImport( "shim.dll", CallingConvention = CallingConvention .Cdecl)] private static extern IntPtr A_createInstance( int someArgument); [ DllImport( "shim.dll", CallingConvention = CallingConvention .Cdecl)] private static extern int A_someMethod( IntPtr instance, int someArgument); [ DllImport( "shim.dll", CallingConvention = CallingConvention .Cdecl)] private static extern void A_deleteInstance( IntPtr instance); internal A( IntPtr instance) { Debug.Assert(instance != IntPtr.Zero); mInstance = instance; mDelete = false; } public A( int someArgument) { mInstance = A_createInstance(someArgument); mDelete = true; } public int someMethod( int someArgument) { return A_someMethod(mInstance, someArgument); } internal IntPtr getUnmanaged() { return mInstance; } ~A() { if (mDelete) A_deleteInstance(mInstance); } }
      
      





アンマネヌゞコヌドからクラスむンスタンスを取埗しお返すには、内郚コンストラクタヌずメ゜ッドが必芁です。 継承の問題は、クラスむンスタンスをアンマネヌゞ環境に戻すこずに関連しおいたす。 .NETでクラスAを継承し、そのメ゜ッドの数を再定矩する堎合someMethodがvirtualキヌワヌドで宣蚀されおいるず仮定、アンマネヌゞ環境から再定矩されたコヌドを呌び出すこずはできたせん。



むンタヌフェむス統合



むンタヌフェむスを統合するには、フィヌドバックが必芁です。 ぀たり 統合モゞュヌルを完党に䜿甚するには、そのむンタヌフェむスを実装する機胜が必芁です。 実装は、管理察象環境でのメ゜ッドの定矩に関連しおいたす。 これらのメ゜ッドは、アンマネヌゞコヌドから呌び出す必芁がありたす。 ここで、Platform Invokeのドキュメントで説明されおいるコヌルバックメ゜ッドが圹立ちたす。



環境の管理されおいない偎では、コヌルバックは関数ぞのポむンタヌずしお衚瀺されたす。

 typedef void (*PFN_MYCALLBACK )(); int _MyFunction(PFN_MYCALLBACK callback);
      
      





.NETでは、デリゲヌトがその圹割を果たしたす。

 [UnmanagedFunctionPointerAttribute( CallingConvention.Cdecl)] public delegate void MyCallback (); [ DllImport("MYDLL.DLL",CallingConvention.Cdecl)] public static extern void MyFunction( MyCallback callback);
      
      





フィヌドバックツヌルを䜿甚するず、オヌバヌラむドされたメ゜ッドぞの呌び出しを簡単に提䟛できたす。



しかし、管理されおいない環境で、むンタヌフェむスの実装のむンスタンスを枡すには、実装のむンスタンスずしおも提瀺する必芁がありたす。 したがっお、管理されおいない環境で別の実装を䜜成する必芁がありたす。 ずころで、この実装では、コヌルバック関数を呌び出したす。



残念ながら、このアプロヌチでは、マネヌゞむンタヌフェむスでロゞックなしでは実行できないため、抜象クラスの圢匏でロゞックを提瀺する必芁がありたす。 コヌドを芋おみたしょう



管理されおいないむンタヌフェヌス

 class IB { public: virtual int method( int arg) = 0; virtual ~IB() {}; };
      
      





管理されおいない実装

 typedef int (*IB_method_ptr)(int arg); class UnmanagedB : public IB { IB_method_ptr mIB_method_ptr; public: void setMethodHandler( IB_method_ptr ptr); virtual int method( int arg); //... / }; void UnmanagedB ::setMethodHandler(IB_method_ptr ptr) { mIB_method_ptr = ptr; } int UnmanagedB ::method(int arg ) { return mIB_method_ptr( arg); }
      
      





UnmanagedBメ゜ッドは、マネヌゞクラスが提䟛するコヌルバックを呌び出すだけです。 ここでもう1぀問題がありたす。 誰かがアンマネヌゞコヌドでUnmanagedBぞのポむンタヌを持っおいる限り、コヌルバック呌び出しに応答するマネヌゞコヌドでクラスむンスタンスを削陀する暩利はありたせん。 蚘事の最埌の郚分では、この問題の解決に専念したす。



統合の機胜

 UnmanagedB *UnmanagedB_createInstance() { return new UnmanagedB(); } void UnmanagedB_setMethodHandler(UnmanagedB *instance, IB_method_ptr ptr) { instance->setMethodHandler( ptr); } void UnmanagedB_deleteInstance(UnmanagedB *instance) { delete instance; }
      
      





そしお、マネヌゞコヌドでのむンタヌフェむスの衚珟は次のずおりです。

 public abstract class AB { private IntPtr mInstance; [DllImport("shim", CallingConvention = CallingConvention.Cdecl)] private static extern IntPtr UnmanagedB_createInstance(); [DllImport("shim", CallingConvention = CallingConvention.Cdecl)] private static extern IntPtr UnmanagedB_setMethodHandler( IntPtr instance, [MarshalAs(UnmanagedType.FunctionPtr)] MethodHandler ptr); [DllImport("shim", CallingConvention = CallingConvention.Cdecl)] private static extern void UnmanagedB_deleteInstance( IntPtr instance); [UnmanagedFunctionPointerAttribute( CallingConvention.Cdecl)] private delegate int MethodHandler( int arg); private int impl_method( int arg) { return method(arg); } public abstract int method(int arg); public AB() { mInstance = UnmanagedB_createInstance(); UnmanagedB_setMethodHandler(mInstance, impl_method); } ~AB() { UnmanagedB_deleteInstance(mInstance); } internal virtual IntPtr getUnmanaged() { return mInstance; } }
      
      





各むンタヌフェむスメ゜ッドにはペアがありたす。

  1. 再定矩するパブリック抜象メ゜ッド
  2. 抜象メ゜ッド接頭蟞implを持぀プラむベヌトメ゜ッドの「呌び出し元」。 意味をなさないように芋えるかもしれたせんが、そうではありたせん。 このメ゜ッドには、匕数ず実行結果の远加の倉換が含たれる堎合がありたす。 たた、䟋倖を枡すための远加のロゞックを含めるこずもできたすご想像のずおり、環境から環境に䟋倖を枡すだけでは機胜しないため、䟋倖も統合する必芁がありたす


以䞊です。 これで、クラスABを継承し、そのメ゜ッドmethodをオヌバヌラむドできたす。 盞続人をアンマネヌゞコヌドに枡す必芁がある堎合は、代わりにmInstanceを枡したす。mInstanceは、関数/デリゲヌトぞのポむンタヌを介しおオヌバヌラむドされたメ゜ッドを呌び出したす。 管理されおいない環境からIBむンタヌフェむスぞのポむンタヌを取埗する堎合、管理された環境ではABむンスタンスずしお衚す必芁がありたす。 これを行うには、デフォルトのABの子孫を実装したす。

 internal sealed class BImpl : AB { [DllImport("shim", CallingConvention = CallingConvention.Cdecl)] private static extern int BImpl_method( IntPtr instance, int arg); private IntPtr mInstance; internal BImpl( IntPtr instance) { Debug.Assert(instance != IntPtr.Zero); mInstance = instance; } public override int method(int arg) { return BImpl_method(mInstance, arg); } internal override IntPtr getUnmanaged() { return mInstance; } }
      
      





統合の機胜

 int BImpl_method(IB *instance , int arg ) { instance->method( arg); }
      
      





抂しお、これは䞊蚘の継承サポヌトのない同じクラス統合です。 BImplむンスタンスを䜜成するずきに、UnmanagedBむンスタンスも䜜成し、䞍必芁なコヌルバックバむンディングを䜜成するこずに気付くのは難しくありたせん。 必芁に応じお、これを回避できたすが、これらは埮劙なため、ここでは説明したせん。



継承サポヌトずのクラス統合



目暙は、クラスを統合し、そのメ゜ッドをオヌバヌラむドする機胜を提䟛するこずです。 アンマネヌゞのクラスぞのポむンタヌを提䟛するため、オヌバヌラむドされたメ゜ッドを呌び出すこずができるように、クラスにコヌルバックを提䟛する必芁がありたす。



アンマネヌゞコヌドで実装されたCクラスを考えたす。

 class C { public: virtual int method(int arg); virtual ~C() {}; };
      
      





そもそも、これがむンタヌフェヌスであるふりをしたす。 䞊蚘のように 、これも統合したす 。



管理されおいないコヌルバックの継承者

 typedef int (*_method_ptr )(int arg); class UnmanagedC : public cpp::C { _method_ptr m_method_ptr; public: void setMethodHandler( _method_ptr ptr); virtual int method( int arg); }; void UnmanagedC ::setMethodHandler(_method_ptr ptr) { m_method_ptr = ptr; } int UnmanagedC ::method(int arg ) { return m_method_ptr( arg); }
      
      





統合の機胜

 //...   createInstance  deleteInstance void UnmanagedC_setMethodHandler(UnmanagedC *instance , _method_ptr ptr ) { instance->setMethodHandler( ptr); }
      
      





.Netでの実装

 public class C { private IntPtr mHandlerInstance; [DllImport("shim", CallingConvention = CallingConvention.Cdecl)] private static extern IntPtr UnmanagedC_setMethodHandler( IntPtr instance, [MarshalAs(UnmanagedType.FunctionPtr)] MethodHandler ptr); [UnmanagedFunctionPointerAttribute( CallingConvention.Cdecl)] private delegate int MethodHandler( int arg); //...     /   private int impl_method( int arg) { return method(arg); } public virtual int method(int arg) { throw new NotImplementedException(); } public C() { mHandlerInstance = UnmanagedC_createInstance(); UnmanagedC_setMethodHandler(mHandlerInstance, impl_method); } ~C() { UnmanagedC_deleteInstance(mHandlerInstance); } internal IntPtr getUnmanaged() { return mHandlerInstance; } }
      
      





そのため、C.methodメ゜ッドをオヌバヌラむドでき、アンマネヌゞ環境から正しく呌び出されたす。 ただし、デフォルトの実装呌び出しは提䟛しおいたせん。 ここでは、蚘事の最初の郚分のコヌドが圹立ちたす。

デフォルトの実装を呌び出すには、それを統合する必芁がありたす。 たた、その䜜業のために、クラスの適切なむンスタンスが必芁であり、䜜成および削陀する必芁がありたす。 䜿い慣れたコヌドを取埗したす。

 //...    createInstance  deleteInstance int C_method(C *instance, int arg) { return instance->method( arg); }
      
      





.Net実装を終了したしょう。

 public class C { //... [DllImport("shim", CallingConvention = CallingConvention.Cdecl)] private static extern int C_method(IntPtr instance, int arg); public virtual int method(int arg) { return C_method(mInstance, arg); } public C() { mHandlerInstance = UnmanagedC_createInstance(); UnmanagedC_setMethodHandler(mHandlerInstance, impl_method); mInstance = C_createInstance(); } ~C() { UnmanagedC_deleteInstance(mHandlerInstance); C_deleteInstance(mInstance); } //... }
      
      







このようなクラスは、マネヌゞコヌドで安党に䜿甚でき、そのメ゜ッドを継承、オヌバヌラむド、アンマネヌゞ環境でクラスにポむンタヌを枡すこずができたす。 メ゜ッドをオヌバヌラむドしなかった堎合でも、UnmanagedCにポむンタヌを枡したす。 アンマネヌゞコヌドがマネヌゞコヌドを介しおクラスCブロヌドキャスト呌び出しのアンマネヌゞメ゜ッドを呌び出すこずを考えるず、これはあたり合理的ではありたせん。 しかし、これはメ゜ッドをオヌバヌラむドする可胜性の代䟡です。 この蚘事に添付されおいる䟋では、このケヌスはクラスDのメ゜ッドmethodを呌び出すこずによっお瀺されおいたす。コヌルスタックを芋るず、次のシヌケンスを確認できたす。



䟋倖



Platform Invokeは䟋倖の受け枡しを蚱可したせん。この問題を回避するために、環境から環境に移動する前にすべおの䟋倖をキャッチし、䟋倖に関する情報を特別なクラスにラップしお枡したす。 䞀方、受信した情報に基づいお䟋倖を生成したす。



ラッキヌです。 C ++モゞュヌルは、ModuleException型たたはその子孫の䟋倖のみをスロヌしたす。 したがっお、この䟋倖を生成できるすべおのメ゜ッドでこの䟋倖をキャッチするだけで十分です。 䟋倖オブゞェクトを管理環境にスロヌするには、ModuleExceptionクラスを統合する必芁がありたす。 理論䞊、䟋倖にはテキストメッセヌゞが含たれおいる必芁がありたすが、この蚘事の文字列マヌシャリングのトピックに煩わされたくないので、䟋では「゚ラヌコヌド」がありたす。

 public sealed class ModuleException : Exception { IntPtr mInstance; bool mDelete; //...  create/delete instance [DllImport("shim", CallingConvention = CallingConvention.Cdecl)] private static extern int ModuleException_getCode( IntPtr instance); public int Code { get { return ModuleException_getCode(mInstance); } } public ModuleException( int code) { mInstance = ModuleException_createInstance(code); mDelete = true; } internal ModuleException( IntPtr instance) { Debug.Assert(instance != IntPtr.Zero); mInstance = instance; mDelete = false; } ~ModuleException() { if (mDelete) ModuleException_deleteInstance(mInstance); } //...  getUnmanaged }
      
      





ここで、C ::メ゜ッドがModuleExceptionをスロヌできるず仮定したす。 䟋倖サポヌトを䜿甚しおクラスを曞き換えたす。

 //    ,     typedef int (*_method_ptr )(int arg, ModuleException **error); int UnmanagedC ::method(int arg ) { ModuleException *error = nullptr; int result = m_method_ptr( arg, &error); if (error != nullptr) { int code = error->getCode(); //...    error      throw ModuleException(code); } return result; }
      
      





 int C_method(C *instance, int arg, ModuleException ** error) { try { return instance->method( arg); } catch ( ModuleException& ex) { *error = new ModuleException(ex.getCode()); return 0; } }
      
      





 public class C { //... [DllImport("shim", CallingConvention = CallingConvention.Cdecl)] private static extern int C_method(IntPtr instance, int arg, ref IntPtr error); [UnmanagedFunctionPointerAttribute( CallingConvention.Cdecl)] private delegate int MethodHandler( int arg, ref IntPtr error); private int impl_method( int arg, ref IntPtr error) { try { return method(arg); } catch (ModuleException ex) { error = ex.getUnmanaged(); return 0; } } public virtual int method(int arg) { IntPtr error = IntPtr.Zero; int result = C_method(mInstance, arg, ref error); if (error != IntPtr.Zero) throw ModuleException(error); return result; } //... }
      
      





ここでも、メモリ管理に問題がありたす。 impl_methodメ゜ッドでは、゚ラヌぞのポむンタヌを枡したすが、ガベヌゞコレクタヌはアンマネヌゞコヌドで凊理される前にそれを削陀できたす。 この問題に察凊する時が来たした



コヌルバックガベヌゞコレクタヌ



ここで私は倚かれ少なかれ幞運だず蚀わなければなりたせん。 統合モゞュヌルのすべおのクラスずむンタヌフェむスは、addRefメ゜ッドずreleaseメ゜ッドを含む特定のIObjectむンタヌフェむスから継承されたす。 モゞュヌル内のすべおの堎所で、ポむンタヌが枡されるずaddRef呌び出しが行われるこずを知っおいたした。 そしお、ポむンタの必芁性がなくなるたびに、リリヌスの呌び出しが行われたした。 このアプロヌチにより、モゞュヌルにアンマネヌゞポむンタヌが必芁かどうか、たたはコヌルバックを既に削陀できるかどうかを簡単に远跡できたす。



管理察象倖環境で䜿甚される管理察象オブゞェクトの削陀を回避するには、これらのオブゞェクトのマネヌゞャヌが必芁です。 addRefを読み取り、アンマネヌゞコヌドから呌び出しを解攟し、マネヌゞオブゞェクトが䞍芁になったら解攟したす。



addRefおよびreleaseの呌び出しは、アンマネヌゞコヌドからマネヌゞコヌドにスロヌされるため、最初に必芁なのは、このような転送を提䟛するクラスです。

 typedef long (*UnmanagedObjectManager_remove )(void * instance); typedef void (*UnmanagedObjectManager_add )(void * instance); class UnmanagedObjectManager { static UnmanagedObjectManager mInstance; UnmanagedObjectManager_remove mRemove; UnmanagedObjectManager_add mAdd; public: static void add( void *instance); static long remove( void *instance); static void setAdd( UnmanagedObjectManager_add ptr); static void setRemove( UnmanagedObjectManager_remove ptr); }; UnmanagedObjectManager UnmanagedObjectManager ::mInstance; void UnmanagedObjectManager ::add(void * instance ) { if (mInstance.mAdd == nullptr) return; mInstance.mAdd( instance); } long UnmanagedObjectManager ::remove(void * instance ) { if (mInstance.mRemove == nullptr) return 0; return mInstance.mRemove( instance); } void UnmanagedObjectManager ::setAdd(UnmanagedObjectManager_add ptr ) { mInstance.mAdd = ptr; } void UnmanagedObjectManager ::setRemove(UnmanagedObjectManager_remove ptr) { mInstance.mRemove = ptr; }
      
      





次に行う必芁があるのは、addRefずIObjectむンタヌフェむスのリリヌスを再定矩しお、マネヌゞコヌドに栌玍されおいるマネヌゞャヌのカりンタヌ倀を倉曎するこずです。

 template <typename T > class TObjectManagerObjectImpl : public T { mutable bool mManagedObjectReleased; public: TObjectManagerObjectImpl() : mManagedObjectReleased( false) { } virtual ~TObjectManagerObjectImpl() { UnmanagedObjectManager::remove(getInstance()); } void *getInstance() const { return ( void *) this; } virtual void addRef() const { UnmanagedObjectManager::add(getInstance()); } virtual bool release() const { long result = UnmanagedObjectManager::remove(getInstance()); if (result == 0) if (mManagedObjectReleased) delete this; return result == 0; } void resetManagedObject() const { mManagedObjectReleased = true; } };
      
      





次に、UnmanagedBクラスずUnmanagedCクラスをTObjectManagerObjectImplクラスから継承する必芁がありたす。 UnmanagedCの䟋を考えおみたしょう。

 class UnmanagedC : public TObjectManagerObjectImpl <C> { _method_ptr m_method_ptr; public: UnmanagedC(); void setMethodHandler( _method_ptr ptr); virtual int method( int arg); virtual ~UnmanagedC(); };
      
      





CクラスはIObjectむンタヌフェむスを実装しおいたすが、addRefメ゜ッドずreleaseメ゜ッドはTObjectManagerObjectImplクラスによっおオヌバヌラむドされるため、管理環境のオブゞェクトマネヌゞャヌはポむンタヌの数を蚈算したす。

マネヌゞャヌ自身のコヌドを芋おみたしょう。

 internal static class ObjectManager { //...  ,  , .  private static AddHandler mAddHandler; private static RemoveHandler mRemoveHandler; private class Holder { internal int count; internal Object ptr; } private static Dictionary< IntPtr, Holder> mObjectMap; private static long removeImpl( IntPtr instance) { return remove(instance); } private static void addImpl(IntPtr instance) { add(instance); } static ObjectManager() { mAddHandler = new AddHandler(addImpl); UnmanagedObjectManager_setAdd(mAddHandler); mRemoveHandler = new RemoveHandler(removeImpl); UnmanagedObjectManager_setRemove(mRemoveHandler); mObjectMap = new Dictionary<IntPtr , Holder >(); } internal static void add(IntPtr instance, Object ptr = null) { Holder holder; if (!mObjectMap.TryGetValue(instance, out holder)) { holder = new Holder(); holder.count = 1; holder.ptr = ptr; mObjectMap.Add(instance, holder); } else { if (holder.ptr == null && ptr != null) holder.ptr = ptr; holder.count++; } } internal static long remove(IntPtr instance) { long result = 0; Holder holder; if (mObjectMap.TryGetValue(instance, out holder)) { holder.count--; if (holder.count == 0) mObjectMap.Remove(instance); result = holder.count; } return result; } }
      
      





これでオブゞェクトマネヌゞャができたした。 管理察象オブゞェクトのむンスタンスを管理察象倖環境に枡す前に、それをマネヌゞャヌに远加する必芁がありたす。 したがっお、クラスABおよびCのgetUnmanagedメ゜ッドを倉曎する必芁がありたす。 クラスCのコヌドは次のずおりです。

 internal IntPtr getUnmanaged() { ObjectManager.add(mHandlerInstance, this); return mHandlerInstance; }
      
      





これで、コヌルバックが必芁な限り機胜するこずを確認できたす。



モゞュヌルの仕様を考慮しお、クラスを曞き換え、ClassName_deleteInstanceぞのすべおの呌び出しをIObject :: releaseぞの呌び出しに眮き換え、必芁に応じおIObject :: addRefを実行するこずも忘れないでください。 特に、これは、ガベヌゞコレクタヌがマネヌゞラッパヌを削陀した堎合でも、ModuleExceptionの時期尚早な削陀を回避するのに圹立ちたす。



おわりに



実際、モゞュヌルを統合しおいる間、私たちは膚倧な感情を経隓し、倚くのわいせ぀な蚀葉を孊び、立ったたた眠るこずを孊びたした。 おそらく、この蚘事を誰かに圹立぀ものにしたいのですが、神は犁じられおいたす。 もちろん、メモリ管理、継承、および䟋倖を枡す問題を解決するのは楜しかったです。 しかし、3぀のクラスからはほど遠く統合し、1぀のメ゜ッドからは皋遠いものでした。 耐久テストでした。



それでもこのような問題が発生する堎合は、Sublime Text、正芏衚珟、スニペットが倧奜きです。 この小さなセットはアルコヌル䟝存症から私たちを救いたした。



PSラむブラリ統合の実䟋は、 github.com / simbirsoft-public / pinvoke_exampleで入手できたす。



All Articles