NO

Author Topic: Treat a BSTR like a WCHAR  (Read 12388 times)

czerny

  • Guest
Treat a BSTR like a WCHAR
« on: March 28, 2013, 10:00:17 PM »
The following is running without errors. But if one uncomment the marked line, the program crashes after some output lines.
I starr on it for long time, but ...
Code: [Select]
#define UNICODE
#include <stdio.h>
#include <windows.h>
#include <wchar.h>
#include <comcat.h>

#pragma comment(lib, "user32.lib")
#pragma comment(lib, "ole32.lib")
#pragma comment(lib, "oleaut32.lib")
#pragma comment(lib, "uuid.lib")


int wmain(void)
{
    CoInitializeEx(NULL, COINIT_MULTITHREADED);
    ICatInformation* catInfo = NULL;
    HRESULT hr = CoCreateInstance(&CLSID_StdComponentCategoriesMgr,
        NULL, CLSCTX_INPROC_SERVER, &IID_ICatInformation, (void*)&catInfo);

    IEnumGUID* enumGuid = NULL;
    CATID catidImpl[1];
CATID catidReqd[1];
catidImpl[0] = catidReqd[0] = CATID_Programmable;
    catInfo->lpVtbl->EnumClassesOfCategories(catInfo, 1, catidImpl, 1, catidReqd, &enumGuid);

    CLSID clsid;
    while((hr = enumGuid->lpVtbl->Next(enumGuid, 1, &clsid, NULL)) == S_OK)
    {
        BSTR name;
        OleRegGetUserType(&clsid, USERCLASSTYPE_FULL, &name);
        //if (name[0]==L'A') //<<<<<< uncomment
wprintf(L"%ls\n", name);
        SysFreeString(name);
    }

    enumGuid->lpVtbl->Release(enumGuid);
    catInfo->lpVtbl->Release(catInfo);
    CoUninitialize();
    return 0;
}

Offline Bitbeisser

  • Global Moderator
  • Member
  • *****
  • Posts: 772
Re: Treat a BSTR like a WCHAR
« Reply #1 on: March 28, 2013, 10:20:22 PM »
Well, haven't dealt with this too much in C yet, but for all I know, BSTR is the Visual BASIC style string, which, just as in Pascal, is preceded by the length of the string and is not NULL terminated like a C string. Not 100% sure right now of the size of the length, but given that the maximum of a Visual BASIC string is 2G of 16 bit Unicode characters, I suspect it is a 32bit value.
So when you are doing a compare with the name[0], it seems you are not actually comparing the first character of the string but with (part of) the length value of that string...

A basic reference in dealing with that can be found at http://www.codeproject.com/Articles/4829/Guide-to-BSTR-and-C-String-Conversions , though this is referencing Visual C++ 6...

Ralf

Offline TimoVJL

  • Global Moderator
  • Member
  • *****
  • Posts: 2115
Re: Treat a BSTR like a WCHAR
« Reply #2 on: March 28, 2013, 10:31:07 PM »
« Last Edit: March 28, 2013, 11:01:27 PM by timovjl »
May the source be with you

sapero

  • Guest
Re: Treat a BSTR like a WCHAR
« Reply #3 on: March 28, 2013, 10:59:42 PM »
Czerny, OleRegGetUserType may fail for several reasons, you have to check its return value. On my machine it fails 8 times.
I personally would use:
Code: [Select]
LPOLESTR name;
if (SUCCEEDED(OleRegGetUserType(&clsid, USERCLASSTYPE_FULL, &name)))
{
if (name[0]==L'A')
wprintf(L"%s\n", name);
CoTaskMemFree(name);
}
Notice CoTaskMemFree instead SysFreeString, and LPOLESTR instead BSTR - always read the docs!

Offline jj2007

  • Member
  • *
  • Posts: 536
Re: Treat a BSTR like a WCHAR
« Reply #4 on: March 28, 2013, 11:52:10 PM »
So when you are doing a compare with the name[0], it seems you are not actually comparing the first character of the string but with (part of) the length value of that string...

The comparison works OK as long as 'name' does actually contain a valid pointer.

        OleRegGetUserType(&clsid, USERCLASSTYPE_FULL, &name);
        if (name) {
                if (name[0]==L'A') {
                        ctjj++;
                        wprintf(L"%ls\n", name);
                }
                else {
                        printf("%s\n", "undefined");  //FREQUENTLY SEEN
                }
        }
        SysFreeString(name);

czerny

  • Guest
Re: Treat a BSTR like a WCHAR
« Reply #5 on: March 29, 2013, 02:25:09 AM »
Thank you!

Offline jj2007

  • Member
  • *
  • Posts: 536
Re: Treat a BSTR like a WCHAR
« Reply #6 on: March 29, 2013, 09:05:54 AM »
I was a bit puzzled that there was no valid length in the DWORD before the string pointer, so I investigated and voilĂ , it turns out that this is actually not a BSTR but just an ordinary zero-terminated Unicode string aka OLECHAR, see MSDN: _Out_  LPOLESTR *pszUserType

As a rule,
- OLE is happy with an ordinary WCHAR (no prepended len) while
- COM requires a BSTR typically allocated with SysAllocString and freed with SysFreeString.

Which probably implies that the SysFreeString(name); does not do what the name says; under "community additions" at the bottom of the MSDN linked above, user adeyblue suggests
Use CoTaskMemFree to free the string memory

Taking this into consideration, the code should look like this:
Code: [Select]
    while((hr = enumGuid->lpVtbl->Next(enumGuid, 1, &clsid, NULL)) == S_OK)
    {
wchar_t *name;
OleRegGetUserType(&clsid, USERCLASSTYPE_FULL, &name);
if (name && name[0]==L'A') {
wprintf(L"%ls\n", name);
}
CoTaskMemFree(name);
    }

MSDN says CoTaskMemFree does not return a value. This is bulls**t - it does return 1 for success, and 0 for failure (ERROR_INVALID_PARAMETER, 0x57). You can test that with
Code: [Select]
__asm int 3;
wchar_t *badname;
CoTaskMemFree(badname);

On a side note, OleRegGetUserType does not complain about being fed a BSTR, and SysFreeString accepts the wchar_t (and for some reason, WCHAR_T is unknown to PellesC). So the type checking could be improved.

P.S.: The Complete Guide to C++ Strings looks good - see in particular the "Bob" example under "Strings in COM - BSTR and VARIANT".
« Last Edit: March 29, 2013, 09:43:09 AM by jj2007 »

Offline frankie

  • Global Moderator
  • Member
  • *****
  • Posts: 2113
Re: Treat a BSTR like a WCHAR
« Reply #7 on: March 29, 2013, 10:49:20 AM »
The function OleRegGetUserType is defined as:
Code: [Select]
HRESULT OleRegGetUserType(
  _In_   REFCLSID clsid,
  _In_   DWORD dwFormOfType,
  _Out_  LPOLESTR *pszUserType
);
In http://msdn.microsoft.com/en-us/library/windows/desktop/ms682271(v=vs.85).aspx  and returns a pointer to OLESTR not BSTR. That's why you can't see length.
On the other hand BSTR is really a structure and not a simple variable (see http://msdn.microsoft.com/en-us/library/windows/desktop/ms221069(v=vs.85).aspx) as the following snippet tests:
Code: [Select]
typedef struct
{
INT32 len;
WCHAR str[];
} MYBSTR, *PMYBSTR;

PMYBSTR bstr = (LPVOID)((LPBYTE)((LPVOID)SysAllocString(L"TEST")) - (4*sizeof(BYTE)));
wprintf(L"String len %d, string \"%ls\"\n", bstr->len, bstr->str);

CoTaskMemFree is a wrapper and simply call another function. The return value we see is what remains in eax/RAX register  of that call. If MS doesn't document the value means that is not fully guarantee, and may change in future versions.
« Last Edit: March 29, 2013, 10:55:39 AM by frankie »
"It is better to be hated for what you are than to be loved for what you are not." - Andre Gide

Offline jj2007

  • Member
  • *
  • Posts: 536
Re: Treat a BSTR like a WCHAR
« Reply #8 on: March 29, 2013, 11:55:23 AM »
Thanks for confirming my findings. Still, I think that PellesC should throw error #2140 for the BSTR.

CoTaskMemFree is a wrapper and simply call another function. The return value we see is what remains in eax/RAX register  of that call. If MS doesn't document the value means that is not fully guarantee, and may change in future versions.

Yes, CoTaskMemFree calls RtlFreeHeap, which returns TRUE for success and FALSE for failure. But legally speaking you are right, of course - one can never exclude that M$ changes the COM architecture :)

czerny

  • Guest
Re: Treat a BSTR like a WCHAR
« Reply #9 on: March 29, 2013, 12:11:56 PM »
I know what a BSTR is:
Code: [Select]
BSTR btest;
btest = SysAllocString(L"Test");
wprintf(L"len=%u string=%ls\n", *(&btest[-2]), btest);
SysFreeString(btest);

But I was not aware of the behaviour of printf familie, to print a NULL pointer as (null).

Offline jj2007

  • Member
  • *
  • Posts: 536
Re: Treat a BSTR like a WCHAR
« Reply #10 on: March 29, 2013, 01:26:54 PM »
I know what a BSTR is:

I didn't doubt that. However, whether you call "name" a w_char, a BSTR or a banana,

OleRegGetUserType(&clsid, USERCLASSTYPE_FULL, &name);

will always fill the DWORD memory location represented by &name with a w_char, and consequently SysFreeString will fail. Silently, of course. No problem if you do that a hundred or thousand times or so, but I wouldn't recommend it for a large scale app ;-)

czerny

  • Guest
Re: Treat a BSTR like a WCHAR
« Reply #11 on: March 29, 2013, 02:01:21 PM »
... SysFreeString will fail.
Oh, I have accepted that OleRegGetUserType() and SysFreeString() do not match after the first tip. All I wanted to say, is that the printf behaviour was the trap for me (and this after almost 30 years of C programming). I was confused by the fact, that the commented version is running, while the uncommented crashed. Maybe this is helpfull for others.

Offline jj2007

  • Member
  • *
  • Posts: 536
Re: Treat a BSTR like a WCHAR
« Reply #12 on: March 29, 2013, 03:05:29 PM »
the printf behaviour was the trap for me

But I was not aware of the behaviour of printf familie, to print a NULL pointer as (null).

I see. Or rather, I didn't see until I continued in the debugger, ignoring the access violation caused by name[0]==, with the wprintf(L"%ls\n", name) and saw the (null) in the output window. Nice find indeed :)