问题
I've been trying to work out how to return an array of strings from a c++ dll to a c# application but am stuck on how to do this or find an article at a very basic level.
Suppose I have the code below. How do I fix the bolded 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
回答1:
You could use the COM Automation SAFEARRAY type, even without doing full COM (no object, no class, no interface, no TLB, no registry, etc.), just with DLL exports, as .NET supports it natively with P/Invoke, something like this:
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();
}
C#:
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();
回答2:
You can't do it directly - you need an extra level of indirection. For a C-style compatible interface you'll need to return a primitive type. Forget about using C++ DLLs from any other compiler - there is no strict C++ ABI.
So, you'd need to return a opaque pointer to an allocated string vector, e.g.
#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 implementation 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 of this you can then provide a cleaner wrapper on the C# end. Don't forget to destroy the allocated object via the DestroyStringList function, of course.
回答3:
You have two "standard" ways to get from C++ to C#.
The first is C++/CLI. In this case you will build a C++/CLI library that takes the std::vector<std::string>
and converting that into a System::vector<System::string>
. Then you can use it freely as a System.String[]
in C#.
The other is COM. There you create a COM interface that returns a SAFEARRAY
containing BSTR
string. This COM interface is then instantiated though the System.Runtime.InteropServices in C#. The SAFEARRAY
is then a Object[] which can be cased to single string objects.
The facility to load C interfaces into C# is basically restricted to C. Any C++ will fail and Pete provides that "non standard" approach. (It works very well, just not what MS wants you to do.)
来源:https://stackoverflow.com/questions/15810016/declspecdllexport-vectorstdstring