8
votes

My Question is simple

VB.dll (VB5.0 I guess) includes these methods

Private Declare Function ffr_device_find Lib ".\ffr_32.dll" () As Boolean
Private Declare Function ffr_data_transceive_ex Lib ".\ffr_32.dll" (ByVal sp_sdata As String, ByVal sp_rdata As String) As Boolean

in C#.... ( .NET 4.5 )

[DllImport("FFR_32.dll", CallingConvention = CallingConvention.Cdecl)]
extern public static Boolean ffr_device_find();

[DllImport("FFR_32.dll", CallingConvention = CallingConvention.Cdecl)]
extern public static void ffr_data_transceive_ex([Out] string sp_sdata, [Out] string sp_rdata);
// FYI, I tried [Out], out, and ref but to no avail.

The first one works great,

but the second one spilt this error.

A call to PInvoke function 'ffr_data_transceive_ex' has unbalanced the stack.
This is likely because the managed PInvoke signature does not match the unmanaged target signature. Check that the calling convention and parameters of the PInvoke signature match the target unmanaged signature.

FYI

This is a working code from VB... ( NOT INNER DLL SOURCES )

Dim st As String
Dim rData As String * 40

st = "4401" & "20202020202020202020202020202020"
Text1.Text = st
Cal_BCC
Call ffr_data_transceive_ex(Text1.Text, rData)
Text2.Text = rData

I don't even understand what Dim rData As String * 40 is about... will it become 0 when rData is 0? and become 40 when rData has 1? ...

What's wrong with my DllImport methods in C#???

2
Are you sure the first parameter of ccr_data_transceive_ex is [Out]? Because I don't see it used as an output parameter in the example you provided.kdojeteri
* 40 in VB makes the string have a fixed preallocated length of 40. In C# try: [Out] StringBuilder sp_rdata where sp_rdata is a new StringBuilder(40), do the same for sp_sdata if it is infact an output, otherwise remove the out. ccr_data_transceive_ex is not void its boolean.Alex K.
@Peping It should use [Out], ref or out because the dll takes the param as ByValhina10531
@AlexK. Yeah good point. But I've tried it before... Both void and boolean ( or bool ) don't work either. It produces the same error.hina10531
@Peping ByVal has a different meaning in VB when applied to strings. Passing strings ByVal to API functions is the way to do it, and the function can change the contents of the string. It is not related to [In] and [Out], these are mere hints for the marshaler to skip copying data.GSerg

2 Answers

7
votes
   [DllImport("FFR_32.dll", CallingConvention = CallingConvention.Cdecl)]

It is not Cdecl. Visual Basic assumes the default, StdCall. You got away with it on the first function because it does not take any arguments. Not on the second since it imbalanced the stack, both the caller and the callee popped the arguments off the stack, forcing the MDA to step in and tell you about the drastic mishap.

Simply remove the property completely so you get the correct default in your C# program as well, CallingConvention.StdCall.

   void ffr_data_transceive_ex([Out] string sp_sdata, [Out] string sp_rdata)

You cannot use string, strings are immutable in .NET. Use StringBuilder instead. Do make sure that its Capacity is large enough to be able to store the received data that the function writes. Guessing too low causes heap corruption, a very nasty bug to troubleshoot.

Also odds that it should be byte[], your question doesn't document the type of the returned data well enough. VB5 did not have a Byte type yet so a fixed string (like String * 40) was the next best choice. Doesn't work in .NET either, not all possible byte values have a corresponding Unicode codepoint. Use StringBuilder only if you know for a fact that the function only returns ASCII codes.

0
votes

I choose the above answer by Hans Passant

But for those who want an exact solution for this case, I will give you some additional information and some code snippets.

First

Like Hans Passant said, CallingConvention should be gotten rid of.

Second

Like Hans Passant said, string shouldn't be passed into the function. More accurately, the first parameter can be passed into as string type. But the second one should be char[] with explicit length.

I tested this and It throws another error when the length is different.

Working code

extern public static void ffr_data_transceive_ex([Out] string sp_sdata, [Out] char[] sp_rdata);

Necessary Ingredient

// To pass the second parameter.
// Because the dll returns 40 length characters, this should be specified as length '40'
char[] ForCardHex = new char[40];

// Command is an already defined protocol format, like "200080581028000001"
// , which can be taken as string.
ffr_data_transceive_ex(Command, ForCardHex);

This works greatly and it returns the expected value on ForCardHex. You should use char[]. I couldn't get byte[] passed into the function.

Thank all of the commentors and the answerer.