2

Is it possible using macro magic or TMP to insert the length into a string at compile time?

For example:

const wchar_t* myString = L"Hello";

I would want the buffer to actually contain "[length] [string constant]".

I'm using MSVC 2010 which lacks constexpr. I figured there must be some trick to make this work as its possible to do:

const wchar_t* myString = L"\x005Hello";

My attempt so far:

template<int Size>
wchar_t* toBstr(const wchar_t* str)
{
    #pragma pack(push)
    #pragma pack(1)
    struct BStr
    {
       int len;
       wchar_t data[Size];
    };
    #pragma pack(pop)

  static BStr ret;
  ret.len = Size;

  // don't want to have to copy here, how else could this work??
  //ret.data = str;

  return ret.data;
 }

 const wchar_t* m = toBstr<_countof(L"Hello")>(L"Hello");

This question seems related:

C++ template string concatenation

But not concat for two string constants, rather a constant generated from the length of the 2nd :)

Community
  • 1
  • 1
paulm
  • 5,629
  • 7
  • 47
  • 70
  • 2
    Hmm, that looks like a Big Mistake in the making. The most important implementation detail of a BSTR is not what it looks like, it is where it is *allocated*. That **must** be CoTaskMemAlloc() so it is allocated from the COM heap. Anything else is going to make the consumer of the BSTR fail when it tries to release the string. There is therefore no point in avoiding SysAllocString() – Hans Passant Nov 28 '13 at 11:56
  • At the moment I have a lot of code passing const static wchar_t*'s. Its being passed to Microsofts MSXML COM components and dosen't crash. But recently a tool pointed out that it should really be a BSTR, so I figured that at least putting the length in there is worthwhile..? – paulm Nov 28 '13 at 11:59
  • 1
    Well, you are playing a very dangerous game. Definitely wrong what you did before, I don't understand why you'd consider doing it wrong again. Later versions of Windows have a much stricter heap manager that doesn't put up with bad deallocation calls anymore. – Hans Passant Nov 28 '13 at 12:09
  • The function in question is IXMLDOMElement::setAttribute's first argument which doesn't take ownership of the memory, so the location of the string data shouldn't be an issue. There are 1000's of these calls so the idea is not to use SysAllocString as this will up the execution time and heap usage. – paulm Nov 28 '13 at 12:19
  • 2
    And of course you have absolutely no idea by what degree this is actually a problem because you've never done it the right way before. If you are convinced that this is not a problem then it is a mystery why you are trying to fix it :) – Hans Passant Nov 28 '13 at 12:36
  • Well it would seem that a non owned BSTR is not used as a BSTR, but to avoid any issues in the future it would be nice to prefix the length to avoid any crashes. The other option is do nothing and risk future change causing an issue. – paulm Nov 28 '13 at 13:07
  • 1
    The best option is to follow the specification, and **allocate a proper `BSTR`** using `SysAllocString`. – Ben Nov 28 '13 at 13:13
  • @HansPassant, SysAllocString maintains its own allocator (albeit built on CoTaskMemAlloc), so using CoTaskMemAlloc may not be good enough. – Ben Nov 28 '13 at 13:15
  • [Very similar question](http://stackoverflow.com/questions/27918831/is-there-a-way-to-write-a-bstr-literal). – M.M Feb 15 '15 at 21:16

2 Answers2

1

You cannot create a compile-time BSTR. BSTR are defined to be allocated by SysAllocString and family. If it isn't it isn't a BSTR, but an impostor.

However if the contents of the BSTR is known at compile time, you can have a global BSTR variable, and allocate it only once, avoiding the thousands of allocations you are concerned about.

I.e., have a variable declared as BSTR, but initialize it to the string using SysAllocString.

E.g.:

BSTR bsHello = SysAllocString(L"Hello");
Ben
  • 34,935
  • 6
  • 74
  • 113
  • I still don't understand why it *has* to come from SysAllocString. If I kept a global string and gave it to a COM API then that means it can't free it. If it can't free it then why would it matter where it was allocated if its up to me to free it? – paulm Nov 28 '13 at 13:18
  • This makes no sense. You can't just arbitrarily re-use a SysAllocString'ed BSTR, it won't have the correct buffer size. – Hans Passant Nov 28 '13 at 13:33
  • Why wouldn't the buffer size be correct if it was passed to a function more than once? Now I'm more confused :) – paulm Nov 28 '13 at 13:43
  • 1
    @paulm, a BSTR is something understood by SysReAllocString, SysStringByteLen, SysStringLen, VectorFromBStr and a number of other functions, which do not modify the string. So the question is: Will any of those functions, either today, or in the future, rely on the fact that the string was allocated by SysAllocString? The answer is this: You cannot predict the future. Maybe in the future additional checks will be added in a service pack, just as the heap implementation changed, which may be checked by the other functions. Just because it works today.... – Ben Nov 28 '13 at 16:51
  • @paulm The code being called may check that the address it receives comes from the COM allocator's memory pool. (IDK if this actually happens or not but it is something that I imagine code could do in order to try and detect bugs). – M.M Feb 15 '15 at 21:15
  • @Ben this might leak memory , for example if your code appears in a DLL then the string will be allocated each time the DLL is loaded, but not deallocated when the DLL is freed. It should probably be a RAII wrapper such as CComBSTR (and you've also got to think about static intiialization order fiasco). – M.M Feb 15 '15 at 21:19
1

For BSTR's which are transferred to functions only for reading, I use this simple macro:

#define DECLARE_BSTR(Variable, String)\
struct                                \
{                                     \
    uint32_t uLength;                 \
    OLECHAR szData[sizeof(String)];   \
}                                     \
Variable = {sizeof(String) - sizeof(OLECHAR), String};

Example:

ITaskFolder *pTaskFolder;
DECLARE_BSTR(static bstrTaskFolderName, L"\\");
if (SUCCEEDED(pTaskService->GetFolder(bstrTaskFolderName.szData, &pTaskFolder)))

Variant which can be used in place of a BSTR, i.e. without .szData:

#define DECLARE_BSTR(Variable, String)               \
struct                                               \
{                                                    \
    uint32_t uLength;                                \
    OLECHAR szData[sizeof(String)];                  \
    operator const OLECHAR *() const {return szData;}\
    operator       OLECHAR *()       {return szData;}\
}                                                    \
Variable = {sizeof(String) - sizeof(OLECHAR), String};

Example:

ITaskFolder *pTaskFolder;
DECLARE_BSTR(static bstrTaskFolderName, L"\\");
if (SUCCEEDED(pTaskService->GetFolder(bstrTaskFolderName, &pTaskFolder)))
CoolCmd
  • 939
  • 8
  • 13