I'm trying to make an RPC call which requests 2 numbers and a string from the RPC server, the IDL looks like this:
void GetCurrentStatus([in] handle_t hBinding, [out, ref] DWORD *dwRef1, [out, ref] DWORD *dwRef2, UINT *nLength, [out, size_is(, *nLength)] LPWSTR *pszName);
In the server-side call I do this:
// name = std::wstring
*pszName = (wchar_t*)midl_user_allocate(name.length()+1 * sizeof(wchar_t));
_tcscpy(*pszName, name.c_str());
*nLength = name.length();
But any attempt to call from the client-side results in nothing returned the error The array bounds are invalid.
What is the correct way to return a string from an RPC call?
Thanks,
J
If you have a choice in the matter, use BSTR (i.e. SysAllocString). RPC knows all about this data type and how to copy it and find its length.
Just
[out, retval] BSTR* pstrName
is enough, no separate length parameter needed.
The server is not able to pass string value back to client since it doesn't know how to marshall the string..
When you use BSTR type, the server knows to the length of the string. BSTR must be preceded by a 4-byte length field and terminated by a single null 2-byte character.
Where you have written:
*nLength = name.length();
I believe you need
*nLength = (name.length() + 1) * sizeof(WCHAR);
In particular, if you have an empty (length zero) string, then returning a size_is(0) array is not legal -- so you must add space for the string-terminating NUL (L'\0').
You also want to supply the size in bytes, where each Unicode character uses two bytes -- therefore you must multiply by the character size.
Related
HRESULT UrlCanonicalize(
_In_ PCTSTR pszUrl,
_Out_ PTSTR pszCanonicalized,
_Inout_ DWORD *pcchCanonicalized,
DWORD dwFlags
);
Example:
LPCTSTR pszURL = URL.c_str();
LPSTR pszOutPut = new CHAR[ strUrl.length ];
DWORD* dwCount = new DWORD[ strUrl.length ];
hRes = UrlCanonicalize( pszURL, pszOutPut,dwCount, URL_ESCAPE_UNSAFE );
Output:
E_INVALIDARG
This API fails and returns E_INVALIDARG every time I try to call it. Please give me a working code snippet to call the UrlCanonicalize function.
If you know the C++ language, the SDK documentation for the function pretty much tells you everything that you need to know:
You pass it a C-style nul-terminated string that contains your URL.
You pass it pointer to a buffer to receive the output string.
You pass it one or more flags that customize the function's behavior.
And finally, it returns to you an HRESULT value, which is an error code. If it succeeds, that value will be S_OK. If it fails, it will be some other error code.
It works like this:
std::wstring originalURL(L"http://www.example.com/hello/cruel/../world/");
// Allocate a buffer of the appropriate length.
// It needs to be at least as long as the input string.
std::wstring canonicalURL(originalURL.length() + 1, L'\0');
DWORD length = originalURL.length() + 1;
// Call the function to modify the string.
HRESULT hr = UrlCanonicalize(originalURL.c_str(), // input string
&canonicalURL[0], // buffer
&length, // pointer to a DWORD that contains the length of the buffer
URL_UNESCAPE | URL_ESCAPE_UNSAFE);
if (SUCCEEDED(hr))
{
// The function succeeded.
// Your canonicalized URL is in the canonicalURL string.
MessageBox(nullptr, canonicalURL.c_str(), L"The URL is:", MB_OK);
}
else
{
// The function failed.
// The hr variable contains the error code.
throw std::runtime_error("The UrlCanonicalize function failed.");
}
If you want to make sure that the buffer is sufficiently long (and avoid having to handle that error), use the constant INTERNET_MAX_URL_LENGTH (declared in WinInet.h) when allocating it:
std::wstring canonicalURL(INTERNET_MAX_URL_LENGTH, L'\0');
DWORD length = INTERNET_MAX_URL_LENGTH;
The code you tried has a couple of problems:
You've incorrectly initialized the dwCount variable. The function wants a pointer, but that doesn't mean you should declare the variable as a pointer. Nor do you want an array; this is a single DWORD value. So you need to declare it as a regular DWORD, and then use the address-of operator (&) to pass the function a pointer to that variable. Right now, you're passing the function garbage, so it's failing.
You're using C-style strings, which you should avoid in C++ code. Use the C++ string class (std::wstring for Windows code), which is exception safe and manages memory for you. As you already know, the c_str() member function gives you easy access to a C-style nul-terminated string like all C APIs want. This works fine, you do not need to use raw character arrays yourself. Avoid new whenever possible.
Potentially, a third problem is that you're trying to use the C++ string type std::string instead of std::wstring. The former is an 8-bit string type and doesn't support Unicode in a Windows environment. You want std::wstring, which is a wide string with Unicode support. It's what all the Windows API functions expect if you have the UNICODE symbol defined for your project (which it is by default).
Here you go:
LPCTSTR pszURL = URL.c_str();
DWORD nOutputLength = strUrl.length * 2 + 32;
LPTSTR pszOutPut = new TCHAR[nOutputLength];
hRes = UrlCanonicalize( pszURL, pszOutPut, &nOutputLength, URL_ESCAPE_UNSAFE);
On the third parameter you provided garbage instead of pointer to initialized value, so you had API failure back. MSDN has it all for you:
A pointer to a value that, on entry, is set to the number of characters in the pszCanonicalized buffer.
I am implementing a Windows system service, which acts as RPC server, and a corresponding client, both in C++. I am using plain Windows RPC functionality.
Passing strings from the RPC client to the server is easy. Just declare the function parameter in the IDL file like this:
[in, string] wchar_t* myString
MIDL will take care of the memory-allocation magic. Works like a treat.
Returning a modified client string is also easy:
[in, out, string] wchar_t* myString
That requires me to properly size the string on the client side, though.
Problem:
I need to return strings from the server to the client. I do not know on the client how large they are going to be, so memory allocation on the client is not an option.
I could allocate a very large amount of memory, say 10K, an amount large enough for every string the server could possibly return. But that is a huge waste of resources (memory, network), and I still cannot know for certain that the server never needs to return a larger string.
What I tried:
Amonst many other things I tried the technique used in Microsoft's strout sample. It worked when calling the RPC function for the first time, but crashed the server when called for the second time.
The MSDN page Multiple Levels of Pointers brought me on the right track. With the example and explanation given there I managed to make it work. The essentail parts are as follows:
IDL file:
error_status_t ReturnsString
(
[out] long* size,
[out, size_is(, *size)] wchar_t** outString
);
Server function:
error_status_t ReturnsString (long* size, wchar_t** outString)
{
wstring outStringWString = L"Return this to caller";
int stringSize = sizeof(wchar_t) * (outStringWString.size() + 1);
*outString = (wchar_t*) midl_user_allocate (stringSize * 2);
wcscpy_s (*outString, stringSize, outStringWString.c_str());
*size = outStringWString.size() + 1;
return ERROR_SUCCESS;
}
I do not know why the multiplication by two (stringSize * 2) is necessary, but it is. If omitted, you get instant heap corruption.
Client code:
wchar_t** versionRPC = (wchar_t**) midl_user_allocate (sizeof(wchar_t*));
*versionRPC = NULL; // Required to create a unique pointer
long stringSize = 0;
DWORD retVal = ReturnsString (&stringSize, versionRPC);
// Copy the returned string
wstring stringFromServer (*rpcString);
MIDL_user_free (*rpcString);
MIDL_user_free (rpcString);
How can I convert a PSID type into a byte array that contains the byte value of the SID?
Something like:
PSID pSid;
byte sidBytes[68];//Max. length of SID in bytes is 68
if(GetAccountSid(
NULL, // default lookup logic
AccountName,// account to obtain SID
&pSid // buffer to allocate to contain resultant SID
)
{
ConvertPSIDToByteArray(pSid, sidBytes);
}
--how should I write the function ConvertPSIDToByteArray?
Use the GetLengthSid() to get the number of bytes you'll need. Then memcpy() from the PSID.
I think the function you might be looking for is ConvertSidToStringSid. The general idea is to convert the PSID struct to a LPTSTR which is in fact of type wchar_t. You can then convert this using standard functions to a multi-byte char array using wcstombs which will then give you the SID in bytes. Alternatively, you can operate on the wchar_t type directly and just write that out - there are functions for handling that. In either case, the result will be UTF-16 LE encoded and if you need to change from that you'll have to do a conversion.
I am writing a C++ DLL that is called by an external program.
1.) I take an array of strings (as char *var) as an argument from this program.
2.) I want to iterate through this array and call a COM function on each element of the string array. The COM function must take a BSTR:
DLL_EXPORT(void) runUnitModel(char *rateMaterialTypeNames) {
HRESULT hr = CoInitialize(NULL);
// Create the interface pointer.
IUnitModelPtr pIUnit(__uuidof(BlastFurnaceUnitModel));
pIUnit->initialiseUnitModel();
int i;
for(i=0; i < sizeOfPortRatesArray; i++)
pIUnit->createPort(SysAllocString(BSTR((const char *)rateMaterialTypeNames[i])));
I think its the SysAllocString(BSTR((const char *)rateMaterialTypeNames[i])) bit that is giving me problems. I get an access violation when the programs runs.
Is this the right way to access the value of the rateMaterialTypeName at i? Note I am expecting something like "IronOre" as the value at i, not a single character.
If you're using Microsofts ATL, you can use the CComBSTR class.
It will accept a char* and create a BSTR from it, also, you don't need to worry about deleting the BSTR, all that happens in the dtor for CComBSTR.
Also, see Matthew Xaviers answer, it doesn't look like you're passing your array of strings into that function properly.
Hope this helps
Because a variable holding a C string is just a pointer to the first element (a char*), in order to pass an array of C strings, the parameter to your function should be a char**:
DLL_EXPORT(void) runUnitModel(char **rateMaterialTypeNames)
This way, when you evaluate rateMaterialTypeNames[i], the result will be a char*, which is the parameter type you need to pass to SysAllocString().
Added note: you will also need to convert the strings to wide chars at some point, as Tommy Hui's answer points out.
If the parameter to the function rateMaterialTypeNames is a string, then
rateMaterialTypeNames[i]
is a character and not a string. You should use just the parameter name itself.
In addition, casts in general are bad. The conversion to a BSTR is a big flag. The parameter type for SysAllocString is
const OLECHAR*
which for 32-bit compilers is a wide character. So this will definitely fail because the actual parameter is a char*.
What the code needs is a conversion of narrow string to a wide string.
const OLECHAR* pOleChar = A2COLE( *pChar );
BSTR str = SysAllocString( pOleChar );
// do something with the 'str'
SysFreeString( str ); // need to cleanup the allocated BSTR
I have a COM API foo, the IDL looks like:
foo([in] unsigned long ulSize, [in, size_is(ulSize)] unsigned char* pData)
when I consume this function with foo(0,NULL);
I get an error - NULL argument passed. Is there a way to workaround this?
Have you tried passing an empty string?
unsigned char Data = 0;
foo(0,&Data);
Don't use char* in COM APIs -- use BSTR instead. Then pass an empty string.
foo([in] unsigned long ulSize, [in] BSTR pData)
...
foo(1, _bstr_t(""));
You should probably mark the char* as as string to get some assistance with the marshaling.
foo([in] unsigned long ulSize, [in,string,size_is(ulSize)] unsigned char* pData)
We don't use the size_is option in the IDL, perhaps it is forcing the issue of having a non NULL address?
foo([in] unsigned long ulSize, [in,string] unsigned char* pData)
I'd certainly recommend using BSTR or SAFEARRAY rather than char. The issue would then be how to handle this empty case best, possibly treating the same as an empty string, or having a separate method.
Passing pointers in COM is very bad form, like passing a pointer using shared memory the (potentially/likely) remote process will not have access to the memory. As such COM tries to help by martialling the actual data for you, but if you have hidden it behind a different data type it won't be martialling the data properly. For instance using wchar_t* it will create a system allocated string available between the processes. or you can do the same and have an interface taking a bstring and pass it the result of a sysallocstring()
Perhaps you could tell us more about the structure you want to use, it might be more appropriate to expand the com interface with objects of this type. Or there may be some other trick in martialling to transfer the data, you can write custom martialling methods to serialize and deserialize the content.
If you're passing in a BSTR you should just pass the BSTR value - they're already length counted (use SysStrLength to find the length).
If you want to pass in a null terminated string, use the [string] attribute as Greg said
But the answer to your actual question is that you need to mark the string parameter as "unique" - that lets the MIDL compiler (and the RPC runtime library) know that it's ok for that parameter to be NULL.
So use:
foo([in, string] unsigned char* pData)
You don't need the length field because it's a null terminated string - so you can use strlen on the string.
foo is probably implemented like this:
HRESULT foo(unsigned long ulSize, unsigned char* pData) {
if (!pData) {
return E_POINTER;
}
...
}
In this case the only workaround is to pass non-NULL pData.