9

Is it legal to cast a LPTSTR directly to a BSTR?

Based on my understanding of BSTR, casting a LPTSTR to a BSTR directly will leave you with a corrupted length prefix. The example code explicitly states that a string literal cannot be stored to a BSTR. Can anyone confirm for me that a LPTSTR/LPCTSTR cannot be cast directly to a BSTR without corrupting the length prefix?

EDIT:

My confusion is from seeing this used in a call to a COM object. It turns out that when compiling the COM dll, a .tli file is generated that creates an intermediate method. This method takes type _bstr_t. The _bstr_t can take LPTSTR in its constructor, so everything works smoothly.

Mashmagar
  • 2,556
  • 2
  • 29
  • 38

8 Answers8

9

If your program is unicode and your LPTSTR therefore is a LPWSTR, you can use SysAllocString to convert from a pointer to a wide character string to BSTR.

A direct cast is not possible because the two have different memory representations.

If you use C++, you can use the _bstr_t class to simplify the usage of BSTR strings.

Timbo
  • 27,472
  • 11
  • 50
  • 75
4

If you are trying to pass LPCTSTRs as BSTRs you will find that you will randomly blow up any interop marshalling code you get near. The marshalling will pull the 4 byte prefix (which is random data) for an LPCTSTR and try and Marshall the string.

You will find that the 4 byte prefix turns out to be the stack contents prior to your buffer, or even better character data from the string before it. You will then try to marshall megabytes of data... :-)

use the CComBSTR wrappers and use .Detach() if you need to keep the BSTR pointer.

Be nice if the compiler spat out a warning on this one.

NG_
  • 6,895
  • 7
  • 45
  • 67
  • Just got bitten by this, passing three L"My String" parameters to a .Net COM method expecting 3 BSTRs and only one survived the interop marshalling. No warnings from the compiler VS2008 /W4 – Lallen Jun 20 '16 at 12:06
  • The 4 byte prefix represent length of BSTR. – lsalamon Nov 16 '16 at 10:32
1

It cannot be, because then the four bytes in memory preceding the LPTSTR would be considered as the length of the resulting BSTR. This might cause a memory protection fault on the spot (not very likely), but it would certainly result in a BSTR with a length that could be way larger than the length of the original LPTSTR. So when someone tries to read or write from that they may access invalid memory.

Jon
  • 428,835
  • 81
  • 738
  • 806
  • 4
    The string data would not be interpreted as length. The bytes immediately preceding would be. A BSTR points to the string part of the length+string structure. – Ben Voigt Mar 10 '11 at 15:30
  • @Ben: Thank you for the clarification, I did not know that. I 'll revise the answer accordingly. – Jon Mar 10 '11 at 15:33
1

A LPTSTR is a pointer to a char array (or TCHAR to be exact) BSTR is a structur (or composite data) consist of * Length prefix * Data string * Terminator

so casting will not work

nobs
  • 708
  • 6
  • 25
1

No, you cannot - if the code that believes it is a BSTR calls SysStringLen() it will run into undefined behavior since that function relies on some implementation-specific service data.

sharptooth
  • 167,383
  • 100
  • 513
  • 979
0

No, you cannot cast them directly. However, you can make a string that does both. A C-String doesn't have a 4-byte header. However, the bit in the middle is the same so if you find yourself needing both representations, make a wrapper class that constructs a string with a 4byte header and a null terminator but can return accessors to the BSTR portion and the C-String.

This code is intended as an incomplete example, I haven't compiled this!

class YetAnotherStringType //just what the world needs
{
  public:
  YetAnotherStringType(const char *str)
  { 
     size_t slen = strlen(str);
     allocate(slen);
     set_size_dword(slen);  
     copy_cstr(str, slen);     
  }

  const char *get_cstr() const
  {
     return &m_data[4];
  }

  const BSTR get_bstr() const
  {
     return (BSTR*)m_data;
  }

  void copy_cstr(const char *cstr, int size = -1)
  {
      if (size == -1)
         size = strlen(cstr);
      memcpy(&m_data[4], cstr, size + 1); //also copies first null terminator
      m_data[5 + size] = 0; //add the second null terminator
  }

  void set_size_dword(size_t size)
  {
     *((unsigned int*)m_data) = size;
  }

  void allocate(size_t size)
  {
     m_data = new char[size + 6]; //enough for double terminator
  }

  char *m_data;
};
Luther
  • 1,786
  • 3
  • 21
  • 38
0

You cannot cast, you must convert. You can use the built-in compiler intrinsic _bstr_t (from comutil.h) to help you do this easily. Sample:

#include <Windows.h>
#include <comutil.h>

#pragma comment( lib, "comsuppwd.lib")

int main()
{
    LPTSTR p = "Hello, String";
    _bstr_t bt = p;
    BSTR bstr = bt;
    bstr;
}
John Dibling
  • 99,718
  • 31
  • 186
  • 324
  • @John Dibling: Hi. Would the example I gave not work? If not, could you explain why please? – Luther Mar 10 '11 at 15:27
  • 1
    @Luther: Two things. 1) Why write a bunch of code when MS already provides a class to do this for you (_bstr_t)? 2) The code you wrote may or may not work; I'm not sure. You're supposed to call `SysAllocString` to create a BSTR, but you are trying to use you knowledge of the inner-workings of a BSTR to construct one yourself. Also, the string portion of a BSTR is Unicode. Yours, I think, is not. – John Dibling Mar 10 '11 at 15:41
  • @John Dibling: Thanks for the response. I'm not trying to reinvent the wheel, the code was just to illustrate a point. Our man here was asking about casting, rather than constructing and copying, a BSTR. He may have good reason to cast rather than reconstruct (tight memory requirements, many calls inside an inner loop, doesn't want to touch the heap etc.) and the class I sketched there *should* achieve that. If BSTRs are UniCode, then replace the char* with wchar_t*. Sometimes it is best to write a bit of code yourself if the standard ways don't do the job the way you'd like them to. – Luther Mar 10 '11 at 16:15
  • @Luther: The class you sketched doesn't achieve casting. It achieves conversion, via the various calls int eh constructor. Your constructor does, in effect, exactly what we think `SysAllocString` might do based on what we know of the representation of a BSTR. Moreover, the OP didn't ask how to cast. They asked whether it was "legal" to cast an LPSTR to a BSTR. If we reinterpret "legal" to mean "a good idea" then the answer is clearly "no". Your class would seem to agree with my assesment, since you don't cast yourself - you convert. Albiet I do agree sometimes you have to write code.:) – John Dibling Mar 10 '11 at 16:35
  • When I said casting, I was refering to the accessor functions I gave. This is just to show that you can, given a class that constructs your string in memory with the correct amount of buffer space either side, cast a c-string and a BSTR from the same region in memory. That's all I was trying to show. You're right however: I have no idea if SysAllocString does some reference counting or other shenanegans and in production code people should absolutely use best practice methods. That should never stop people trying to understand what's going on under the hood however. – Luther Mar 10 '11 at 16:48
  • @John: it's not necessarily Unicode, although on "modern" Windows it is. But I think since one shouldn't rely on assumptions like the inner workings with the length field, this assumption shouldn't be made either. Also in VS2003 it's `comsupp.lib`. – 0xC0000022L Mar 10 '11 at 18:39
0

Generally speaking no, but there are ways to make them compatible to some extent via helper classes and macros (see below).

The main reason why a 1:1 mapping will never be possible is that a BSTR (and consequently CComBSTR can contain '\0' in the string, because ultimately it is a counted string type.


Your best choice when using C++ would be to go for the ATL class CComBSTR in place of BSTR proper. In either case you can make use of the ATL/MFC conversion macros CW2A and friends.

Also note that the documentation (MSDN) says:

The recommended way of converting to and from BSTR strings is to use the CComBSTR class. To convert to a BSTR, pass the existing string to the constructor of CComBSTR. To convert from a BSTR, use COLE2[C]DestinationType[EX], such as COLE2T.

... which applies to your use case.

Please see John Dibling's answer for an alternative (_bstr_t).

0xC0000022L
  • 20,597
  • 9
  • 86
  • 152