0

I'm working on calling a third-party DLL from my Perl project using XS, under Cygwin on Windows using g++. One of the DLL functions takes a struct as an argument and returns its main results in a pointer to a struct. For now I pass in a flat list of 28 integers and populate the first struct. Then I call the function. Then I want to flatten the resulting struct into a list of up to 54 integers.

(This seems like a lot of integers, but the DLL function is quite complex and takes a long time to run, so I think it's worth it. Unless someone has a better idea?)

This is close to working. I can tell that the results are mostly sensible. But there are two bizarre problems.

  1. When I print out the same variables, I get different results depending on whether it's in a 'for' loop or not! I show this below. I've stared at this so long now.

  2. I get "Out of memory" as soon as I get to the first XPUSHs.

Here is the XS code.

#include "EXTERN.h"
#include "perl.h"
#include "XSUB.h"
#include "ppport.h"
#include "dll.h"

MODULE = Bridge::Solver::DDS_IF PACKAGE = Bridge::Solver::DDS_IF

PROTOTYPES: ENABLE

void
SolveBoard(inlist)
SV * inlist
INIT:
  struct deal dl;
  struct futureTricks fut;
  int    target, solutions, mode, thrId;
  int    i, j, ret;
  if ((! SvROK(inlist)) ||
    (SvTYPE(SvRV(inlist)) != SVt_PVAV) ||
    av_len((AV *) SvRV(inlist)) != 27)
  {
    XSRETURN_UNDEF;
  }
  printf("New INIT OK\n");
PPCODE:
  dl.trump =               SvIV(*av_fetch((AV *)SvRV(inlist),  0, 0));
  dl.first =               SvIV(*av_fetch((AV *)SvRV(inlist),  1, 0));
  dl.currentTrickSuit[0] = SvIV(*av_fetch((AV *)SvRV(inlist),  2, 0));
  dl.currentTrickSuit[1] = SvIV(*av_fetch((AV *)SvRV(inlist),  3, 0));
  dl.currentTrickSuit[2] = SvIV(*av_fetch((AV *)SvRV(inlist),  4, 0));
  dl.currentTrickRank[0] = SvIV(*av_fetch((AV *)SvRV(inlist),  5, 0));
  dl.currentTrickRank[1] = SvIV(*av_fetch((AV *)SvRV(inlist),  6, 0));
  dl.currentTrickRank[2] = SvIV(*av_fetch((AV *)SvRV(inlist),  7, 0));
  dl.remainCards[0][0]   = SvUV(*av_fetch((AV *)SvRV(inlist),  8, 0));
  dl.remainCards[0][1]   = SvUV(*av_fetch((AV *)SvRV(inlist),  9, 0));
  dl.remainCards[0][2]   = SvUV(*av_fetch((AV *)SvRV(inlist), 10, 0));
  dl.remainCards[0][3]   = SvUV(*av_fetch((AV *)SvRV(inlist), 11, 0));
  dl.remainCards[1][0]   = SvUV(*av_fetch((AV *)SvRV(inlist), 12, 0));
  dl.remainCards[1][1]   = SvUV(*av_fetch((AV *)SvRV(inlist), 13, 0));
  dl.remainCards[1][2]   = SvUV(*av_fetch((AV *)SvRV(inlist), 14, 0));
  dl.remainCards[1][3]   = SvUV(*av_fetch((AV *)SvRV(inlist), 15, 0));
  dl.remainCards[2][0]   = SvUV(*av_fetch((AV *)SvRV(inlist), 16, 0));
  dl.remainCards[2][1]   = SvUV(*av_fetch((AV *)SvRV(inlist), 17, 0));
  dl.remainCards[2][2]   = SvUV(*av_fetch((AV *)SvRV(inlist), 18, 0));
  dl.remainCards[2][3]   = SvUV(*av_fetch((AV *)SvRV(inlist), 19, 0));
  dl.remainCards[3][0]   = SvUV(*av_fetch((AV *)SvRV(inlist), 20, 0));
  dl.remainCards[3][1]   = SvUV(*av_fetch((AV *)SvRV(inlist), 21, 0));
  dl.remainCards[3][2]   = SvUV(*av_fetch((AV *)SvRV(inlist), 22, 0));
  dl.remainCards[3][3]   = SvUV(*av_fetch((AV *)SvRV(inlist), 23, 0));
  target                 = SvIV(*av_fetch((AV *)SvRV(inlist), 24, 0));
  solutions              = SvIV(*av_fetch((AV *)SvRV(inlist), 25, 0));
  mode                   = SvIV(*av_fetch((AV *)SvRV(inlist), 26, 0));
  thrId                  = SvIV(*av_fetch((AV *)SvRV(inlist), 27, 0));

  ret = SolveBoard(dl, target, solutions, mode, &fut, thrId); 
  printf("Return code %d\n", ret);
  printf("Nodes %d\n", fut.nodes);
  printf("Cards %d\n", fut.cards);

  printf("%6s  %12s  %12s  %12s  %12s\n",
    "", "suit", "rank", "equals", "score");
  printf("%6d  %12d  %12d  %12d  %12d\n\n",
    0, fut.suit[0], fut.rank[0], fut.equals[0], fut.score[0]);

  for (i = 0; i < 13; i++)
  {
    printf("%6d  %12d  %12d  %12d  %12d\n",
      i, fut.suit[i], fut.rank[i], fut.equals[i], fut.score[i]);
  }

  printf("\n%6d  %12d  %12d  %12d  %12d\n\n",
    0, fut.suit[0], fut.rank[0], fut.equals[0], fut.score[0]);

  printf("Trying to push nodes\n");
  XPUSHs(sv_2mortal(newSViv(fut.nodes)));

  printf("Trying to push cards\n");
  XPUSHs(sv_2mortal(newSViv(fut.cards)));

  printf("Trying to loop\n");
  for (i = 0; i <= 12; i++)
  {
    XPUSHs(sv_2mortal(newSViv(fut.suit  [i])));
    XPUSHs(sv_2mortal(newSViv(fut.rank  [i])));
    XPUSHs(sv_2mortal(newSViv(fut.equals[i])));
    XPUSHs(sv_2mortal(newSViv(fut.score [i])));
  }

  printf("Done looping\n");

Here is the relevant part of the DLL header file.

struct futureTricks 
{
  int nodes;
  int cards;
  int suit[13];
  int rank[13];
  int equals[13];
  int score[13];
};

struct deal 
{
  int trump;
  int first;
  int currentTrickSuit[3];
  int currentTrickRank[3];
  unsigned int remainCards[4][4];
};


extern "C" int SolveBoard(
  struct deal dl, 
  int target, 
  int solutions, 
  int mode, 
  struct futureTricks *futp, 
  int threadIndex);

And here is the output. The return code is OK. The nodes and cards are not. If you squint, you might notice that 0 and 768 also occur within the output table, so maybe there's some kind of offset going on.

The first bizarre thing is that the two '0' lines before and after the main table are different from the '0' line in the main table. The data in the main table is as expected, though, including the garbage in lines 10-12.

The second problem is that XPUSHs doesn't do as intended.

New INIT OK
Return code 1
Nodes 0
Cards 768
                suit          rank        equals         score
     0             0             2   -2147319000   -2147296756

     0             2             2             0             2
     1             2             6             0             2
     2             2            10           768             2
     3             2            13             0             2
     4             3            14             0             2
     5             0             6             0             1
     6             0            10           512             1
     7             0            13             0             1
     8             3             4             0             0
     9             3            11             0             0
    10    1773292640   -2147056120             4   -2147319000
    11    1772354411             0   -2146989552   -2146837752
    12          8192            35       2665016   -2147319000

     0             0             2   -2147319000   -2147296756

Trying to push nodes
Out of memory!
  • I can't say I've solved it, but the call to the external DLL function ret = SolveBoard(dl, target, solutions, mode, &fut, thrId); seems to cause the problem. dl and fut are both structs, and SolveBoard wants to modify fut. After the call, dl as well as the integers target, solutions etc. (which are passed by value!) have changed. So I guess it must be some interaction between the Perl stack and the call stack for the DLL. I do know that the DLL in itself is healthy, as it is used by many users. Is there something special about letting the DLL modify args, or modifying a struct? – user3139990 Jun 02 '14 at 19:31
  • it doesn't make sense that dl changed - you are passing it by value. I'd say that SolveBoard is corrupting memory. are you sure you give it the right numbers? – Shmuel Fomberg Jun 03 '14 at 00:55

2 Answers2

1

It was indeed a problem with the stack.

The supplied dll.h tested _WIN32 and #define'd STDCALL to __stdcall under _WIN32, otherwise to empty.

g++ under Cygwin does not emit _WIN32, so I guess the calling convention defaulted to __cdecl.

Manually defining _WIN32 created lots of other errors, but instead I added to dll.h a test for \__CYGWIN__, which the compiler does emit, and gave it to the author for his next release.

A very frustrating error to find, so I hope this might help somebody else in the future. You never know...

Xantium
  • 11,201
  • 10
  • 62
  • 89
0

with the offset problem, there may be because Perl messes pretty bad with C variable definitions.

including dll.h before all others will probably solve that.

Shmuel Fomberg
  • 546
  • 2
  • 11
  • Very nice thought, and I would never have thought of that... But: It doesn't change anything in my case. – user3139990 Jun 02 '14 at 10:41
  • Could this have something to do with stdcall vs cdecl? And if so, how do I debug it? I hardly know what those words mean... – user3139990 Jun 02 '14 at 20:46