Returning multiple lines from dll
we are discussing how a good way to return multiple lines from a single DLL function. We currently have 8 lines, but there will be more. For the sake of simplicity, now consider that all strings will be of equal length.
extern "C" int DLLNAME_ _stdcall GetResult(TestResults* testResults);
Where
struct TestResults
{
int stringLengths;
char* string1;
char* string2;
char* string3;
char* string4;
...
};
or second option: where
struct TestResults
{
int stringLengths;
char string1[64];
char string2[64];
char string3[64];
char string4[64];
...
};
third option: extern "C" int DLLNAME_ _stdcall GetResult (int stringLengths, char * string1, char * string2, char * string3, ...);
The DLL will communicate over a serial line and receive information that will be filled into lines. Where memory needs to be allocated is open to discussion and can be part of the answer.
We are assuming we have a VB6 development team that prefers the second method and a C ++ / C # team that prefers the first method. The last method works for both commands, but looks a little odd to me with so many parameters.
There are probably more options. What is common practice on Windows? Any examples from the Windows API or arguments for choosing one of these?
Edit: strings matter, just like with first name, last name, email. We currently have eight, but in the future we could add a pair, for example, for an address. An array would not have been the right choice for this, but it was not clear from the original context.
source to share
The best way is probably to use a secureBSTR
string storage array .
Both VB and C # understand safe arrays very well: in C #, a safe array of strings is BSTR
automatically converted to an array string[]
.
On the C ++ side, you can use a helper class ATL::CComSafeArray
to make programming a safe array easier.
You will find interesting material in this MSDN article (in particular, see the section on Creating a Safe Array of Strings).
From the above article: On the C ++ side, you can implement the C interface DLL by exporting a function like this:
extern "C" HRESULT MyDllGetStrings(/* [out] */ SAFEARRAY** ppsa)
{
try {
// Create a SAFEARRAY containing 'count' BSTR strings
CComSafeArray<BSTR> sa(count);
for (LONG i = 0; i < count; i++) {
// Use ATL::CComBSTR to safely wrap BSTR strings in C++
CComBSTR bstr = /* your string, may build from std::wstring or CString */ ;
// Move the the BSTR string into the safe array
HRESULT hr = sa.SetAt(i, bstr.Detach(), FALSE);
if (FAILED(hr)) {
// Error...
return hr;
}
}
// Return ("move") the safe array to the caller
// as an output parameter (SAFEARRAY **ppsa)
*ppsa = sa.Detach();
} catch (const CAtlException& e) {
// Convert ATL exceptions to HRESULTs
return e;
}
// All right
return S_OK;
}
On the C # side, you can use this PInvoke declaration:
[DllImport("MyDll.dll", PreserveSig = false)]
public static extern void MyDllGetStrings(
[Out, MarshalAs(UnmanagedType.SafeArray, SafeArraySubType = VarEnum.VT_BSTR)]
out string[] result);
source to share
When you declare your function as extern "C"
, I suppose you cannot use std::vector<std::string>
as a return type.
Another possibility:
struct String
{
int size; /* size of string */
const char* str; /* actual string */
}
struct TestResults
{
int size; /* number of strings */
String* arr; /* pointer to an array of String */
};
and then just like before:
extern "C" int DLLNAME_ _stdcall GetResult(TestResults* testResults);
With this, you can flexibly return as many rows as you want. Plus, looping through yours TestResults
is simple.
Edit # 1: As said in the comments: use BSTR . So your structure will look like this:
struct TestResults
{
int size; /* number of strings */
BSTR* arr; /* pointer to an array of BSTR */
};
A BSTR
is allocated : BSTR MyBstr = SysAllocString(L"I am a happy BSTR");
. This allocation also sets the member that contains the length of the string. You must release the allocated memory by using: SysFreeString(MyBstr);
. Also you need to allocate the whole array BSTR*
.
source to share
I get the idea to work around the problem:
concatenate all string arrays together, split each string with a character (like "\") and of course convert the original character found in the string (replace all "\" with "\\").
The managed code then splits the resulting string into arrays character by character (one "\") and replaces the entire converted character with the original character ("\\" to "\").
I find this eaiser method to use in cross-language code like python.
source to share