1

I am trying to call a C# library from C++. Here's the function that makes the first call:

CapsReport::CapsReport(const CCOMString& reportFileName, CCDatabase* pDatabase) 
    : m_pDatabase(pDatabase), m_pReportServer(__uuidof(ReportServer))
{
    ::CoInitialize(NULL);
    SetReportName(reportFileName);
    // public void Load(string DSNname, string userName, string password, string reportFileName)

    BSTR dsnBstr = pDatabase->DataSource().GetManagedBstr();
    BSTR userBstr = pDatabase->UserName().GetManagedBstr();
    BSTR passwordBstr = pDatabase->Password().GetManagedBstr();
    BSTR reportNameBstr = ::SysAllocString(reportFileName);
    m_pReportServer->Load(dsnBstr, userBstr, passwordBstr, reportNameBstr);
    ::SysFreeString(reportNameBstr);    
}

By the time the Load() method is called, all three BSTRs returned by GetManagedBstr() contain the password. I'm trying to figure out why.

DataSource(), UserName() and Password() all return objects of type CCOMString:

CCOMString UserName() { return m_User; };
CCOMString Password() { return m_Pwd; };
CCOMString DataSource() { return m_DSN; };

I didn't want to have to call ::SysAllocString() and ::SysFreeString() every time I wanted to pass a CCOMString into a C# library, so I added a private BSTR member to the CCOMString class initialized to NULL. In the destructor, I check to see if that member is null. If it is not, I call ::SysFreeString() and set it to NULL.

The GetManagedBstr() method uses an existing member function of CCOMString named AllocSysString(). First, here's GetManagedBstr():

BSTR CCOMString::GetManagedBstr()
{
    m_bstr = AllocSysString();
    return m_bstr;
}

Now, here's AllocSysString():

BSTR CCOMString::AllocSysString() const
{

#ifndef _UNICODE
    int nLen = MultiByteToWideChar(CP_ACP, 0, (LPCSTR)m_pszString, GetLength(), NULL, NULL);
    BSTR bstr = ::SysAllocString(NULL, nLen+1);
    MultiByteToWideChar(CP_ACP, 0, (LPCSTR)m_pszString, GetLength(), bstr, nLen);
#else
    int nLen = _tcslen(m_pszString);
    //BSTR bstr = ::SysAllocStringLen(NULL, nLen);
    BSTR bstr = ::SysAllocString(m_pszString);
//  _tcscpy_s(bstr, nLen, m_pszString);
#endif

    return bstr;
}

m_pszString is merely the plain ordinary C++ string wrapped by CCOMString.

A BSTR object is a pointer. Somehow, all three BSTR objects are ending up pointing to the same memory location, even though they should have come from different CCOMString objects. How is this happening?

ROBERT RICHARDSON
  • 2,077
  • 4
  • 24
  • 57
  • What are `m_User`, `m_Pwd`, `m_DSN`? How are they constructed/initialized? – 1201ProgramAlarm May 02 '19 at 20:27
  • "_m_pszString is merely the plain ordinary C++ string_" So, `std::string`? If so, `(LPCSTR)m_pszString` doesn't make sense. Please provide [mcve], so we shouldn't guess any details. – Algirdas Preidžius May 02 '19 at 20:33
  • If I store the results of Database(), UserName() and Password() into local variables inside the CapsReport constructor, I have no problem. But I would still like to understand where my mistake is so I don't repeat it somewhere else. – ROBERT RICHARDSON May 02 '19 at 20:36
  • I am sorry, but it is impossible for me to provide a minimial, complete and verifiable example. The CCOMString code shown above is more than 13 years old (that's how long I've been with the company, and the code was in use before that.) The code is older than std::string. – ROBERT RICHARDSON May 02 '19 at 20:39
  • m_User, m_Pwd and m_DSN are all CCOMStrings. m_pszString is an LPTSTR. – ROBERT RICHARDSON May 02 '19 at 20:40
  • SysAllocString shouldn't return the same pointer on different calls, so you probably have a bug in this CCOMString class somehow. – Simon Mourier May 02 '19 at 20:54
  • 1
    Does `CCOMString` destructor deallocate the BSTR, by any chance? Who actually manages the string returned by `GetManagedBstr`? My guess is, `DataSource()` returns a temporary, which gets destroyed at the semicolon and takes the `BSTR` to the grave with it, leaving `dsnBstr` a dangling pointer. – Igor Tandetnik May 03 '19 at 04:04
  • 1
    From previous questions, it seems you are using Visual Studio. Why not use _bstr_t or CComBSTR ? Also, you have CoInitialize() in the constructor of your CapsReport(). I probably wouldn't do that. Put it at the beginning of the program/thread with the matching CoUninitialize() at the end of the program/thread. – Joseph Willcoxson May 03 '19 at 04:56
  • I also recommend to use use _bstr_t or CComBSTR! – xMRi May 03 '19 at 06:16
  • @IgorTandetnik I think you've hit it. The CCOMString destructor does deallocate the BSTR. That was what I wanted to have done when I created GetManagedBstr(), so I wouldn't have to remember to do it when I wanted to get a BSTR. Thanks very much! – ROBERT RICHARDSON May 03 '19 at 12:35
  • @JosephWillcoxson The problem is that there is another class in the CapsLib library that calls CoInitialize(). It's a database wrapper that is used through the code. I have no idea whether any thought was ever given to trying to call it only once, or to trying to match CoUninitialize() calls to every CoInitialize() call. But from what I read, multiple calls to CoInitialize() don't hurt anything, as long as you match them with CoUninitialize(), so I put a CoInitialize() in my constructor and CoUnitialize() in the destructor. – ROBERT RICHARDSON May 03 '19 at 12:38
  • I am not familiar with _bstr_t or CComBSTR. I will look them up. Thanks to all of you for your help! – ROBERT RICHARDSON May 03 '19 at 12:40

0 Answers0