So, following my previous post, I have decided that I might try C++/CLI after all, since the SWIG way goes nowhere except “down in flames”, and I really need to implement it.
Turns out that creating a C++/CLI wrapper around a fairly well-made C library is not that hard.
Considering the following .h file from the C library (which are the methods and structures to interface):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
typedef struct { int left; int top; int right; int bottom; } frame_t; typedef struct { /* IN */ int width; int height; /* OUT */ frame_t view; frame_t dest; } image_t; typedef struct { int page_width; int page_height; int margin; int nb_run; } parameters_t; int pack(image_t *images, int nb_images, parameters_t params); |
Add the .h and .c files to a new C++ CLR DLL project, then add a new cpp file, in which we will replicate all the C structures as classes.
We have to name our classes differently than the C structures, otherwise there will be conflicts, both with the compiler, and in our heads.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 |
#include <windows.h> #include <vcclr.h> #include "../../packer/packer.h" #using <System.dll> #using <mscorlib.dll> using namespace System; namespace Packer { public ref class Frame { public: int Left, Top, Right, Bottom; internal: frame_t ToNative() { frame_t f; f.left = Left; f.top = Top; f.right = Right; f.bottom = Bottom; return f; } Frame() { } Frame(const frame_t &f) { Left = f.left; Top = f.top; Right = f.right; Bottom = f.bottom; } }; public ref class Image { public: int Width, Height; Frame ^View, ^Dest; Image() { View = gcnew Frame(); Dest = gcnew Frame(); } internal: image_t ToNative() { image_t i; i.width = Width; i.height = Height; i.view = View->ToNative(); i.dest = Dest->ToNative(); return i; } }; public ref class Parameters { public: int PageWidth, PageHeight, Margin, NbRun; internal: parameters_t ToNative() { parameters_t p; p.page_width = PageWidth; p.page_height = PageHeight; p.margin = Margin; p.nb_run = NbRun; return p; } }; public ref class Packer { private: int _nb_images; image_t* nativeImages; public: Packer(int nb_images) { _nb_images = nb_images; nativeImages = new image_t[_nb_images]; } ~Packer() { delete[] nativeImages; } int Pack(array< Image^ >^% images, Parameters^ params) { // create a native array of image_t to send to the C library for(int i = 0; i < _nb_images; i++) { nativeImages[i] = images[i]->ToNative(); } // call the packing method int result = pack(nativeImages, _nb_images, params->ToNative()); // re-loop on images to get the values from native images, and assign them to managed images for(int i = 0; i < _nb_images; i++) { images[i]->View = gcnew Frame(nativeImages[i].view); images[i]->Dest = gcnew Frame(nativeImages[i].dest); } return result; } }; } |
The annoying thing when like me, you know pretty much nothing about C/C++, is that you never know where to specify pointers and stuff, and it’s mostly trial and error (it says it can’t build, let’s add a *… nope, a & ? Yes, that seems to work).
A few things to note:
- Provide internal ToNative() methods on the structure wrappers to ease up marshalling from managed objects to native structures.
- Also provide internal constructors taking a native structure as parameter, to ease up marshalling from native to managed.
- The “^%” syntax (for the “images” parameter of the “Pack” method) builds to a “ref” in the .Net method signature. Otherwise the calling C# can’t get the values back.
As you can see, it’s not that difficult after all. I guess for an experienced C/C++ developer it would be even more simple.