PInvoke'd strings seem to be different from what is sent

  • Thread starter Thread starter Richard Gohs
  • Start date Start date
R

Richard Gohs

We've got a VB.Net Pocket PC application calling an unmanged C DLL.
One function in particular is giving us problems. This function takes
an XML string as input and attempts to locate a value and return this
value out. This function is declared as ...

HRESULT GetSessionIDW (/*[in]*/ LPCWSTR pszXML, /*[in, out]*/ LPWSTR
pszSessionID, /*[in]*/ int nMaxSessionIDChars);

in the C code, and declared as ...

Private Declare Function GetSessionID Lib "DSCORE.DLL" Alias
"GetSessionIDW" (ByVal pszXML As String, ByVal pszSessionID As
System.Text.StringBuilder, ByVal nMaxSessionIDChars As Int32) As
Integer

in the VB code.

About 98-99% of the time, this code runs just fine. Within the C
code, if the XML tag cannot be found, it generates a logfile which
includes the XML string that it has and returns an error. Also, in
the VB code, if it sees an error returned from the call, it will log
the string that it passed in. Now for the strange part, every now and
then, the XML string that the C code logs is different from the XML
string that the VB code logs - and this has me completely baffled. We
have seen the following cases:

1. The string within the C code is "part of" the string that the VB
code had (the one I recall was the last 100 or so characters of
approximately 1000 characters)
2. The string within the C code is some string that appears within
the VB code somewhere, but is not in the string at all.
3. The string within the C code appears to be non-printable
characters.

I have not done any testing yet to see if this happens on the full
..Net framework.

Any thoughts?

Thanks,
Richard

Any thoughts on where the problem might lie?
 
Are you setting StringBuilder to a necessary capacity prior to making
PInvoke call?
 
The call to this method (from VB.Net) looks like this ...

there is a "database class" that, among it's methods, includes the
following method ...

Public Shared Function DbGetSessionID(ByVal psXML As String, ByRef
psSessionID As String) As Boolean
Dim sbSessionID As New System.Text.StringBuilder (256)
Dim nResult As Integer

DbGetSessionID = False
nResult = GetSessionID(psXML, sbSessionID,
sbSessionID.Capacity + 1)
If FAILED(nResult) Then ProcessLastError()
If (SUCCEEDED(nResult)) Then
psSessionID = sbSessionID.ToString
DbGetSessionID = True
End If
End Function


.... the actual call of DbGetSessionID (which P'Invokes GetSessionID)
is in another class/DLL ... and looks like this ...


Private Function prvProcessBlockResult(ByRef poGridInfo As
clsGridInfo) As Boolean
Try
Dim bResult, bLoaded, bSuccess As Boolean
Dim sFault, sSessionID, sResponseXML As String
Dim nFailedRows, nLoadedRows As Integer, dServerTime As
Double

prvProcessBlockResult = True
If (poGridInfo.rbGridTypeDelete = False) And
(roProvider.DbActive = True) Then
Debug.WriteLine(poGridInfo.rsGridName & "::Processing
block request with OleDb DLL.", "clsDataGrid.prvProcessBlockResult")
poGridInfo.roWebService.GetResponse(sResponseXML)
poGridInfo.roWebService.pRespPending = False
bResult = roProvider.DbGetSessionID(sResponseXML,
sSessionID)
poGridInfo.rdDownloadTime = CDate("1970-01-01")
If (bResult = True) Then
 
Just an idea on what could be happening here... The managed variables get
pinned (will not be touched by the GC) during the P/Invoke call. I don't
know how syncronouse is your C call, but it could be the situation when the
managed would be completed before the native code would finish using a
variable for logging at which point the managed memory could be changed...
 
Alex,

I had thought about this issue ... but the code in question (the C code) ...
in completely synchronous ... in a nutshell, the C code is as follows ...

DSCORE_API HRESULT GetSessionIDW (/*[in]*/ LPCWSTR pszXML, /*[in, out]*/
LPWSTR pszSessionID, /*[in]*/ int nMaxSessionIDChars)
{
HRESULT hr = S_OK;

SetErrorInfo (S_OK, NULL, NULL);

LPTSTR pszSessionIDTag = NULL;
hr = GetTag (pszXML, _T("<sessionId>"), _T("</sessionId>"),
&pszSessionIDTag);
if (SUCCEEDED (hr))
{
if ((int)_tcslen (pszSessionIDTag) <= nMaxSessionIDChars)
{
wcscpy (pszSessionID, pszSessionIDTag);
}
else
{
hr = E_FAIL; // not enough space to copy the session ID
SetErrorInfo (hr, _T("GetSessionID"), _T("Not enough
space to copy the session ID"));
}

LocalFree (pszSessionIDTag);
}

return (hr);
}

.... the "GetTag" call is a simple one as well ... it simply looks for the
"starttag" and the "endtag" in the xml ... and returns the value that's
between them ...

HRESULT GetTag (/*[in]*/ LPCTSTR pszXML, /*[in]*/ LPCTSTR pszStartTag,
/*[in]*/ LPCTSTR pszEndTag, /*[out]*/ LPTSTR* ppszTag)
{
HRESULT hr = S_OK;

LPTSTR pszStartTagFind = _tcsstr (pszXML, pszStartTag);
if (pszStartTagFind)
{
DWORD dwStartTagLen = (DWORD)_tcslen (pszStartTag);

LPTSTR pszEndTagFind = _tcsstr (pszStartTagFind + dwStartTagLen,
pszEndTag);
if (pszEndTagFind)
{
DWORD dwChars = (DWORD)(pszEndTagFind - (pszStartTagFind +
dwStartTagLen));
LPTSTR pszTag = (LPTSTR)LocalAlloc (LMEM_FIXED, (dwChars +
1) * sizeof (TCHAR));
if (pszTag != NULL)
{
_tcscpy (pszTag, _T(""));
_tcsncat (pszTag, pszStartTagFind + dwStartTagLen,
dwChars);

*ppszTag = pszTag;
}
else
{
hr = E_OUTOFMEMORY;
SetErrorInfo (hr, _T("GetTag"), _T("Out of memory
allocating results"));
}
}
else
{
hr = E_FAIL; // no end tag found

TCHAR szError [256];
_stprintf (szError, _T("Failed to find end tag '%.*s' in
XML"),
min (128, _tcslen (pszEndTag)), pszEndTag);
SetErrorInfo (hr, _T("GetTag"), szError);
}
}
else
{
hr = E_FAIL; // no start tag found

TCHAR szError [256];
_stprintf (szError, _T("Failed to find start tag '%.*s' in
XML"),
min (128, _tcslen (pszStartTag)), pszStartTag);
SetErrorInfoWithXML (hr, _T("GetTag"), szError, pszXML);
}

return (hr);
}


.... the SetErrorInfo/SetErrorInfoWithXML saves the errorcode and errortext
out to global variables (the space for the string is allocated) ... so that
a "GetLastError" can be called to get a more meaningful description of the
error ... you may notice that the pszXML parameter is passed to that routine
.... this is were we've noticed a different than what was passed from the VB
code ...

Thanks,
Richard
 
That's my bad ... the code actually does have the +1 on it (I must have lost
it getting it into the post) ... the other thing to remember is that the
string being passed back is only 8 characters (so it would easily fit into
the stringbuilder even if the capacity had been passed in wrong) ... and one
final thing to note is that internally, the 'GetSessionID' call will not
return more characters in the 2nd parameter than was passed in the 3rd
parameter ... so it still wouldn't be able to overflow the buffer of the
stringbuilder object (i.e. I could safely pass in sbSession.Capacity - 123
.... and the C code still wouldn't / couldn't "overwrite" the stringbuilder
buffer).



Hope this make sense and thansk for the help so far ... this problem is the
last known problem with our code ... I'm really trying to figure it out.

Richard
 
Richard,

Off the top of my head I don't see anything wrong with the code. Try
launching it under eVC debugger using the technique from my article. YOu may
be able to spot something that way
 
I agree with Alex that it looks like the string object is moving around
underneath you. Your code below looks good. Is it possible that your
logging code is not printing the string synchronously? Perhaps it is
buffering the pointer to the string at the time of the call and and dumping
it out later?
Brian

--------------------
From: "Richard Gohs" <[email protected]>
References: <[email protected]>
<[email protected]>
Subject: Re: PInvoke'd strings seem to be different from what is sent
Date: Thu, 27 May 2004 12:26:43 -0400

Alex,

I had thought about this issue ... but the code in question (the C code) ...
in completely synchronous ... in a nutshell, the C code is as follows ...

DSCORE_API HRESULT GetSessionIDW (/*[in]*/ LPCWSTR pszXML, /*[in, out]*/
LPWSTR pszSessionID, /*[in]*/ int nMaxSessionIDChars)
{
HRESULT hr = S_OK;

SetErrorInfo (S_OK, NULL, NULL);

LPTSTR pszSessionIDTag = NULL;
hr = GetTag (pszXML, _T("<sessionId>"), _T("</sessionId>"),
&pszSessionIDTag);
if (SUCCEEDED (hr))
{
if ((int)_tcslen (pszSessionIDTag) <= nMaxSessionIDChars)
{
wcscpy (pszSessionID, pszSessionIDTag);
}
else
{
hr = E_FAIL; // not enough space to copy the session ID
SetErrorInfo (hr, _T("GetSessionID"), _T("Not enough
space to copy the session ID"));
}

LocalFree (pszSessionIDTag);
}

return (hr);
}

... the "GetTag" call is a simple one as well ... it simply looks for the
"starttag" and the "endtag" in the xml ... and returns the value that's
between them ...

HRESULT GetTag (/*[in]*/ LPCTSTR pszXML, /*[in]*/ LPCTSTR pszStartTag,
/*[in]*/ LPCTSTR pszEndTag, /*[out]*/ LPTSTR* ppszTag)
{
HRESULT hr = S_OK;

LPTSTR pszStartTagFind = _tcsstr (pszXML, pszStartTag);
if (pszStartTagFind)
{
DWORD dwStartTagLen = (DWORD)_tcslen (pszStartTag);

LPTSTR pszEndTagFind = _tcsstr (pszStartTagFind + dwStartTagLen,
pszEndTag);
if (pszEndTagFind)
{
DWORD dwChars = (DWORD)(pszEndTagFind - (pszStartTagFind +
dwStartTagLen));
LPTSTR pszTag = (LPTSTR)LocalAlloc (LMEM_FIXED, (dwChars +
1) * sizeof (TCHAR));
if (pszTag != NULL)
{
_tcscpy (pszTag, _T(""));
_tcsncat (pszTag, pszStartTagFind + dwStartTagLen,
dwChars);

*ppszTag = pszTag;
}
else
{
hr = E_OUTOFMEMORY;
SetErrorInfo (hr, _T("GetTag"), _T("Out of memory
allocating results"));
}
}
else
{
hr = E_FAIL; // no end tag found

TCHAR szError [256];
_stprintf (szError, _T("Failed to find end tag '%.*s' in
XML"),
min (128, _tcslen (pszEndTag)), pszEndTag);
SetErrorInfo (hr, _T("GetTag"), szError);
}
}
else
{
hr = E_FAIL; // no start tag found

TCHAR szError [256];
_stprintf (szError, _T("Failed to find start tag '%.*s' in
XML"),
min (128, _tcslen (pszStartTag)), pszStartTag);
SetErrorInfoWithXML (hr, _T("GetTag"), szError, pszXML);
}

return (hr);
}


... the SetErrorInfo/SetErrorInfoWithXML saves the errorcode and errortext
out to global variables (the space for the string is allocated) ... so that
a "GetLastError" can be called to get a more meaningful description of the
error ... you may notice that the pszXML parameter is passed to that routine
... this is were we've noticed a different than what was passed from the VB
code ...

Thanks,
Richard



Alex Yakhnin said:
Just an idea on what could be happening here... The managed variables get
pinned (will not be touched by the GC) during the P/Invoke call. I don't
know how syncronouse is your C call, but it could be the situation when the
managed would be completed before the native code would finish using a
variable for logging at which point the managed memory could be changed...

This posting is provided "AS IS" with no warranties, and confers no rights.
 
Back
Top