GET Unmanaged C ++ library in .NET. Full integration / SimbirSoft Blog / Sudo Null IT News FREE
This article discusses the round consolidation of C ++ libraries in a managed environment victimization Program Invoke. By stentorian integration is meant the possibility of inheritable library classes, implementing its interfaces (interfaces will be presented in managed cipher as abstract classes). Instances of the heirs can be "transferred" to an unmanaged environment.
The integration effect has been decorated more one time on the hub, but, as a decree, information technology is devoted to the integration of a few methods that cannot Be implemented in managed codification. Our task was to take a module from C ++ and come through work in .NET. The alternative to write again, for a number of reasons, was not considered, so we started integrating.
This article does non disclose all issues of integrating an unmanaged module into .NET. There are still nuances with exceedingly strings, logical values, etc. ... There are documentation and respective articles on the Habré connected these issues, so these issues were not well thought out Hera.
It is worth noting that the .NET wrap based connected Program Invoke is cross-chopine, it can be assembled on Mono + gcc.
Sealed socio-economic class integration
The first matter you have to realize when integrating with Platform Invoke is that this tool allows you to mix only certain functions. You can't just take and integrate a class. The result to the problem looks simple:
On the Unmanaged side, we publish a function:
SomeType ClassName_methodName(ClassName * instance, SomeOtherType someArgument) { example->methodName(someArgument); }
Get along not forget to add extern "C" to so much functions soh that their names are not decorated with a C ++ compiler. This would prevent us from integration these features into .Internet.
Close, repeat the procedure for all unexclusive methods of the class and integrate the subsequent functions into a course written in .NET. The resulting class cannot embody inherited, sol in .NET such a class is declared every bit sealed. How to get around this restriction and what IT is associated with - see below.
In the meantime, here's a
intelligent example: Unmanaged course of instruction:
class A { int mField; in the public eye: A( int someArgument); int someMethod( int someArgument); };
Features for integration:
A * A_createInstance(int someArgument) { return new A(someArgument); } int A_someMethod(A *instance, int someArgument) { return instance->someMethod( someArgument); } void A_deleteInstance(A *exemplify) { delete instance; }
Effectuation in .Net:
populace sealed class A { head-to-head 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 undynamic medical extern vacuum A_deleteInstance( IntPtr instance); internal A( IntPtr case) { Debug.Assert(example != IntPtr.Zero); mInstance = exemplify; mDelete = fictive; } common A( int someArgument) { mInstance = A_createInstance(someArgument); mDelete = true; } public int someMethod( int someArgument) { paying back A_someMethod(mInstance, someArgument); } intragroup IntPtr getUnmanaged() { return mInstance; } ~A() { if (mDelete) A_deleteInstance(mInstance); } }
The internal builder and method acting are needed to begin separate instances from unmanaged code and pass them back. The job of hereditary pattern is associated with passing the class instance back to the unmanaged environment. If we inherit class A in .Profit and redefine a number of its methods (conjecture that someMethod is declared with the practical keyword), we will non be capable to call the overridden code from an unmanaged environment.
Port integration
To integrate the interfaces, we need feedback. Those. for the full use of the integrated module, we indigence the ability to implement its interfaces. The implementation is related to the definition of methods in a managed environment. These methods will call for to make up called from unmanaged code. Here Callback Methods described in the documentation for Chopine Invoke will come to our aid.
On the unmanaged side of the environment, Callback appears as a pointer to a mathematical function:
typedef void (*PFN_MYCALLBACK )(); int _MyFunction(PFN_MYCALLBACK callback);
And in .Sack, the delegate bequeath play its role:
[UnmanagedFunctionPointerAttribute( CallingConvention.Cdecl)] state-supported delegate void MyCallback (); [ DllImport("MYDLL.DLL",CallingConvention.Cdecl)] public static extern void MyFunction( MyCallback callback);
With a feedback tool, we can well provide a call to overridden methods.
But ready to happen an case of the implementation of an interface, in an unmanaged surround, we also throw to present it as an instance of the implementation. So you have to write another implementation in an unmanaged environment. In this implementation, by the bye, we will make calls to Callback functions.
Unfortunately, this approach will non allow United States to do without system of logic in managed interfaces, thus we will have to present them in the make of abstract classes. Let's front at the code:
Unmanaged user interface:
assort IB { public: virtual int method( int arg) = 0; virtual ~IB() {}; };
Unmanaged implementation
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 acting( int arg); //... конструктор/деструктор }; void UnmanagedB ::setMethodHandler(IB_method_ptr ptr) { mIB_method_ptr = ptr; } int UnmanagedB ::method(int arg ) { return mIB_method_ptr( arg); }
UnmanagedB methods simply call the callbacks that the managed class gives information technology. Some other trouble awaits USA here. As extended as someone has a Spanish pointer to UnmanagedB in unmanaged encipher, we do not let the right to blue-pencil a class instance in managed code that responds to callback calls. The finis part of the article will be dedicated to solving this problem.
Features for integration:
UnmanagedB *UnmanagedB_createInstance() { return new UnmanagedB(); } void UnmanagedB_setMethodHandler(UnmanagedB *instance, IB_method_ptr ptr) { instance->setMethodHandler( ptr); } void UnmanagedB_deleteInstance(UnmanagedB *example) { blue-pencil instance; }
And here is the representation of the interface in managed code:
public abstract class AB { private IntPtr mInstance; [DllImport("shim", CallingConvention = CallingConvention.Cdecl)] private static extern IntPtr UnmanagedB_createInstance(); [DllImport("shim", CallingConvention = CallingConvention.Cdecl)] clubby static extern IntPtr UnmanagedB_setMethodHandler( IntPtr instance, [MarshalAs(UnmanagedType.FunctionPtr)] MethodHandler ptr); [DllImport("shim", CallingConvention = CallingConvention.Cdecl)] private static extern void UnmanagedB_deleteInstance( IntPtr case); [UnmanagedFunctionPointerAttribute( CallingConvention.Cdecl)] private delegate int MethodHandler( int arg); private int impl_method( int arg) { issue 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; } }
All interface method acting has a pair:
- The public abstract method acting that we will redefine
- The "caller" of the abstractionist method acting (private method with the prefix impl). It may appear like it doesn't make sense, only it is not. This method may contain extra conversions of arguments and execution results. It can likewise contain additional logic for passing play exceptions (as you mightiness have guessed, just short-lived an exclusion from surround to environment does non work, exceptions must also be embedded)
That's all. Now we can inherit the class AB and override its method method. If we need to pass the heir to unmanaged code, we will give mInstance instead, which bequeath call the overridden method via a Spanish pointer to the function / delegate. If we start out a pointer to the IB interface from an unmanaged surround, it testament need to represent represented equally an AB instance in a managed environment. To do this, we implement the nonremittal AB descendant:
internal sealed class BImpl : AB { [DllImport("shim", CallingConvention = CallingConvention.Cdecl)] private static extern int BImpl_method( IntPtr instance, int arg); private IntPtr mInstance; internecine BImpl( IntPtr instance) { Debug.Assert(illustration != IntPtr.Zero); mInstance = instance; } open override int method acting(int arg) { retrovert BImpl_method(mInstance, arg); } internal override IntPtr getUnmanaged() { return mInstance; } }
Features for desegregation:
int BImpl_method(IB *instance , int arg ) { instance->method( arg); }
More often than not, this is the same class integration without hereditary pattern support delineated above . It is non difficult to notice that when creating a BImpl instance, we also make over an UnmanagedB instance and make unnecessary callback bindings. If desired, this can comprise avoided, but these are subtleties, here we will not describe them.
Class Consolidation with Inheritance Support
The goal is to integrate the class and provide the ability to overrule its methods. We wish give back the pointer to the class in unmanaged, so we need to offer the class with callbacks in gild to be able to call the overridden methods.
Count a C sort that has an implementation in unmanaged computer code:
class C { public: virtual int method(int arg); practical ~C() {}; };
To get with, we will pretend that this is an interface. We integrate it the same manner as it was done above :
Unmanaged callback heir:
typedef int (*С_method_ptr )(int arg); division UnmanagedC : public cpp::C { С_method_ptr mС_method_ptr; public: nihility setMethodHandler( С_method_ptr ptr); virtual int method acting( int arg); }; void UnmanagedC ::setMethodHandler(С_method_ptr ptr) { mС_method_ptr = ptr; } int UnmanagedC ::method(int arg ) { return mС_method_ptr( arg); }
Features for integration:
//... опустим методы createInstance и deleteInstance nothingness UnmanagedC_setMethodHandler(UnmanagedC *exemplify , С_method_ptr ptr ) { instance->setMethodHandler( ptr); }
And implementation in .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); //... также импортируем функции для создания/удаления экземпляра класса clannish 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; } }
So, we rear end overturn the C.method method and it will make up correctly titled from the unmanaged surround. Simply we did not bring home the bacon a nonremittal effectuation call. Here the code from the outset part of the article will help the States :
To telephone the default execution, we need to integrate it. Also for its work, we need an congruent instance of the assort, which bequeath have to be created and deleted. We get the familiar cypher:
//... опять же опускаем createInstance и deleteInstance int C_method(C *instance, int arg) { return instance->method( arg); }
Let's finish the .Net implementation:
public class C { //... [DllImport("shim", CallingConvention = CallingConvention.Cdecl)] toffee-nosed static extern int C_method(IntPtr exemplify, int arg); open practical int method acting(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); } //... }
Such a class can be safely applied in managed code, inherit, overturn its methods, pass a pointer thereto in an unmanaged environment. Even if we did non redefine any methods, we nonetheless pass a pointer to UnmanagedC. This is not very rational, considering that unmanaged code will song unmanaged methods of class C broadcast medium calls done managed code. But so much is the price for the possibility of overriding methods. In the example attached to the clause, this case is demonstrated away calling the method method of class D. If you look at callstack, you can see the favorable sequence:
Exceptions
Platform Conjure does not allow passing exceptions, and to work around this problem we catch all exceptions before moving from environment to environment, enfold the information about the exception in a special class and pass it on. Along the other side, we generate an exception based on the information received.
We were apotropaic. Our C ++ module throws only exceptions of type ModuleException or its descendants. Then it's enough for us to capture this exception in all methods in which it can represent generated. To project an exception object into a managed environment, we pauperization to integrate the ModuleException class. In theory, the exception should contain a text message, but I do non want to get to with the topic of strand marshaling in that article, so in the example there will be "error codes":
public unopened 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; } intramural ModuleException( IntPtr case) { Debug.Assert(instance != IntPtr.Zero); mInstance = instance; mDelete = fictitious; } ~ModuleException() { if (mDelete) ModuleException_deleteInstance(mInstance); } //... пропущено getUnmanaged }
Immediately suppose the C :: method send away throw a ModuleException. Revision the class with exception keep going:
//Весь класс описывать не будем, ниже приведены только изменения typedef int (*С_method_ptr )(int arg, ModuleException **error); int UnmanagedC ::method(int arg ) { ModuleException *error = nullptr; int consequence = mС_method_ptr( arg, &error); if (error != nullptr) { int encode = error->getCode(); //... управление удалением экземпляра error описано ниже и в сэмпле make ModuleException(code); } return outcome; }
int C_method(C *case, 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 unmoving extern int C_method(IntPtr example, int arg, ref IntPtr error); [UnmanagedFunctionPointerAttribute( CallingConvention.Cdecl)] inward delegate int MethodHandler( int arg, referee IntPtr error); private int impl_method( int arg, referee IntPtr error) { try { return method(arg); } catch (ModuleException ex) { fault = unfashionable.getUnmanaged(); return 0; } } public realistic int method(int arg) { IntPtr misplay = IntPtr.Zilch; int result = C_method(mInstance, arg, ref erroneousness); if (error != IntPtr.Zero) throw ModuleException(error); return result; } //... }
Here we are too in for trouble with memory direction. In the impl_method method, we pass a arrow to an error, but the Garbage Collector tush delete it earlier it is prepared in unmanaged write in code. IT's time to deal with this job!
Callback garbage collector
Hera I mustiness articulate that we are more or less lucky. All classes and interfaces of the joint module are inheritable from a in for IObject interface containing the addRef and release methods. We knew that all over in the module, when the cursor was passed, the addRef call was ready-made. And whenever the need for a pointer disappeared, a release call was made. Imputable this approach, we could easy track whether the module of necessity an unmanaged pointer or whether the callbacks can already be deleted.
To avoid deleting managed objects used in an unmanaged environment, we need a manager for these objects. It will read addRef and release calls from unmanaged write in code and release managed objects when they are no longer needed.
The calls to addRef and release volition be thrown from unmanaged code to managed, so the first matter we need is a division that will provide such a forward:
typedef long (*UnmanagedObjectManager_remove )(void * illustrate); typedef invalid (*UnmanagedObjectManager_add )(void * instance); class UnmanagedObjectManager { static UnmanagedObjectManager mInstance; UnmanagedObjectManager_remove mRemove; UnmanagedObjectManager_add mAdd; semipublic: static annul add( void *illustration); static all-night remove( nihility *instance); unchangeable void setAdd( UnmanagedObjectManager_add ptr); static void setRemove( UnmanagedObjectManager_remove ptr); }; UnmanagedObjectManager UnmanagedObjectManager ::mInstance; void UnmanagedObjectManager ::add(avoid * example ) { if (mInstance.mAdd == nullptr) return; mInstance.mAdd( illustration); } long UnmanagedObjectManager ::transfer(void * instance ) { if (mInstance.mRemove == nullptr) return 0; restoration mInstance.mRemove( illustration); } void UnmanagedObjectManager ::setAdd(UnmanagedObjectManager_add ptr ) { mInstance.mAdd = ptr; } void UnmanagedObjectManager ::setRemove(UnmanagedObjectManager_remove ptr) { mInstance.mRemove = ptr; }
The second thing we need to do is to redefine the addRef and discharge of the IObject interface so that they change the counter values of our manager, stored in managed code:
template class TObjectManagerObjectImpl : public T { mutable bool mManagedObjectReleased; public: TObjectManagerObjectImpl() : mManagedObjectReleased( false) { } practical ~TObjectManagerObjectImpl() { UnmanagedObjectManager::absent(getInstance()); } void *getInstance() const { return ( vitiate *) 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 ensue == 0; } void resetManagedObject() const { mManagedObjectReleased = literal; } };
Like a sho the UnmanagedB and UnmanagedC classes need to be transmitted from the TObjectManagerObjectImpl class. Consider the instance of UnmanagedC:
grade UnmanagedC : public TObjectManagerObjectImpl { С_method_ptr mС_method_ptr; public: UnmanagedC(); void setMethodHandler( С_method_ptr ptr); virtual int method( int arg); essential ~UnmanagedC(); };
The C class implements the IObject interface, but immediately the addRef and release methods are overridden by the TObjectManagerObjectImpl class, so the object manager in the managed environment wish calculate the bi of pointers.
It's time to get a load at the encipher of the manager himself:
internal static socio-economic class ObjectManager { //... импортируем всё, что необходимо, см. сэмпл private static AddHandler mAddHandler; private static RemoveHandler mRemoveHandler; secret course of instruction Holder { internal int count; internal Object ptr; } private static Lexicon< IntPtr, Holder> mObjectMap; private unchangeable long removeImpl( IntPtr instance) { return remove(instance); } private static invalidate addImpl(IntPtr illustrate) { add(instance); } static ObjectManager() { mAddHandler = new AddHandler(addImpl); UnmanagedObjectManager_setAdd(mAddHandler); mRemoveHandler = new RemoveHandler(removeImpl); UnmanagedObjectManager_setRemove(mRemoveHandler); mObjectMap = new Dictionary(); } inside static void tot(IntPtr instance, Physical object ptr = nothing) { Holder holder; if (!mObjectMap.TryGetValue(instance, out holder)) { holder = new Bearer(); holder.count = 1; holder.ptr = ptr; mObjectMap.Add(example, holder); } else { if (bearer.ptr == null && ptr != null) holder.ptr = ptr; holder.count++; } } internal unchangeable long remove(IntPtr instance) { long final result = 0; Holder holder; if (mObjectMap.TryGetValue(illustrate, out bearer)) { holder.count--; if (holder.count == 0) mObjectMap.Remove(instance); result = holder.count; } return key result; } }
Right away we experience an object manager. Before passage the instance of the managed object to an unmanaged environment, we must add it to the manager. So the getUnmanaged method of classes Artium Baccalaurens and C needs to personify changed. Hither is the code for class C:
internal IntPtr getUnmanaged() { ObjectManager.add(mHandlerInstance, this); return mHandlerInstance; }
Now we can beryllium sure that the callbacks will work As longsighted atomic number 3 required.
Acknowledged the specifics of the mental faculty, you will need to rescript the classes, replacing every last calls to ClassName_deleteInstance with calls to IObject :: release, and also think to do IObject :: addRef where necessary. In particular, this will avoid the premature removal of a ModuleException, even if the refuse collector deletes the managed wrapper, the unmanaged instance, being the heritor of IObject, will not constitute deleted until the unmanaged module processes the mistake and raises an IObject_release for it.
Conclusion
In fact, while we were geared in module integration, we experienced a huge come of emotions, learned a lot of offensive language and learned to sleep while standing. Perhaps we should deficiency this article to be utile to somebody, but God forbid. Of course, resolution the problems of memory management, hereditary pattern, and passing exceptions was fun. But we integrated removed from three classes and there was far from one method in them. It was an endurance test.
If you nonmoving encounter such a trouble, then present's a tip over for you: love life Sublime Text, regular expressions and snippets. This small set has saved us from alcoholism.
PS A working example of library integration is available at github.com/simbirsoft-public/pinvoke_example
DOWNLOAD HERE
GET Unmanaged C ++ library in .NET. Full integration / SimbirSoft Blog / Sudo Null IT News FREE
Posted by: hendersonpentrong1942.blogspot.com
0 Response to "GET Unmanaged C ++ library in .NET. Full integration / SimbirSoft Blog / Sudo Null IT News FREE"
Post a Comment