Link to home
Start Free TrialLog in
Avatar of BrotherEarth
BrotherEarthFlag for United States of America

asked on

VB6 to VB.NET, String handling changes causes failure

Hi,
I am new to Experts-Exchange.  I have been "lurking" for a few years and realized the value of the site, so I signed up as a subscriber.

With that said, I am having difficulty with an old DLL (about 8 years old!).  The developer/provider of the dll has not made any changes, and there are no new versions.
This is the documented use of the library to log on to the Common Access Card provided by the library creator:

Long logonToCard (IN BSTR * pin, IN long pinLen)

I have been working with this library for years.  The VB6 code below works fine in VB6 and MS Access 2003:

Call oCac.logonToCard(StrConv(strPin, vbFromUnicode), Len(strPin))

However, I am moving on to VB.NET.  The same VB6 code does not work in VB.NET.  I suspect it is that the strPIN is being provided incorrectly to the LogonToCard call.  Here's what I tried...

Dim strPIN As String = "12345678"
Dim intPinLen As Integer = Len(strPIN)
Call obCac.logonToCard(strPIN, intPinLen)

I get this error:
{Microsoft.VisualBasic.ErrObject}
    Description: "Exception from HRESULT: 0xFFFF8ACC"
    Erl: 0
    HelpContext: 0
    HelpFile: ""
    LastDllError: 0
    Number: -30004
    Source: "Interop.DMDCCACLib"
The error -30004 is defined in the documentation as "Authenticator value or type is not correct"

With the VB6 code (that works fine), I had to "StrConv" the strPIN from vbFromUnicode.  When I look at the value returned in the debug window for VB6, I see "????", so I know that it is changing the human readible string to something the library understands.

Since VB.NET provides for Unicode by default, I guess I need to convert it to a bstr.  But no StrConv function exists in VB.NET.

I am stuck.  Any ideas?
Thanks.
Avatar of Craig Wagner
Craig Wagner
Flag of United States of America image

Is the call to this DLL using COM Interop or are you using DllImport to make the method available? If the latter, could you include your declaration of the external function.
Avatar of BrotherEarth

ASKER

Thank you for the quick response.  I am using the COM Interop.
*shot in the dark*

I've seen passing a StringBuilder instead of String fix some interop problems:

        Dim strPIN As New System.Text.StringBuilder
        strPIN.Append("12345678")
        Call obCac.logonToCard(strPIN, strPIN.Length)
Thanks for the code.  I just tried it...didn't work.  Got this error:
Value of type 'System.Text.StringBuilder' cannot be converted to 'String'.

Alrighty.  =\

It works for some APIs...just depends how they are written I suppose...
That's odd, I would think Interop would take care of whatever translation would be required.

If you need a non-Unicode string to pass to the library (which again seems odd as Interop should be handling that), you could try something like:

Encoding.Default.GetString( Encoding.Default.GetBytes( theString ) )

Given that it's Interop, what does the Intellisense show as the type of the first argument? I would think it would be String.
I agree that this is very odd.  The intellisense shows "string" which makes sense to me.  So I tried your suggested code, and I get the same error.

I am going to keep trying.  The developer that wrote this dll has retired, he used to help me with this library.  Now its' up to his underlings and they are no help.  I'll post anything I find out.

Thank you for trying.
Avatar of HooKooDooKu
HooKooDooKu

Shot in the dark from somewhere else...

What if you wrote your own StrConv() function and have the function return a byte array.  Basically copy the ASC value of each character in the string and pass the byte array.

Otherwise, in doing a search on the details of StrConv(), I ran into this MS article that suggests the command Encoding.Convert can do what you are attempting to do with StrConv().

http://msdn.microsoft.com/en-us/library/d3sz0f8a(VS.80).aspx
Thanks for the link and the idea.  Well, I went to the MSDN site and I created an example class and tested it.  But the results are the same.  I have spent hours researching this without a solution.

I am considering writing a "Wrapper DLL" to wrap the old library with vb6 that does all the work.  I can only justify this because I am using an old library.  Then I'll include the new VB6 dll in my project.  I feel like that is "cheating" or a copping out.  But I cannot move forward until this is solved.  What do you think?
If you can easily get VB.Net to talk to a VB6 dll, GO FOR IT.  It should be doable, as I have written a VB6 DLL that was getting called from a C++.Net application (through the did have to jump through some hoops to make it work... first was the creation of an import library, and then special logic had to be written to change all CString objects to BStr before they could be passed to the VB6 dll.  Don't know if you'll encounter anything similar trying to interface VB.Net with VB6.

One question that has not been address in this discussion so far... what does the interface to logonToCard() look like?  If the purpose of StrConv was to convert Unicode text to ASC strings, that's where I thought you might be able to get away with passing a byte array and just right your own StrConv.
Well, the dll specifically required a string, passing the the byte array caused it to choke.  I still don't quite understand what was happenining.

But since my last post, I installed VB6 on my virtual Win2K.  Created a small class using the code snippet that worked in VB6 (logontocard), that allows me to pass the Object from VB.net to VB6 along with the PIN.  It returns a string consisting of success message "OK" or the error description.  Then I compiled it and created the DLL.  Then I registered it in my VB.NET machine, added its com reference and VOILA!!!  Worked perfectly.

I still think I cheated.  But at least I can move forward.  Some day, I'll figure it out.

Thank you everyone for your help.  I signed up for this service for 2 years...I plan to use it.  Thanks again.
-Dino
I don't know what your Declare statement would look like since I don't have the api that you are using so if you could post that it would help.


You should be able to declare the api call in your VB.Net code and call it with out any issue if you modify the declare statement to change how the string is sent the com api.

The following article might help with your issue:

http://visualbasic.about.com/od/usingvbnet/a/WinAPISet.htm

I wish I could post the library and code, but this is a DoD Common Access Card SDK, and I think that I am not allowed to post the dll in public forums since I have to use my CAC to download the libraries from the DoD site in the first place.  So that's why I tried to boil it down to just string handling in .net vs vb6 since that way I could post my code and questions, without having to post the libraries or docs.

I don't even declare the library, I just reference it.  Then intellisense guides me through the calls.  The Object Browser also helps.  When I tried declaring the specific calls in a Declare statement, the entry point is never found.  Maybe I don't know exactly how to declare the specific functions with this library.  So I have to create the object dmdccaclib.CommonAccessCard before I can start making calls.

dim objCac as new dmdccaclib.CommonAccessCard  
ObjCac.ConnectToCard(strReaderName, len(strReaderName)
ObjCac.LogonToCard(string, len(string))
   strName = ObjCac.getName()....[many calls like this to get individual data]
ObjCac.DsconnectFromCard

How would I declare the logontocard with the dmdccaclib.commonaccesscard object?

All the calls work fine except the LogonToCard, where the PIN is provided to unlock the personal data side of the chip.  I have an email to the DoD CAC lab folks, but since my main contact is gone, I doubt I'll get much assistance.  I'll keep EE posted.  If they do provide a solution, I will add it to this thread.
Given that it's a COM DLL I'm not sure you can declare an imported method. If you were to try it you would use:

<DllImport("TheDLL.dll")> _
Public Shared Function NameOfFunctionInDLL(arguments...) As ReturnType
End Function

If the DLL function doesn't return anything, declare it as a Sub instead of a Function.
Umm, Isn't that what DECLARE is for? Maybe I am wrong but I seem to remember having no problem calling COM methods this way.
First Link Explains the DECLARE statement usage in the 1.1 framework. Second link is an example from this site on usage as well.
http://msdn.microsoft.com/en-us/library/4zey12w5(VS.71).aspx
http://www.experts-exchange.com/Programming/Languages/.NET/Visual_Basic.NET/Q_21095462.html
 
Declare and DllImportAttribute - which are synonymous - are for referencing exported functions in an unmanaged library dll (i.e. not COM). For example:

<DllImport( "user32.dll" )> _
Public Shared Function DrawText( ByVal hDC as IntPtr, ByVal lpString As String, ByVal nCount as Integer, ByRef lpRect As Rect, ByVal uFormat As DrawTextFlags ) as Integer
End Function

user32.dll is not a COM component (go ahead, try running regsvr32 against it).

The documentation for DllImportAttribute (http://msdn.microsoft.com/en-us/library/system.runtime.interopservices.dllimportattribute.aspx) states:

"You apply this attribute directly to C# and C++ method definitions; however, the Visual Basic compiler emits this attribute when you use the Declare statement."

So the end result is the same whether you use Declare or DllImportAttribute.

For accessing COM methods you use Interop. Importing the DLL and going through P/Invoke is not necessary as .NET already has a COM interface layer built into it. The interop assembly can be created one of two ways, either by running tlbimp (TypeLib Importer) directly against the COMponent's type library and then setting a reference to the generated Interop assembly, or by setting a reference to the COMponent in Visual Studio (which will run tlbimp behind the scenes for you).

COM dlls don't expose methods, they expose interfaces which contain methods. I don't know how you'd even map an external function declaration to something inside a COM component. If you have a working example I'd be interested in seeing it.
Craig you are correct on the statments about COM, a win32 api has no built in type libraries.
Are we sure the original VB 6 code was calling a COM object with this line of code: Long logonToCard (IN BSTR * pin, IN long pinLen) .
I would bet that it wasn't, which would mean that Declare statment would work in VB.Net to call the method directly in the external dll as long as the string is changed to send it back to the logonToCard method as an unmanaged string. This also would eliminate the need for the poster to have to use a separate VB 6.0 object to make the call into the external dll where the logonToCard method is defined.  My understanding as of right now is the posters work around is VB.Net calls VB 6.0 and VB 6.0 calls the External DLL where the logonToCard method is defined.
 I guess we need more info from the poster.



SOLUTION
Avatar of Craig Wagner
Craig Wagner
Flag of United States of America image

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
ASKER CERTIFIED SOLUTION
Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
One question I have is whether or not the logonToCard method in DMDCCALib is expecting a fixed length string?
Gentlemen,  I was off work yesterday, and now its the weekend, still off.  I won't be able to try the suggestion until Monday.  I'll let you know how it goes.

I appreciate all the discussions regarding this topic.  You all are coming up with things I never thought of.  So I am learning quite a bit.  One of my biggest difficulties is that I am the ONLY programmer on my team, so I don't have other programmers to have these discussions with in person (which I miss).

Thanks for the help.
OutofTouch got me on the right track...

Using the link in his last post (http://support.microsoft.com/kb/311338), it got me working with strings and encoding.
It seems I needed to create a Byte array from the strPIN first.  Then convert that byte array to  a string via unicode.

Here is the code that worked:
strPIN = "12345678"
Dim strX as Byte() = System.Text.Encoding.GetEncoding(1252).GetBytes(strPIN)
Dim dataString as string = Encoding.Unicode.GetString(StrX)
Call obCac.LogonToCard(dtatString, strX.length)

I could mess around with this for hours, possibly cleaning up the code.  But it works, and I don't need the VB6 dll that I had to write.
My thanks goes to everyone that responded to my post.
-Dino
OutOfTouch and CraigWagner.  You two spent the most time on this.  The discussions you both provided to me and each other got me on the right track.  I have learned a ton regarding strings in vb.net.  Thank you for your efforts.
Respectfully,
-Dino