0

I make an OCX in C++ Builder Borland. My OCX has these function as I describe it:

  • Login
  • Write
  • Read
  • DebugOutput
  • GetVal

I am using this OCX in my c# application. In c# side I have a button and list box. The button calls login method from OCX and the list box shows this method's output.

In OCX side, the login method creates a command for server (with socket programming) to get authentication. Then call Write function to writes on socket. Then gets response from socket and calls Read function to reads the socket response.

The Read method reading the result and send it to DebugOutput to debug the output stream and call GetVal to find Last main server response. Then they pass their parameters to each other. after all the C# side show the result (SUCCESS | FAIL ) for login methods.

I used BSTR. (I read the topics about BSTR in stackoverflow and on MSDN but I think I did not get it well in my solution). These are my code in OCX side:

BSTR STDMETHODCALLTYPE TVImpl::Login(BSTR PassWord)
{
  wchar_t wcs1[500];
  Var *var=new Var();
  //here make the command
  ...................
  //get the server response to show to user
  BSTR read=::SysAllocString(Write(wcs1));
  if(read!=NULL) ::SysFreeString(read);

  return read;
}

BSTR STDMETHODCALLTYPE TVImpl::Read()
{
  BSTR str ;
  try
  {
   IdTCPClient1->ReadTimeout=100;
   str =::SysAllocString( IdTCPClient1->IOHandler->ReadLn().c_str());
  }
  catch(Exception &e)
  {
    str= e.Message.c_str();
  }
  str=(DebugOutput(str));
  return str;
}

BSTR STDMETHODCALLTYPE TVImpl::Write(BSTR str)
{
  IdTCPClient1->IOHandler->WriteLn(str) ;
  BSTR str2=::SysAllocString(L"TST");
  str2=Read();
  return str2;
}
BSTR STDMETHODCALLTYPE TVImpl::GetVal(BSTR st,BSTR ValTyp)
{
  BSTR res;
  AnsiString stAnsi;
  //Do some thing with st and save it to stAnsi
  .................
  res=(BSTR)WideString(stAnsi);
  return ::SysAllocString(res);
}

BSTR STDMETHODCALLTYPE TVImpl::DebugOutput(BSTR st)
{
    Var *val=new Var();
    BSTR res;
    res=GetVal(st,val->CMD_CMD);
    if(res==val->CMD_AUTHENTICATE)
        res=GetVal(st,val->XPassword);
    return res;
}

In C3 code it was hanging. I know my problem is to use sysAllocString. but I when I used ::sysFreestring for each one in each method again my C# code hanging.
This is my C# code :

private void button4_Click(object sender, EventArgs e)
    {
        VinSockCmplt.Vin vin = new Vin();
        listBox1.Items.Add(vin.Login("1234"));
   }
Community
  • 1
  • 1
MHM
  • 261
  • 1
  • 2
  • 15
  • So your c# code hangs, but you havent shown it... – BugFinder Sep 13 '16 at 06:46
  • There is no need to be rude. – BugFinder Sep 13 '16 at 06:58
  • Excuse me but I did not mean anything bad – MHM Sep 13 '16 at 07:02
  • You forget to use ::sysFressstring. – H.Ghassami Sep 13 '16 at 07:24
  • I said . It a gains hanging. – MHM Sep 13 '16 at 07:25
  • I test it. When I used `BSTR* [out]` The c# code is hanging. – MHM Sep 13 '16 at 07:46
  • the solution to "the c# code is hanging" is not "randomly try other stuff and see if it stops hanging" – M.M Sep 13 '16 at 07:47
  • "is certainly a bug, you free a string and then return it", so ::sysFreestring is clean the variables? "you return pointer to something that isn't even a BSTR" I can omit it. really thanks for help. for fourth comment, you said "leaks memory, you immediately leak the string you just allocated". sorry. could you explain it for me? and also what you mean dangling pointer? – MHM Sep 13 '16 at 07:52
  • "dangling pointer" means pointer pointing to memory that has already been freed. `WideString(stAnsi)` creates a temporary `WideString` that stops existing at the next `;` ; and the cast to `(BSTR)` produces a pointer into that temporary string. But you then use that pointer on the next line of code. – M.M Sep 13 '16 at 07:53
  • `str2=Read();` makes the pointer `str2` point to the same location as the return value of `Read`. It no longer points to the space allocated by `::SysAllocString(L"TST");`, which is now leaked since there are no pointers to that space. (Maybe you mistakenly think that `=` has some sort of string copy effect) – M.M Sep 13 '16 at 07:54
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/123218/discussion-between-mhm-and-m-m). – MHM Sep 13 '16 at 08:42

2 Answers2

5

There are lots of mistakes in your code:

  • if(read!=NULL) ::SysFreeString(read); return read; frees a string and then returns it
  • str= e.Message.c_str(); ... return str; returns a pointer to something that isn't even a BSTR
  • BSTR str2=::SysAllocString(L"TST"); str2=Read(); allocates a string and then leaks that string immediately
  • res=(BSTR)WideString(stAnsi); creates a dangling pointer

All of these are undefined behaviour (except the leak) and might cause a crash.

Also, I'm not sure if it is valid to have your functions return BSTR; the normal convention in COM programming is that functions return HRESULT and any "return values" go back via [out] parameters. This is compatible with all languages that have COM bindings; whereas actually returning BSTR limits your code compatibility. (I could be wrong - would welcome corrections here). You can see this technique used in the other question you linked.


To get help with a question like "Why is my code crashing?", see How to Ask and How to create a Minimal, Complete, and Verifiable example.

Community
  • 1
  • 1
M.M
  • 138,810
  • 21
  • 208
  • 365
5

You should not return BSTR (or any "complex" type that various compilers compile in various ways). The recommended return type for COM methods is HRESULT (a 32-bit integer). Also, you must not release a BSTR you just allocated and return it to the caller.

Here is how you could layout your methods:

HRESULT STDMETHODCALLTYPE TVImpl::Login(BSTR PassWord, /*out*/ BSTR *pRead)
{
  ...
  *pRead = ::SysAllocString(L"blabla"); // allocate a BSTR
  ...
  // don't SysFreeString here, the caller should do it
  ...
  return S_OK;
}

If you were defining your interface in a .idl file, it would be something like:

interface IMyInterface : IUnknown
{
    ...
    HRESULT Login([in] BSTR PassWord, [out, retval] BSTR *pRead);
    ...
}

retval is used to indicate assignment semantics are possible for languages that support it, like what you were trying to achieve initially with code like this var read = obj.Login("mypass")

Simon Mourier
  • 132,049
  • 21
  • 248
  • 298
  • really thanks for help. I do this: `*pRead = ::SysAllocString(L"blabla"); // allocate a BSTR` but this error is showing `Cannot convert 'wchar_t *' to 'wchar_t * *'` . Is `BSTR` is need * ? – MHM Sep 13 '16 at 08:10
  • @MHM That error message does not correspond to that code . Maybe you accidentally put `BSTR **pRead` in the parameter; or you wrote `BSTR *pRead = ` – M.M Sep 13 '16 at 08:14
  • @M.M When I create method in .ridl and put param in [out,retval] it shows this error : Out parameters required pointers type. then I change BSTR to BSTR* . Ans this is my method.STDMETHODIMP TVinaImpl::tst(BSTR* p) { try { p=::SysAllocString(L"Test bstr232"); } catch(Exception &e) { return Error(e.Message.c_str(), IID_IVina); } return S_OK; } but this error shows: Cannot convert 'wchar_t *' to 'wchar_t * *' – MHM Sep 13 '16 at 08:38
  • @MHM It should be `*p =` . Pay attention to detail – M.M Sep 13 '16 at 08:39