C++ CLI

Vor 10 Jahren gehörte neben Win32 und POSIX noch eine dritte quasi-Plattform zum ersten Entwurf des damalig GATE Projektes.
Und das war dotNet, genau genommen C++/CLI, also jene Erweiterung von C++, mit der man seit Studio 2005 verwalteten dotNet Code mit C++ kreuzen konnte.

Tja … gebraucht hat das keiner und am wenigsten ich selbst. … aber zumindest war es eine nette Erfahrung.


Folgende Info zu Visual Studio 2017 erregte neulich meine Aufmerksamkeit:

If your code needs to be safe or verifiable, then we recommend that you port it to C#.

Das steht bei der Info zum alten pure und safe Mode für C++/CLI. in der MS Doku.

Es ist kein besonderes Kunststück mit C++ CLI eine dotNet Anwendung zu bauen. Im sogenannten mixed Mode kann man freie Pointer und native APIs gemeinsam mit dotNet Assemblies nutzen … zumindest theoretisch.
Denn wenn die dotNet Security Features zur Geltung kommen, wird eine solche “Mixed Assembly” schnell als Risiko eingestuft und nicht ausgeführt.

Für Bibliotheken ist das ein sprichwörtlicher Killer.

Doch genau dafür gab es den pure Mode. Damit konnte man jeglichen C++ Code dotNet-like kompilieren, jedoch nicht gegen native Bibliotheken linken. Heißt also:

  • Algorithmen, Datenformate und In-Memory-Operationen: gut
  • WINAPI, DLLs, oder nicht-pure statische Libs: schlecht

Genau das faszinierte mich damals, denn so könnte man die Codes von ZLIB und Co als “pure” dotNet Assembly kompilieren und von C# oder VB.NET aus nutzen.

Aber auch der umgekehrte Weg war interessant: Ein C++ Framework, das optional als “echte” dotNet Assembly kompiliert werden kann.

Ein Problem dabei war das Aufbewahren von dotNet-Referenz Objekten in nicht-verwalteten C++ Objekten.

Pointer-Äquivalent für dotNet Objekt

Man kann zwar native Pointer in dotNet ref-Klassen aufnehmen, aber nicht umgekehrt.
Aber: Man kann eine ref-Instanz in ein GCHandle kapseln und dieses als IntPtr speichern und umgekehrt aus dem IntPtr wieder ein GCHandle machen. Und reine Integer kann man in ein natives Objekt bekanntlich problemlos aufnehmen.

Dafür hatte ich damals ein kleines Template geschrieben, das aus einem irgendwo gespeicherten intptr eine nutzbare dotNet Referenz machen konnte:

 1template<class T> Class CliHandle
 2{
 3protected:
 4  intptr_t& refaddr;  // external intptr to store object
 5
 6public:
 7  // bind to external `intptr` Instance
 8  CliHandle(intptr_t& h = 0) : refaddr(h) {}
 9  CliHandle(void*& ptr) 
10    : refaddr(reinterpret_cast<intptr_t&>(ptr)) 
11  { }
12  ~CliHandle() {}
13
14  // release object for intptr
15  void release()
16  {
17    if(this->refaddr)
18    {
19      System::Runtime::InteropServices::GCHandle gch(
20        System::Runtime::InteropServices::GCHandle::FromIntPtr(
21          System::IntPtr(this->refaddr)));
22      gch.Free();
23      this->refaddr = 0;
24    }
25  }
26
27  // embed object in intptr
28  void set(T t)
29  {
30    this->release();
31    System::Runtime::InteropServices::GCHandle gch(
32      System::Runtime::InteropServices::GCHandle::Alloc(t));
33    this->refaddr = (intptr_t)
34      System::Runtime::InteropServices::GCHandle::ToIntPtr(gch);
35  }
36  
37  // access object by intptr
38  T% get()
39  {
40    System::Runtime::InteropServices::GCHandle gch(
41      System::Runtime::InteropServices::GCHandle::FromIntPtr(
42        System::IntPtr(this->refaddr)));
43    return safe_cast<T%>(gch.Target);
44  }  
45
46  T% operator*()
47  {
48    return this->get();
49  }
50
51  T% operator->()
52  {
53    return this->get();
54  }
55};

So bettet man also dotNet Objekte in native Strukturen als intptr_t ein. Und in nativen Callbacks kann man über diesen Trick aus dem intptr wieder eine dotNet Instanz machen und darauf dann Methoden ausführen.

 1intptr_t my_dotnet_handle;
 2
 3CliHandle<DotNetClass> adapter(my_dotnet_handle);
 4adapter.set(ref new DotNetClass());
 5
 6adapter->dotnetFoo();
 7adapter->dotnetBar();
 8
 9adapter.release();

Theoretisch lassen sich so dotNet Codes auch durch reine C Schichten durchschleifen … aber so weit habe ich es damals nicht getrieben.

Fazit

Naja, wie gesagt, gebraucht habe ich das nie und so flog die Idee aus dem GATE Konzept und ist heute auch nicht mehr vorhanden.

Trotzdem finde ich es Schade, das Microsoft diesen Pfad nun zugeschüttet hat, schließlich war er ja schon “ausgetrampelt” und gehbereit. Aber vermutlich war das so eine Nischenlösung, dass sie wirklich fast niemand gebraucht hat.

Wie auch imemr … ich hatte zumindest kurzfristig etwas Spaß damit, diesen Weg zu entdecken.


Wenn sich eine triviale Erkenntnis mit Dummheit in der Interpretation paart, dann gibt es in der Regel Kollateralschäden in der Anwendung.
frei zitiert nach A. Van der Bellen
... also dann paaren wir mal eine komplexe Erkenntnis mit Klugheit in der Interpretation!