Solved

VB6 to VB.NET, String handling changes causes failure

Posted on 2009-04-09
23
869 Views
Last Modified: 2013-11-07
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.
0
Comment
Question by:BrotherEarth
  • 9
  • 5
  • 5
  • +2
23 Comments
 
LVL 21

Expert Comment

by:Craig Wagner
ID: 24107786
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.
0
 

Author Comment

by:BrotherEarth
ID: 24109210
Thank you for the quick response.  I am using the COM Interop.
0
 
LVL 85

Expert Comment

by:Mike Tomlinson
ID: 24109398
*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)
0
 

Author Comment

by:BrotherEarth
ID: 24109553
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'.

0
 
LVL 85

Expert Comment

by:Mike Tomlinson
ID: 24109593
Alrighty.  =\

It works for some APIs...just depends how they are written I suppose...
0
 
LVL 21

Expert Comment

by:Craig Wagner
ID: 24109877
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.
0
 

Author Comment

by:BrotherEarth
ID: 24109967
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.
0
 
LVL 16

Expert Comment

by:HooKooDooKu
ID: 24110884
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
0
 

Author Comment

by:BrotherEarth
ID: 24111424
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?
0
 
LVL 16

Expert Comment

by:HooKooDooKu
ID: 24112070
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.
0
 

Author Comment

by:BrotherEarth
ID: 24112179
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
0
How your wiki can always stay up-to-date

Quip doubles as a “living” wiki and a project management tool that evolves with your organization. As you finish projects in Quip, the work remains, easily accessible to all team members, new and old.
- Increase transparency
- Onboard new hires faster
- Access from mobile/offline

 
LVL 3

Expert Comment

by:OutOfTouch
ID: 24113229
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

0
 

Author Comment

by:BrotherEarth
ID: 24114574
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.
0
 
LVL 21

Expert Comment

by:Craig Wagner
ID: 24115236
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.
0
 
LVL 3

Expert Comment

by:OutOfTouch
ID: 24116528
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
 
0
 
LVL 21

Expert Comment

by:Craig Wagner
ID: 24119969
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.
0
 
LVL 3

Expert Comment

by:OutOfTouch
ID: 24120995
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.



0
 
LVL 21

Assisted Solution

by:Craig Wagner
Craig Wagner earned 200 total points
ID: 24121044
The original call was definitely COM. The error message posted in the original question was:

{Microsoft.VisualBasic.ErrObject}
    Description: "Exception from HRESULT: 0xFFFF8ACC"
    Erl: 0
    HelpContext: 0
    HelpFile: ""
    LastDllError: 0
    Number: -30004
    Source: "Interop.DMDCCACLib"

The Source property indicates it was indeed COM ("Interop".DMDCCACLib). If it wasn't a COM component there's no way Visual Studio would have been able to generate an Interop assembly for it.
0
 
LVL 3

Accepted Solution

by:
OutOfTouch earned 300 total points
ID: 24121113
OK my fault completely, here is the solution I think and it was hinted at by you early on. At least this is worth trying anyways. Again sorry.
http://support.microsoft.com/kb/311338
 
0
 
LVL 3

Expert Comment

by:OutOfTouch
ID: 24121177
One question I have is whether or not the logonToCard method in DMDCCALib is expecting a fixed length string?
0
 

Author Comment

by:BrotherEarth
ID: 24121651
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.
0
 

Author Comment

by:BrotherEarth
ID: 24152355
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
0
 

Author Closing Comment

by:BrotherEarth
ID: 31568537
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
0

Featured Post

How to improve team productivity

Quip adds documents, spreadsheets, and tasklists to your Slack experience
- Elevate ideas to Quip docs
- Share Quip docs in Slack
- Get notified of changes to your docs
- Available on iOS/Android/Desktop/Web
- Online/Offline

Join & Write a Comment

In my previous two articles we discussed Binary Serialization (http://www.experts-exchange.com/A_4362.html) and XML Serialization (http://www.experts-exchange.com/A_4425.html). In this article we will try to know more about SOAP (Simple Object Acces…
If you need to start windows update installation remotely or as a scheduled task you will find this very helpful.
In this tutorial you'll learn about bandwidth monitoring with flows and packet sniffing with our network monitoring solution PRTG Network Monitor (https://www.paessler.com/prtg). If you're interested in additional methods for monitoring bandwidt…
This demo shows you how to set up the containerized NetScaler CPX with NetScaler Management and Analytics System in a non-routable Mesos/Marathon environment for use with Micro-Services applications.

746 members asked questions and received personalized solutions in the past 7 days.

Join the community of 500,000 technology professionals and ask your questions.

Join & Ask a Question

Need Help in Real-Time?

Connect with top rated Experts

12 Experts available now in Live!

Get 1:1 Help Now