The Design of Software (CLOSED)

A public forum for discussing the design of software, from the user interface to the code architecture. Now closed.

The "Design of Software" discussion group has been merged with the main Joel on Software discussion group.

The archives will remain online indefinitely.

Passing Strings From VB6 to VC++6

Hi all,

I think this is a classic but I can't seem to get the results I'm looking for. I'm trying to pass a string from VB6 to a VC++6 export Windows DLL (non-MFC, non ActiveX) which then returns a string to VB and then VB displays it.

My cpp file is:
          .
          .
          .
// uses C mangled name "_PassString@4"
extern "C" __declspec(dllexport) ABSTR __stdcall PassString(LPSTR pText)
{
  // Change of [in,out] pText
  int pTextLen = SysStringByteLen((ABSTR)pText);
  strncpy(pText, pText2, pTextLen);

  // Return string
  return SysAllocStringByteLen(pText2, strlen(pText2));
}

// uses .DEF file to define symbol "PassStringByDEFFile"
extern "C" __declspec(dllexport) ABSTR __stdcall PassStringByDEFFile(LPSTR pText)
{
  return PassString(pText);
}

// uses C++ mangled name "?PassStringByCPP@@YGPAGPAD@Z"
extern "C" __declspec(dllexport) ABSTR __stdcall PassStringByCPP(LPSTR pText)
{
  return PassString(pText);
}
          .
          .
          .
and I have the required DEF and header files. I've modified this program from a VC++ program which doesn't accept any parameter but passes a long to VB and it works fine. But strings are a different matter altogether. I've made sure I use BSTR.

The problem is I'm getting the compiler message:

Can't find DLL entry point

to all my functions above. I've :-

(1) __declspec(dllexport) them;
(2) extern "C" them (so that there's no name mangling);
(3) used BSTR for compatibility between VB / VC++
(4) and put the functions under the EXPORT keyword in my DEF file

My VB6 calling code is :

Public Declare Function PassString Lib "PassString.dll" Alias "_PassString@4" (ByVal s As String) As String

Public Declare Function PassStringByDEFFile Lib "PassString.dll" (ByVal s As String) As String

Public Declare Function PassStringByCPP Lib "PassString.dll" Alias "?PassStringByCPP@@YGPAGPAD@Z" (ByVal s As String) As String

  Dim s As String, s1 As String
 
  s = "PassString"
  s1 = PassString(s)
 
  s = "PassStringByDEFFile"
  s1 = PassStringByDEFFile(s)
 
  s = "PassStringByCPP"
  s1 = PassStringByCPP(s)

Can you tell me what I'm doing wrong ? Thank you in advance!
Ezani Send private email
Wednesday, June 20, 2007
 
 
Just use the DEF file, why are you messing with trying to call mangled names? See the Usenet post below for a good explanation.

http://groups.google.com/group/microsoft.public.vc.language/browse_thread/thread/3af8a1d0400bafbf/c784b32f61069747%23c784b32f61069747
ptr2void Send private email
Wednesday, June 20, 2007
 
 
I really wouldn't try to 'return' a String.  All you can really 'return' is a single value, be that a long, a char, an int, or an address.

If you try to return a 'string', you're probably returning the address of the string -- and I'm not sure what VB is going to do with a string address.

Now, VB can call your routine, and pass you a fixed-length string space.  You can put your results into that fixed-length string space, then VB will get the modified string back.  That works in VB6 all the time.
AllanL5
Wednesday, June 20, 2007
 
 
The two books you really need are "Visual Basic 6.0 Programmer's Guide to the Win32 API" by Appleman, and Hardcore Visual Basic 5.0 by Bruce McKinney.

These two go into the greatest depth about the VB String model, and how it matches with the c/C++ string model.
AllanL5
Wednesday, June 20, 2007
 
 
You're right, ptr2void. Actually the use of extern "C" already avoids the name mangling in the first place.
Ezani Send private email
Wednesday, June 20, 2007
 
 
Ok, you're absolutely right AllanL5. All Windows API calls by VB are in this manner too .. they pass an empty buffer and buffer length and get a return string from the given buffer as in (passed_string, returned_string, string_length).

So, I've modified my code as such:

In my cpp file:
  char * tempStr = "";

extern "C" PASSSTRING_API int __stdcall PassString(char * outString, size_t nBytes)
{
  strncpy(outString, tempStr, nBytes);
  return 0;
}

In my header file:

#ifdef PASSSTRING_EXPORTS
#define PASSSTRING_API __declspec(dllexport)
#else
#define PASSSTRING_API __declspec(dllimport)
#endif

extern "C" PASSSTRING_API int __stdcall PassString(char * outString, size_t nBytes);

and in my DEF file:
EXPORTS
PassString

Compiles okay. No errors, no warnings.

Thus in my VB6 file:

Public Declare Function PassString Lib "PassString.dll" (ByVal myString As String, strLength As Byte) As String

Public Function GetMyString(myString As String) As String
GetMyString = PassString(myString, Len(myString) * 2)
End Function

Dim myStringValue As String *3

myStringValue = "abc"
m = GetMyString(myStringValue)
MsgBox m

But the compiler is giving me error message:

The instruction at "0x100011a9" referenced memory at "0x0013f000". The memory could not be "written".

And then when I clicked OK, another message:

The instruction at "0x77f8a93bnced memory at "0x0000004. The memory could not be "read".

What's going on ? Please help!
Ezani Send private email
Wednesday, June 20, 2007
 
 
What's going on is you chose to write your app in VB and these are the consequences. Learn from your mistakes!
Earl
Wednesday, June 20, 2007
 
 
You are right there, Earl but I have no choice...oh come on, there must be someone out there who's managed to send a string into VC++ from VB and get something back without any problems...
Ezani Send private email
Thursday, June 21, 2007
 
 
I have not called from VB into VC++ but I do a lot of IE/JScript into ActiveX controls.  Programmatically it is almost the same thing. First, in my instance, everything uses COM interfaces.  Not sure if makes difference but there you go.  Second, I *NEVER* pass LPSTR or other Win32 string types.  I have always used BSTR type in and out (mostly for IDispatch reasons). 

It seems that your problem is the same: Calling from an IDispatch based environment (VB) into Vtable based environment (C++).  First, I would use the BSTR type to pass strings in and out. My guess is that your exception is happening because VB and C++ char are different type, sizes and the buffer is totally different.  BSTR gets you around this. 

If that fails then make a COM object and call it that way. I know VB can handle loading and executing an IDisptach based object.  That I have done.
Kenneth Brittain Send private email
Thursday, June 21, 2007
 
 
Here's a call I wrote in one of the products I work on:

extern "C" __declspec (dllexport) long __stdcall GetSessionWithURL (
    const char *pServer,
    const char *pKey,
    const char *pPrincipalID,
    const char *pProxyUser,
    const char *pProxyPassword,
    char *pSessionIDBuf,
    long lSessionIDBufLen,
    int *useSSL)

It retrieves a string into pSessionIDBuf containing a session ID and URL.

Here is how it is declared in VB:

Private Declare Function GetSessionWithURL Lib "RemoteAccess.DLL" ( _
    ByVal szServer As String, _
    ByVal szKey As String, _
    ByVal szPrincipalID As String, _
    ByVal szProxyUserName As String, _
    ByVal szProxyPassword As String, _
    ByVal szSessionIDBuf As String, _
    ByVal lszSessionIDBufLen As Long, _
    ByRef nUseSSL As Integer) As Long

And how it is used:

strSessionIDBuf = String(8000, vbNullChar)
lngKeyBufLen = Len(strSessionIDBuf) - 1
   
hDLL = LoadLibraryEx(m_strAuthDLLPath, 0&, LOAD_WITH_ALTERED_SEARCH_PATH)
If hDLL <> 0 Then
   
  lngKeyRetVal = GetSessionWithURL(strServer, _
                                    strKey, _
                                    strPrincipalID, _
                                    ProxyUserName, _
                                    ProxyPassword, _
                                    strSessionIDBuf, _
                                    lngKeyBufLen, _
                                    nUseSSL)
   
  Call FreeLibrary(hDLL)

Creating a string buffer initialized with NULL characters -- in this case strSessionIDBuf -- is crucial to the success of the call.
ptr2void Send private email
Thursday, June 21, 2007
 
 
Is this happening only with strings?
jaywalker Send private email
Thursday, June 21, 2007
 
 
It's failing because you're telling "PassString" to copy twice as many bytes as you're actually sending it.  so "PassString" is then stepping on the stack when it runs past the 3 bytes you DID send it.

While it is true that the VB run-time DOES store all its strings as Unicode, VB knows this, and so un-unicodes all 'strings' sent to DLL's.
AllanL5
Thursday, June 21, 2007
 
 
Very, very interesting AllanL5 because yes, the reason I did a String*2 is because of the Unicode portion and also because Len("a") returns 1 but LenB("a") returns 2 for VB.

But for the benefit of all programmers concerned (and especially those that has helped me in JoS hi hi hi ;-)), here's how I got the program working :

char tempStr[10] = {' ',' ',' ',' ',' ',' ',' ',' ',' ',' '};

extern "C" PASSSTRING_API long __stdcall PassString(char * outString)

{
  strncpy(tempStr, outString, 10);
  //if (strncmp(tempStr,"1234567890", 10) == 0)
  if (strncmp(tempStr,"1234567890", 10) == 0)
        {return 10;}
  else
        {return 9;}
}

And in VB6:

Public Declare Function PassString Lib "PassString.dll" (ByVal myString As String) As Long

Private Sub Command1_Click()
Dim mn As String * 10

mn = "1234567891"
m = PassString(mn)
MsgBox m

Exit Sub


One poster from CodeGuru recommended that as a start, I send back a Long from VC++ to VB rather than start out with a string because Long are compatible in both VB and VC++ so the problem would be simpler to solve.

The next step to further simplify the problem was to fix the length of the string to be moved to and from from VC++ / VB which would eliminate the need for a 2nd parameter -- the string's length. I fixed that at 10 knowing the string would not exceed this size.

Once I got that working (see above), than I could try expanding it to try to return a string (or rather a pointer --my that would be difficult!), no perhaps better a BSTR to VB from VC++. So now I'm in the process of doing that. However, I don't think I want to even attempt returning a VC++ "string" to VB. What I'll do is just put the VC++ string back into the initial 10 character string that VB sent so it goes in and out through the parameters of the exported function...

And yes, it should be String * 1, not String * 2!
Ezani Send private email
Friday, June 22, 2007
 
 
Ok, guys it works ! -- the returning the string back from VC++6 to VB6. Here's the code:

In VC++:

char tempStr[10] = {' ',' ',' ',' ',' ',' ',' ',' ',' ',' '};

extern "C" PASSSTRING_API long __stdcall PassString(char * outString, char * inString)
{
  strncpy(tempStr, outString, 10);
  //if (strncmp(tempStr,"1234567890", 10) == 0)
  if (strncmp(tempStr,"1234567890", 10) == 0)
    {
      strncpy(inString, "correct...", 10);
      return 1;
    }
  else
    {
      strncpy(inString, "wrong.....", 10);
      return 0
    }
}

I just added another parameter equivalent to the first string parameter (same length of 10 also).

And in VB6:

Public Declare Function PassString Lib "PassString.dll" (ByVal myString As String, ByVal myString1 As String) As Long

Private Sub Command1_Click()

Dim mn As String * 10
Dim mn1 As String * 10

mn = "1234567890"
mn1 = "          "
m = PassString(mn, mn1)
MsgBox mn1

End Sub

Try setting mn = "1234567890" and mn1 displays "correct...", any other values mn1 displays "wrong....."
Ezani Send private email
Friday, June 22, 2007
 
 
Guess where I have to head to next -- my superior has asked me to ActiveX the whole thing (using ActiveX with MFC Foundation) so that it can be called from ASP. Oh boy!
Ezani Send private email
Friday, June 22, 2007
 
 

This topic is archived. No further replies will be accepted.

Other recent topics Other recent topics
 
Powered by FogBugz