__declspec (dllexport) :: vector <std :: string>
I tried to figure out how to return an array of strings from a C ++ dll to a C # application, but I was fixated on how to do this or find an article at a very basic level.
Suppose I have the code below. How to fix the highlighted line:
extern "C" { __declspec(dllexport) int GetANumber(); //unsure on this line: **__declspec(dllexport) ::vector<std::string> ListDevices();** } extern::vector<std::string> GetStrings() { vector<string> seqs; return seqs; } extern int GetANumber() { return 27; } thanks
Matt
You can use the COM Automation SAFEARRAY type, even without full COM (without an object, without a class, without an interface, without TLB, without registries, etc.), only with a DLL export, since .NET supports it with P / Invoke , something like that:
C ++:
extern "C" __declspec(dllexport) LPSAFEARRAY ListDevices(); LPSAFEARRAY ListDevices() { std::vector<std::string> v; v.push_back("hello world 1"); v.push_back("hello world 2"); v.push_back("hello world 3"); CComSafeArray<BSTR> a(v.size()); // cool ATL helper that requires atlsafe.h std::vector<std::string>::const_iterator it; int i = 0; for (it = v.begin(); it != v.end(); ++it, ++i) { // note: you could also use std::wstring instead and avoid A2W conversion a.SetAt(i, A2BSTR_EX((*it).c_str()), FALSE); } return a.Detach(); } WITH#:
static void Main(string[] args) { foreach(string s in ListDevices()) { Console.WriteLine(s); } } [DllImport("MyUnmanaged.dll")] [return: MarshalAs(UnmanagedType.SafeArray)] private extern static string[] ListDevices(); You cannot do this directly - you need an additional level of indirection. For a C-style compatible interface, you need to return a primitive type. Forget using the C ++ DLL from any other compiler - there is no strict C ++ ABI.
So, you will need to return an opaque pointer to the selected row vector, for example.
#define MYAPI __declspec(dllexport) extern "C" { struct StringList; MYAPI StringList* CreateStringList(); MYAPI void DestroyStringList(StringList* sl); MYAPI void GetDeviceList(StringList* sl); MYAPI size_t StringList_Size(StringList* sl); MYAPI char const* StringList_Get(StringList* v, size_t index); } And the implementation is wise:
std::vector<std::string>* CastStringList(StringList* sl) { return reinterpret_cast<std::vector<std::string> *>(sl); } StringList* CreateStringList() { return reinterpret_cast<StringList*>(new std::vector<std::string>); } void DestroyStringList(StringList* sl) { delete CastStringList(sl); } void GetDeviceList(StringList* sl) { *CastStringList(sl) = GetStrings(); // or whatever } size_t StringList_Size(StringList* sl) { return CastStringList(sl)->size(); } char const* StringList_Get(StringList* v, size_t index) { return (*CastStringList(sl))[index].c_str(); } After doing all this, you can provide a cleaner shell at the end of C #. Do not forget, of course, to destroy the selected object through the DestroyStringList function.
You have two โstandardโ ways to switch from C ++ to C #.
The first is C ++ / CLI. In this case, you will create a C ++ / CLI library that will take std::vector<std::string> and convert it to System::vector<System::string> . Then you can use it freely as System.String[] in C #.
The other is COM. There you create a COM interface that returns the string SAFEARRAY containing the string BSTR . This COM interface is then created through System.Runtime.InteropServices in C #. SAFEARRAY is an Object [] that can be surrounded by single string objects.
The ability to load C-interfaces in C # is mostly limited to C. Any C ++ does not work, and Pete provides this โnon-standardโ approach. (This works very well, just not what MS wants you to do.)