Link to home
Start Free TrialLog in
Avatar of sirwin
sirwin

asked on

Pass VB structure to C DLL

I am receiving this error "run-time error 49" bad DLL calling convention.

I am calling a  DLL writen in C.  The DLL expects a LONG and then a structure with 4 ints and one char *.  

In VB:
I have declared the function as follows;
Declare Function dllname Lib "api.dll" Alias "_dllname @24" (ByVal val1 As Long, ByVal options As Options) As Integer

I have declared the structure as follows;
Type Options
    var1                       As Integer
    var2                       As Integer
    var3                       As Integer
    var4                       As Long
    var5                       As Integer
End Type

With the string data in var4, I convert the  UNICODE string to ANSI.  To do this, I create an array of byte characters.
The string convert function (StrConv) will do this.  To get the pointer to a memory address in VB, I use an undocumented function in VB called
(VarPtr) that returns the memory address of the pointer.

I then set this to the Long variable in the var4 in the structure.

When I call the function in the C DLL, I get the above mentioned error. Any thoughts as to why?

SI
Avatar of clifABB
clifABB

I assume you're using Win95 or WinNT.

The problem is that, to C++, an integer is four bytes and a long is eight bytes.

While you can pass a long where C expects an integer, you need to pass a structure of two longs where C expects a long:

Type C_Long
  Low  As Long
  High As Long
End Type
Avatar of sirwin

ASKER

I don't follow this logic.  C should be expecting LONG holding the memory address of the PTR.  (Souldn't it).  How do I declare the variable in the structure?  what do I add to the second LONG variable that has been added?
When you declare the C_Long structure, the two four byte longs are contiguous allowing C to see it as an eight byte memory location.

Here is what your declares should look like:
Type C_Long
  Low  As Long
  High As Long
End Type

Type Options
    var1                       As Long
    var2                       As Long
    var3                       As Long
    var4                       As C_Long
    var5                       As Long
End Type
Declare Function dllname Lib "api.dll" Alias "_dllname @24" (ByVal val1 As C_Long, ByVal options As Options) As Long
Avatar of sirwin

ASKER

This still does not work.  One problem that I noticed is that I have to pass user defined Type ByRef and not ByVal.  Therfore, this should read ByRef options As Options.  I have passed val1 (first vaiable outside the structure) as Long to other C DLLs without a problem so I don't know why this would need to change.

The C DLL is expecting;
val1 as int
var1 as int
var2 as int
var3 as int
var4 as char*
var5 as int

All var# are in a structure.
Are you sure about a long being 64 bit, ie 2 longs, I thought that Win32 brought in long int = int, 64 bit ints are declared as _int64.

For this sort of thing I generally copy the numbers into a string in VB and pass the string.
Avatar of sirwin

ASKER

But if you pass as a string, the C API is looking for a char* and will still need the memory location (prt) of the string.  This would be invalid type.
You don't pass the string, you pass the pointer, it works, I have done it, usually the problem is in returning a string, you have to use space(x) to allocate the space first.

Also the struct declared in C++ may be optimized so each element falls on a 4 byte boundary, use pragma pack(1) to stop this for the structure.
sirwin,
  You have no need to use Varptr or Strptr to pass the string. Most of the time VB will convert for you.Try declare the type Option as:

Type Option
  Var1 as Long
  Var2 as Long
  Var3 as Long
  Var4 as String
  Var5 as Long
End Type

  When you assign any string to Var4, add a vbNullChar to the end of the string(since C expect a null terminated string). eg.
  Dim op as Option
  op.Var4 = "Testing" + vbNullChar

  Then pass it to the DLL function.
  Hope this help.

 


 Another question, do you write the C function in DLL? Since your error appear as "DLL calling convention", the C function must be exported with standard call ( put a "WINAPI" in front of the function, other calling convention will cause the error).
>The C DLL is expecting;
      var1 as int
      var2 as int
      var3 as int
      var4 as char*
      var5 as int <

TYPE C_TYPE
  var1 AS LONG
  var2 AS LONG
  var3 AS LONG
  var4 AS LONG   '<-- pointer to string data
  var5 AS LONG
END TYPE

var4 = AddressOf(AnsiString)   '<-- Use AddressOf in VB5 to get the address of the string data

--Dave
Avatar of sirwin

ASKER

Dave,

The "AddressOf" Operator will give you a memory address of a procedure not a string.  This is used in call backs.

sirwin
You would need to use StrPtr() (if VB supports it) or a third-part DLL/Control to get the addres of the string data.  If VB doesn't support StrPtr(), let me know and I'll create a small DLL for you that will return the address of any variable passed to it.

--Dave
sirwin,
  Do you write the C DLL? Could you show your declaration of the export function _dllname@24 in your C code?
I don't program in C.  I was actually going to create the DLL in PowerBASIC and email it to you if you wanted one that returned a data pointer.

--Dave
Avatar of sirwin

ASKER

EntLog EXPORT dllname(const val1 ctx,  const OPTIONS Options).

I think I know what is wrong.  This should be;

EntLog EXPORT dllname(const val1 ctx,  const OPTIONS* Options)

Since VB can only pass UDT ByRef, the C DLL must receive a ptr.  I will have to test but it should work.  The only immediate problem is that I cannot make immediate changes to the C code.

Options:

1.  Is there anything in VB that I can do to work around this?
2.  I can write another C DLL that receives this ByRef and then pass to the ByVal C DLL.

Any thoughts?


1. Bad DLL calling convention concerned about how the dll exported function being called. If I am not wrong, in standard calling convention (__stdcall or WINAPI in front of function name), should be the function clear the parameter in stack before return to caller. So, in this case, I don't think you could do anything in VB to workaround this.

2. I am not sure about this. How your another DLL recognize UDT Option? Did the type OPTIONS stay in global namespaces? It should be pass by pointer even in different C DLL.

If change the passing UDT to pass by pointer still not worked, check you definition of EXPORT or maybe put EXPORT Entlog....

Hope this info help. :)
ASKER CERTIFIED SOLUTION
Avatar of Darlof
Darlof

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
Avatar of sirwin

ASKER

Excellent suggestions.  Most if not all helped me get this working.  
I declared the UDT as Long, long, Long, String, Long. The other problem I had was that the C function was expecting the UDT ByVal.  I had to write another C DLL to accept it ByRef and then pass it to the exisiting C DLL function ByVal.

thanks for the help.
Steve