Introduction
Choosing the right programming language for .NET is mostly a matter of personal preference and the problem that needs to be solved. For me, one of the most important reasons to use managed extensions for C++ (MC++) is the possibility to reuse "native" C++ code, especially C++ Standard Library.
In the October 2002 edition of C/C++ User Journal, there is an article by Jonathan Caves, that describes some issues with using C++ Standard Library with managed types. My aim is to go one step further and offer some code that will help developers to easily integrate managed types and STL. This code is contained in a single header file gcstl.h.
To use the code I provided here, you will need working knowledge of Managed Extensions for C++ and STL. That assumes that, at least you have read Managed Extensions for C++ Specification and some introductionary STL text. To really understand what is going on under the hood, you will need to be familiar with STL internals, and to have a good understanding of CLR and .NET Framework.
What is the problem?
To illustrate the problems with working with managed types and STL, try to compile the next piece of code:
#using <mscorlib.dll>
#include <vector>
#include <algorithm>
#include <iterator>
#include <iostream>
using namespace System;
using namespace std;
int main()
{
vector <String __gc*> v;
v.push_back(S"bca");
v.push_back(S"bac");
v.push_back(S"abc");
sort (v.begin(), v.end());
copy (v.begin(), v.end(), ostream_iterator<String __gc*>(wcout, L" "));
}
Also, it would be nice if we could use STL algorithms with .NET collections. Something like:
String __gc* st_array[] = {S"bca", S"bac", S"abc"};
std::find(st_array[0], st_array[3], S"abc");
The code above has following problems:
- Unmanaged classes cannot contain
__gc pointers.
- Built in comparison operators can not be used to sort managed objects.
- C++ output streams don't work with managed types, and therefore we can not use
ostream_iterator.
- Standard algorithms don't work with .NET collections.
Let's discuss each of those problems separately.
Problem 1: Storing __gc Pointers in STL Containers
As explained in Managed Extensions for C++ Specification, chapter 16.3 "It is illegal to declare a member of an unmanaged class to have __gc pointer type.". However, there is the class System::Runtime::InteropServices::GCHandle that enables to reference a managed object from unmanaged heap. Even better, good people from VC.NET team have made a wrapper template gcroot to make working with GCHandle easy. Just include vcclr.h and use it like this:
#using <mscorlib.dll>
#include <vector>
#include <algorithm>
#include <iterator>
#include <iostream>
#include <vcclr.h>
using namespace System;
using namespace std;
int main()
{
vector <gcroot<String __gc*> > v;
v.push_back(S"bca");
v.push_back(S"bac");
v.push_back(S"abc");
}
To find out about internals of gcroot, take a look at the header file gcroot.h.
Problem 2: Comparisons of Managed Objects
To work with STL algorithms, as well as with sorted containers, you must be able to compare the values of your objects. STL heavily relies on value semantics, and it is generally recommended to avoid pointers (__gc as well as __nogc) in general, but it is possible to work easily with managed objects by using some function objects, that I provide in the header file gcstl.h:
#using <mscorlib.dll>
#include <vector>
#include <algorithm>
#include <iterator>
#include <iostream>
#include "gcstl.h"
using namespace System;
using namespace std;
int main()
{
vector <gcroot<String __gc*> > v;
v.push_back(S"bca");
v.push_back(S"bac");
v.push_back(S"abc");
sort (v.begin(), v.end(), gc_less<String __gc*>());
}
Note that, we now use the version of sort, which uses a user-defined predicate function object that defines the comparison criterion. Most STL algorithms have versions that enable users to provide comparison predicates.
In the header file gcstl.h, I define comparison function objects that work with __gc pointers and correspond to the ones defined in the standard header <functional>. Be sure to use those function objects with algorithms, as well as with sorted containers like set and map:
set<gcroot<String __gc*>, gc_less<String __gc*>()> my_set;
rather than:
set<gcroot<String __gc*> > my_set;
Actually, both versions will compile, but in the later case ordering will be meaningless.
Function objects defined in gcstl.h are listed in the following table:
| std version |
gc version |
Implemented in terms of |
equal_to |
gc_equal_to |
System::Object::Equals |
not_equal_to |
gc_not_equal_to |
System::Object::Equals |
less |
gc_less |
System::IComparable::CompareTo |
greater |
gc_greater |
System::IComparable::CompareTo |
less_equal |
gc_less_equal |
System::IComparable::CompareTo |
greater_equal |
gc_greater_equal |
System::IComparable::CompareTo |
From the table, you can see that for a managed type to work with less, greater, less_equal and greater_equal, it must implement IComparable interface.
Problem 3: Replacement for ostream_iterator
I often use ostring_stream to dump the content of my STL containers either to screen or to a file. Therefore, I thought it would be nice to have an output iterator with similar semantics, that will internally work with .NET System::IO::TextWriter. So I made textwriter_iterator. Here it is:
#include <vector>
#include <algorithm>
#include <iterator>
#include "gcstl.h"
using namespace System;
using namespace std;
int main()
{
vector <gcroot<String __gc*> > v;
v.push_back(S"bca");
v.push_back(S"bac");
v.push_back(S"abc");
sort (v.begin(), v.end(), gc_less<String __gc*>());
copy (v.begin(), v.end(), textwriter_iterator<String __gc*>
(Console::Out, S" "));
}
Internally, textwriter_iterator uses Object::ToString() to write an object to a text writer.
Notice that I didn't provide a managed counterpart for istream_operator. The reason for that is that, in my C++ programming I have not find this operator that useful, and since the implementation would not be trivial, I decided to skip it.
Problem 4: Using STL algorithms with .NET collections
To make STL algorithms work with .NET collections, I created a template wrapper gc_collection. Here is an example how to work with this wrapper:
#include <algorithm>
#include "gcstl.h"
using namespace System;
using namespace std;
int main()
{
String __gc* st_array[] = {S"bca", S"bac", S"abc"};
gc_collection<Array __gc*, String __gc*> cont(st_array);
gc_collection<Array __gc*, String __gc*>::const_iterator it =
find_if(cont.begin(), cont.end(),
bind2nd(gc_equal_to<gcroot<String __gc*> >(),S"abc"));
if (it != cont.end())
Console::WriteLine(*it);
}
gc_collection has two template parameters: Container (by default it is System::Collections::ICollection __gc*) - the type of the wrapped container; Elem (by default System::Object __gc*) - the type of contained elements.
I designed gc_collection to cover a broad range of .NET collections, rather than to have many features. Therefore, it was implemented in terms of ICollection interface, and not IList or IDictionary. I plan to make wrappers for these interfaces in the next release of gcstl.h.
gc_collection has the following member functions:
gc_collection (Container container) |
Constructor |
const_iterator begin() |
The first element |
const_iterator end() |
Past-the-end element |
int size() const |
Number of the elements |
bool empty() const |
Is the collection empty? |
operator Container() const |
Conversion to the wrapped container type |
Container operator->() const |
Enables to use gc_collection as a smart pointer |
gc_collection::const_iterator is not even a forward immutable iterator - it lacks post-increment operator. It is implemented in terms of IEnumerator interface.
Minimalist as is, gc_collection gives us possibility to work with most non-mutating STL algorithms.
References
- Managed Extensions for C++ Specification
- Jonathan Caves: Using the C++ Standard Library with Managed Types - C/C++ Users Journal, October 2002.
| You must Sign In to use this message board. |
|
|
 |
|
 |
The SIMPLE answer to using STL is: use MC++ containers.
I use mosly vector and map (both single and multi-map). Is there enough efficiency gain in STL to warrent the pain.
(I have been strongly advised to learn a LOT more about templates. That V7 does a much better job with templates. And that I should look into using templates more than just the few STL's I currently know and love. But... is it worth the pain!?)
WedgeSoft
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
 |
If you are using Managed C++, STL will probably add some overhead comparing to .NET collections. As I already noted, the advantage of STL is not performance in this case, but rather type safety and use of STL algorithms.
My programming blahblahblah blog. If you ever find anything useful here, please let me know to remove it.
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
 |
Well sometimes I use std::set which allows me enumerating some informations, which .NET doesn't support to do it as I know so far. Anybody been use union or intersection ?
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
 |
I am working through a Managed C++ book, doing it's exercies. And tried to us STL vector today for the first time. (And on failure, hunted up this article.)
I can get your code to work in a console app. I can get it to work if a local vector in my Form if in the cpp file. But -- it will not compile if I define your vector in the Form.h. Used your provided includes, your gcstl.h code and the following declarations:
tried vector < gcroot<String __gc*> > stringVec; // failed
tried typedef vector < gcroot<String __gc*> > stringVecType;
with just the typedef above, compiled; but when added the following failed again.
stringVecType stringVec;
Error is long, so have not included it all; but here is some of it:
Form1.cpp k:\V7Net\NetLearning\ManagedCPP_Ch11\MaintainSTLCoords\Form1.h(64) : error C3633: cannot define 'stringVec' as a member of managed 'MaintainSTLCoords::Form1' because of the presence of default constructor 'std::vector<_Ty>::__ctor' on class 'std::vector<_Ty>' with [ _Ty=gcroot<System::String __gc *> ] and [ _Ty=gcroot<System::String __gc *> ] and [ _Ty=gcroot<System::String __gc *> ] C:\Program Files\Microsoft Visual Studio .NET 2003\Vc7\include\vector(297) : see declaration of 'std::vector<_Ty>::__ctor' with.....
Hope you can offer me some help. (I use map a lot too -- read the earlier posts on that; but need to get by this before I can move on to maps.)
WedgeSoft
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
 |
Not sure what you are really doing, but it seems that you are trying to declare a nogc object (vector of strings) inside of a gc class (Form). You just can't do that. Be sure to read the MC++ specification before diving deeper into programming with Managed Extensions for C++.
My programming blahblahblah blog. If you ever find anything useful here, please let me know to remove it.
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
 |
Thanx very much for this article. It has answered a lot of my questions.
got a 5 from me
If there's one thing I've learned, it's that life is one crushing defeat after another until you just wish Flanders was dead. - Homer Simpson
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
 |
|
 |
I read your article. It is indeed very helpful. I am new to MC++ and struglling with the following issue:
In our project we make use of third pirty DLLs which are written in VC++. We call this DLL from one MC++ client app. We are having issue while passing std::vector objects from MC++ to thie VC++ DLL where as the reference type vaibles are working. In short: API that does work: Method(std::vector& strArr)
API that does not work: Method(std::vector strArr)
It asserts with "_CrtIsValidHeapPointer". Please help..
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
 |
In order to be able to pass C++ objects accross DLL boundaries, you need to link CRT dynamicaly with both the exe and the dll (look at Code Generation -> Runtime Library in your project properties)
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
 |
thanks for your reply. In fact in both DLL (unmanged) and the exe (managed) the C/C++ Code generation->Runtime Library setting is pointed to "multithreaded debug DLL". Still we are not able to get the vector in the unmanaged DLL.
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
 |
This is strange. Would you post your API signatures again, but be sure to check "Do not treat <'s as HTML tags" option?
|
| Sign In·View Thread·PermaLink | 1.00/5 |
|
|
|
 |
|
 |
The DLL function(unmanged VC6.0): int fnDivaSim(std::vector<std::string> ;
And the Exe (Manged C++ .NET1.1): [DllImport("divaSim.dll",EntryPoint="#3")] std::vector<std::string> arcVector; arcVector.push_back(string("myName")); arcVector.push_back(string("yourName")); int i = fnDivaSim(divaArcVector);
Note: In both EXE and the DLL C/C++ Code generation is set to: 'Multithreaded Debug DLL'.
The vector that is coming to the unmanaged DLL is garbage.
regds, --Sabir
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
 |
Sabir wrote: The DLL function(unmanged VC6.0):
This is a problem. STL in VC6 is different than in VC7.x, which means that vector<string> in VC6 is not the same thing as vector<string> in VC7. I ran into this problem when I tried to pass wstring between a VC7 exe and VC6 dll. What I ended up doing was writing a C wrapper in VC6 that would convert wstrings to wchar_t*s and back and it worked fine.
To make sure this is the problem, try to call fnDivaSim from an unmanaged VC 7.1 exe. You should see the same thing.
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
 |
You were actually spot on. I created one Win32 Console app in VC7.0 and copied the exe code. And guess what the same crash ( I used to get from MC++ app). Same code is working in VC6.0 console app. This is scary. In fact I have to follow your path and have to write a VC6.0 wrapper DLL.
thanks a lot for your support.
--sabir.
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
 |
The DLL function(unmanged VC6.0): int fnDivaSim(std::vector<std::string> ;
And the Exe (Manged C++ .NET1.1): [DllImport("divaSim.dll",EntryPoint="#3")] std::vector<std::string> arcVector; arcVector.push_back(string("myName")); arcVector.push_back(string("yourName")); int i = fnDivaSim(divaArcVector);
Note: In both EXE and the DLL C/C++ Code generation is set to: 'Multithreaded Debug DLL'.
The vector that is coming to the unmanaged DLL is garbage.
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
 |
Does anyone know how to the copy the contents of a managed string to an unmanaged wide character array? The following does not compile.
wchar_t szBuff; String *s = S"test"; wcscpy (szBuff,s->ToCharArray());
compiler gives error - can not convert parameter 2 from wchar_t __gc[] to wchar_t *
Any Ideas would be appreciated.
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
 |
|
 |
Sir
Your article showing how to use vectors was a big help to me, a newcomer to .Net. However, I also need to use STL maps and am having BIG problems just getting a compile. I can declare a map OK as:
map, gcroot >myMap;
or, to sort:
map, gcroot, less< gcroot > >myMap;
where gcObj is a __gc object.
The above compiles without error. However when I attempt to insert into the map I get compiler errors. I've tried the following:
myMap.insert(pair, gcroot >(myStr, myObj)); and myMap.insert(pair, gcroot >(myStr, myObj)); and myMap.insert(pair, gcroot >(myStr, myObj)); and simply myMap.insert(pair(myStr, myAb)); //Trying a long shot!
All of the above produce compiler errors. Assuming that STL maps do work with managed code, what the heck is the correct syntax to make it happen?
FYI, I'm not real heavy into STL, but I have used vectors and maps with MFC code with great success and would like to be able to do the same with .Net. Vectors work fine, but maps.....
I'd appreciate any help
John Bollwerk 
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
 |
Thanks for reading my article.
This snippet of code may help you. I use System::String as an example of a managed type, and std::string as an example of unmanaged type. Header gcstl.h can be downloaded from the link at the beginning of this page.
#using <mscorlib.dll> #include "gcstl.h" #include <map> using namespace std; using namespace System;
int _tmain() { map <gcroot<String __gc*>, string, gc_less<String __gc*> > myMap; myMap.insert(make_pair(gcroot<String __gc*>(S"first"), "second")); return 0; }
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
 |
THANK YOU! Compiles OK. Now, I seem to not understand how to implement and get the iterator to work. I'm declaring the iterator as:
map, gcroot >::const_iterator it;
What I've been doing to iterate through unmanaged maps is:
it = myMap.begin(); while (it != myMap.end()) { . . it++; }
Unfortunately, this does not work with managed maps. I looked at the gcstl.h file but I can't figure out the right way to iterate. Hate to be a pest, but I need help again.
Thanks
John
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
 |
I just noticed that my iterator declaration did not come through correctly in the previous message. I declare it as:
map, gcroot >::const_iterator it;
When I write:
it = myMap.begin();
I get:
Form1.cpp(318) : error C2679: binary '=' : no operator found which takes a right-hand operand of type 'std::_Tree<_Traits>::iterator' (or there is no acceptable conversion)
John
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
 |
Your code is not very readable (please check "Do not treat <'s as HTML tags" when you post templated code), but this compiles just fine:
typedef map <gcroot<String __gc*>, string, gc_less<String __gc*> > MapType; MapType myMap; myMap.insert(make_pair(gcroot<String __gc*>(S"first"), "second")); MapType::const_iterator it = myMap.begin();
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
 |
Sir
Again I thank you. I finally figured out what the iterator problem was, a dumb oversite on my part, I was leaving out the "sort" criteria in the iterator declaration. DUH! I guess sometimes I miss the trees when looking at the forest...
Anyway, the map now works (compiles OK, but haven't yet checked the processing), using managed types as both a "first" and a "second" and sorting on the "first" by using your gcstl.h. Hand salute!
Also appreciate the tip about checking for "<" as HTML tags.
John (Who thinks he likes .Net better than MFC, but it's too soon to be positive!)
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
 |
Things seemed to be going smoothly until I attempted to access an object stored in a multimap using "find". Here's what is happening:
The multimap and iterator declaration: multimap<double, gcroot<CGeo __gc*>, less < double > > mapLat; multimap<double, gcroot<CGeo __gc*>, less < double > >::iterator latIt;
where CGeo is a public __gc class
I load the map with: mapLat.insert(make_pair(double(lat), gcroot<CGeo __gc*>(geo)));
mapLat.size() returnes 6268 entries.
The value -8.702569 is one of the keys (first)
If I do the following: double search = -8.702569; latIt = mapLat.find(search);
and try: double myFirst = latIt->first; or double mySeconf = latIt->second->lat;
I get an unhandled exception even though I'm in a try...catch block 
However, I can set latIt = mapLat.begin(); and iterate through the table OK, eventually locating the key and its second.
It seems that "find" is not returning a good iterator but I don't understand enough to figure out why. Hopefully you will be good enough to help again.
Thanks
John
|
| Sign In·View Thread·PermaLink | 5.00/5 |
|
|
|
 |
|
 |
Hmmm, this kinda works for me (I use String for your CGeo):
typedef System::String CGeo;
int _tmain() { multimap<double, gcroot<CGeo __gc*>, less < double > > mapLat; multimap<double, gcroot<CGeo __gc*>, less < double > >::iterator latIt;
double lat = -8.702569; mapLat.insert(make_pair(double(lat), gcroot<CGeo __gc*>(S"geo")));
double search = -8.702569; latIt = mapLat.find(search);
double myFirst = latIt->first;
return 0; }
Do you do anything different?
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|