C++ Vectors are sequence containers representing arrays of certain data type, which can change in size. It is widely used by lots of C++ application, if we did not include in our C# wrapper library for C++ DLL, it would be very difficult for the C# wrapper generator to create wrapper classes for C++ class automatically and smoothly since lots of classes may contain interfaces using std::vector of any data type, it is so common that we just would not be able to ignore it. A template class is not a concrete class until it gets instantiated, so a vector template class is really lots of classes with different types contained in that class, in order to map all kinds of vector class of different data type, the C# wrapper library for C++ run-time contains a bunch of wrapper class for each of the instantiated template class, see the list below. All the wrapper classes were generated by the C# wrapper generator with fine tuning specifically done to the vector type of template class.
StdStringVector (std::vector<std::string>) StdWStringVector (std::vector<std::wstring>) StdCharVector (std::vector<char>) StdWCharVector (std::vector<wchar_t>) StdShortVector (std::vector<short>) StdIntVector (std::vector<int>) StdLongVector (std::vector<long>) StdDoubleVector (std::vector<double>) StdFloatVector (std::vector<float>)
Not seeing the vector of the data type you want? no worry, the C# wrapper generator has the capability of generating any type of template classes of any types as long as they can be instantiated from C++ using that C++ DLL, for std::vector, you can have std:vector<any_of_your_type>, including your own class and structure. It actually can automatically discover the template class inside the C++ DLL interface, instantiate that type of template class and create the wrapper class for that instantiated C++ template class. It is quite powerful, we will be discussing that in later blogs. For developer’s convenience, we added the pre-instantiated template class in the C# wrapper library for C++ run-time, which makes it useful even when using C DLL.
Since most of the functionality of all of the wrapper classes, the only difference is that each wrapper class contain different type of data type. We will be discussing the wrapper class of std::vector<std::string>, StdStringVector.
std::vector<std::string> is commonly used to pass or retrieve an array of C++ strings, without the help of the C# wrapper library for C++ run-time, it would be very complex for developers to marshal this data type from C++ from C#. Any other kinds of solution targeting marshaling the data type would be time-consuming, or just wasting developers’ valuable time.
Developers asked questions on stackoverflow, I listed a few of them below,
How to Get array of string from Native Code(C++) in Managed Code(C#)
How to marshal collection in c# to pass to native (C++) code
C# pinvoke marshalling structure containg vector<structure>
Without a wrapper class for std::vector<std::string>, such as StdStringVector, there are really no simple answer for those questions, developers would have to do a lot of work to marshal this kind of instantiated template class, besides this, std::string still needs to be marshaled before std::vector<std::string> can be marshaled.
Let’s look into the details of the class of StdStringVector, the .NET wrapper class for std::vector<std::string>. The followings are the metadata I copied and pasted from VS IDE.
using System; using System.Collections; using System.Collections.Generic; using System.Reflection; using System.Runtime.InteropServices; namespace NSpeech.Win32.StdLib { public class StdStringVector : StdLibExpressBaseClass, IEnumerable<string>, IEnumerable { public StdStringVector(); public StdStringVector(IntPtr handle = null); public StdStringVector(long count); public StdStringVector(StdStringVector that); public StdStringVector(StdStringVectorStructX86 objectStruct); public StdStringVector(string[] source); public StdStringVector(long count, StdString val); public StdStringVector(IntPtr thisObject, IntPtr thatObject, bool initialize); public StdStringVector(long count, StdString val, IntPtr al); public static implicit operator StdStringVectorStructX86(StdStringVector o); public static implicit operator string[](StdStringVector o); public static implicit operator StdStringVectorStructX64(StdStringVector o); protected override int StorageSize { get; } public string TemplateClassName { get; } public StdString this[long index] { get; set; } public StdStringVector Assign(StdStringVector thatObject); public void assign(long count, StdString val); public void assign(StdStringVectorConstIterator first, StdStringVectorConstIterator last); public void assign(StdStringVectorConstReverseIterator first, StdStringVectorConstReverseIterator last); public StdString at(long pos); public StdString back(); public StdStringVectorConstIterator begin(); public long capacity(); public StdStringVectorConstIterator cbegin(); public StdStringVectorConstIterator cend(); protected override void CleanupNativeResource(); public void clear(); public StdStringVectorConstReverseIterator crbegin(); public StdStringVectorConstReverseIterator crend(); public StdString data(); public void emplace_back(StdString val); public bool empty(); public StdStringVectorConstIterator end(); public StdStringVectorIterator erase(StdStringVectorConstIterator position); public StdStringVectorIterator erase(StdStringVectorConstIterator first, StdStringVectorConstIterator last); public StdString front(); public IEnumerator<string> GetEnumerator(); public StdStringVectorIterator insert(StdStringVectorConstIterator where, StdString val); public void insert(StdStringVectorConstIterator where, long count, StdString val); public void insert(StdStringVectorConstIterator position, StdStringVectorConstIterator first, StdStringVectorConstIterator last); public void insert(StdStringVectorConstIterator position, StdStringVectorConstReverseIterator first, StdStringVectorConstReverseIterator last); public long max_size(); protected IntPtr operator_get_set(long pos); public void pop_back(); public void push_back(StdString val); public StdStringVectorConstReverseIterator rbegin(); public StdStringVectorConstReverseIterator rend(); public void reserve(long count); public void resize(long newsize); public void resize(long newsize, StdString val); public void shrink_to_fit(); public long size(); public void swap(StdStringVector right); public string[] ToArray(); public class Marshaller : ICustomMarshaler { public Marshaller(); public void CleanUpManagedData(object ManagedObj); public void CleanUpNativeData(IntPtr pNativeData); public static ICustomMarshaler GetInstance(string pstrCookie); public int GetNativeDataSize(); public IntPtr MarshalManagedToNative(object ManagedObj); public object MarshalNativeToManaged(IntPtr pNativeData); } } }
StdStringVector wraps all the public methods of the C++ counterpart, std::vector<std::string>, so you can find all the methods familiar in C++ world, like push_back, pop_back, size, etc, to make it easier for developers to use in .NET, the names of all the methods are not kept without change.
How do developers to marshal std::vector<std::string> from C# to C++? let’s take an earlier example of the wrapper class of StdString, we would need to change a little bit of it.
void __stdcall SetVector(std::vector<std::string>& myVector) { // ... } // or using a pointer void __stdcall SetVector(std::vector<std::string>* myVector) { // ... }
Again, let’s write a P/Invoke for it, remember, every C# wrapper class can be converted to a .NET IntPtr, and they also have a public marshaller class for each of them.
// std::vector<std::string>& can be marshalled as IntPtr since StdStringVector has conversion operator defined. [DllImport("Sample.dll", CallingConvention=CallingConvention.StdCall)] public extern static void SetVector(IntPtr myVector); // You can also define the P/Invoke alternatively as following since // StdStringVector class has a custom marshaller. [DllImport("Sample.dll", CallingConvention=CallingConvention.StdCall)] public extern static void SetVector([MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef=typeof(StdStringVector.Marshaller))] StdStringVector myVector);
Okay, now we have figured out the way to pass the object from C# to C++ via P/Invoke, let’s talk about the ways to access the data inside of the C++ vector instance.
There are two different ways to access StdStringVector class hence the C++ vector depending how developers would want to. Let’s give an example of how to iterate the vector and print them out to the console.
If developers prefer the way they usually do in C++ as a C++ developer, they can use iterator as they usually do in C++, It also allow them to convert any part of C++ code to C# more easily.
// vector be the C++ object retrieved from a C/C++ method call, it is not // necessary that it gets created from C#. StdStringVector vector = new StdStringVector(); vector.push_back("String #1"); vector.push_back("String #2"); vector.push_back("String #3"); vector.push_back("String #4"); vector.push_back("String #5"); vector.push_back("String #6"); for (var iter = vector.begin(); iter < vector.end(); iter++) { Console.WriteLine(iter.current()); }
If you look carefully, you would find there are other interfaces and additional methods implemented in the C# version of std::vector<std::string>, those interfaces and methods allow the developer access StdStringVector without using an iterator which is really not a native way to access an array in .NET world. You may have already noticed that it implements IEnumerable interface, ToArray method and string[] type conversion, etc.
// Lets create an array of string. List<string> list = new List<string>(); list.Add("String #1"); list.Add("String #2"); list.Add("String #3"); list.Add("String #4"); list.Add("String #5"); list.Add("String #6"); StdStringVector vector = new StdStringVector(list.ToArray()); // Now the vector can be passed to C/C++ DLL. SetVector(vector);
Quite simple, right? Developers do not have to do anything else, they would just have the capabilities of manipulating the C++ vector object when using the C# wrapper library for C++ run-time.