First, what I am trying to do...

I like PHP, and am trying to use it for some script based testing. In order to do this, I need access to 3rd party dll's, specifically the National Instruments GPIB dll's.

And some details about my environment,
I have WinXP, Apache 2.0.49, and PHP 5.1.1. The Apache/PHP install work fine, and are used for other applications.

What I think I have learned...
[INDENT]w32api* is no longer supported. I have found copies of php_w32api.dll, which won't load because it's compiled for the wrong version of PHP.

pecl ffi is no longer supported, (maybe only for PHP 5.0.5?). It again won't load because it's complied for the wrong version of PHP.[/INDENT]

So I am using Dynacall (or dynawrap, or dynwrap.dll). It works for parts of the code.

I have gotten several pieces of code working, but reading doesn't.

The send code is (which works):

//definition
$GPIB = new COM("DynamicWrapper");
//extern void __stdcall Send(int boardID, Addr4882_t addr, PVOID databuf, long datacnt, int eotMode);
$GPIB->Register("gpib-32.DLL", "Send", "i=uuslu", "f=s");

//wrapper function
function GPIBSend($ud,$addr,$buf,$term)
{
    global $GPIB;
    $cnt=strlen($buf);
    $GPIB->Send($ud, $addr, $buf, $cnt, $term);
    if($GPIB->ThreadIbsta() & ERR)
        GPIBERR("GPIBSend ERROR");
}

//actual call
$a="FREQ 5000";
GPIBSend(0,10,$a,NLend);

The receive function doesn't work, it executes, the NI Spy program shows it executing, but it returns the same string I gave it. However, I believe the problem is writing the data into the supplied buffer, somehow I am not getting the right pointer supplied to the function.

The receive code is (which doesn't work):

//definition
$GPIB = new COM("DynamicWrapper");
//extern void __stdcall Receive(int boardID, Addr4882_t addr, PVOID buffer, long cnt, int Termination);
$GPIB->Register("gpib-32.DLL", "Receive", "i=uuslu", "f=s");

//wrapper function
function GPIBReceive($ud,$addr,&$buf,$term)
{
    global $GPIB;
    $cnt=strlen($buf);
//	settype($buf,"string");
    $GPIB->Receive($ud, $addr, $buf, $cnt, $term);
    if($GPIB->ThreadIbsta() & ERR)
        GPIBERR("GPIBReceive ERROR");
}

//actual call
$a=str_repeat(" ",100);
GPIBReceive(0,10,$a,STOPend);

I have tried with and without the "&" sign in the function declaration, with and without the settype call, and everything else I can think of.

I tried what I thought would be an easier attempt, again with no luck, this one actually crashes PHP.

//declaration
$com = new COM("DynamicWrapper");
//BOOL WINAPI GetUserName(__out LPTSTR lpBuffer, __inout  LPDWORD lpnSize);
$com->Register("Advapi32.dll","GetUserName","i=sl","f=s","r=l");

//and call
$a=str_repeat(" ",100);
settype($a,"string");
$cnt=strlen($a);
$com->GetUserName($a,$cnt);
echo $a . "\r\n";

Does anybody have an idea?

Thanks, paulcsf

    2 months later

    Hello,

    I met the same problem and decided to submit this to Gilles LAURENT himself (the developer of the DLL) to find some help.

    I forward his answer to our problem :

    Bonjour jérome,

    Le problème provient du format des chaînes de caractères. VBScript s'appuie sur
    le format Variant (VT_BSTR) et PHP sur celui du language C (char*). Ci-dessous
    le portage de l'exemple GetProfileSection en PHP. Vous noterez l'utilisation de
    la classe VARIANT permettant d'obtenir un buffer de type VT_BSTR compatible avec
    les méthodes de manipulation de buffer disponibles avec DynaWrap :

    <?php
    $oDyn= new COM("DynamicWrapper");
    $oDyn->Register("Kernel32.dll", "GetProfileSection", "r=l", "i=sll");
    $sReturnedString=new VARIANT(sprintf("%1024s", " "));
    $dwBSTRAddr=$oDyn->GetBSTRAddr($sReturnedString);
    printf("%ld\n",$oDyn->GetProfileSection("Mail", $dwBSTRAddr, 1024));
    $nOffset=0;
    do {
     $sKeyVal=$oDyn->GetMemInBSTRAddr($dwBSTRAddr, $nOffset, 0);
     if ($sKeyVal != "") {
       print "$sKeyVal\n";
       $nOffset+=strlen($sKeyVal)+1;
     }
    }
    while ($sKeyVal != "");
    ?>
    

    --
    Gilles LAURENT
    MVP Windows Server - Admin Frameworks
    http://glsft.free.fr

    I hope this can be helpful to you.

    Many many thanks to Gilles LAURENT for his answer !!

    Best regards,
    Jérôme

      8 days later

      Thanks Jérôme. And thanks to Gilles, and everybody who has worked on Dynawrap.

      I had also found and corresponded with Gilles LAURENT, though I hadn't gotten around to trying the solution.

      So, a little bit of summary...

      The first source of dynawrap (the COM wrapper for dynacall) comes from:
      http://freenet-homepage.de/gborn/WSHBazaar/WSHDynaCall.htm

      Gilles updated version comes from (1.0.0.1):
      http://glsft.free.fr/index.php?option=content&task=view&id=47 (it's in French, but translates acceptably)

      The solution that Gilles has created works, and here is some test code all for the NI GPIB libraries. This has been tested on WinXP, PHP 5.1.1, Dynawrap 1.0.0.1, NI gpib-32.dll 02.04.00.3055, NI GPIB PCI card, and HP/Agilent 33220A.

      gpib_test.php

      <?php
      require_once("fn_GPIB.php");
      
      //GPIB test code
      SendIFC(0);
      DevClear(0,10);
      $a="FREQ 5000";
      GPIBSend(0,10,$a,NLend);
      
      usleep(100000);
      $a="FREQ 25000";
      GPIBSend(0,10,$a,NLend);
      
      $a="*IDN?";
      GPIBSend(0,10,$a,NLend);
      usleep(100000);
      $a=str_repeat(" ",100);      //allocate enough space
      GPIBReceive(0,10,$a,STOPend);
      echo $a;
      ?>
      

      Hope it's useful to others.

        fn_GPIB.php

        <?php
        /*
        0000  20080805  PH  Modified from NI decl-32.h
        0001  20080905  PH  Attempted by reference string
        0002  20081008  PH  Added code from Gilles LAURENT for receive.
        0003  20081008  PH  Cleanup
              20081009  PH  Cut down for posting
        
         *                     Win32 include file
         *          for accessing the 32-bit GPIB DLL (gpib-32.dll)
         *
         *
         *         Contains user variables (ibsta, iberr, ibcnt, ibcntl),
         *         function prototypes and useful defined constants for
         *         calling NI-488 and NI-488.2 routines from a Microsoft
         *         C/C++ Win32 application.
         *
         *
         *            Copyright 1998 National Instruments Corporation
         *
        */
        
        require_once("cst_GPIB.php");
        
        //declare PHP GPIB COM entity
        $GPIB = new COM("DynamicWrapper");
        
        global $GPIBRemoteArray;
        
        //************************************************************************
        //  Functions to access Thread-Specific copies of the GPIB global vars 
        
        //extern int  __stdcall ThreadIbsta (void);
        $GPIB->Register("gpib-32.dll","ThreadIbsta","f=s","r=u");
        //extern int  __stdcall ThreadIberr (void);
        $GPIB->Register("gpib-32.dll","ThreadIberr","f=s","r=u");
        //extern int  __stdcall ThreadIbcnt (void);
        $GPIB->Register("gpib-32.dll","ThreadIbcnt","f=s","r=u");
        //extern long __stdcall ThreadIbcntl (void);
        $GPIB->Register("gpib-32.dll","ThreadIbcntl","f=s","r=l");
        
        //************************************************************************
        //  NI-488.2 Function Prototypes  
        
        //extern void __stdcall DevClear      (int boardID, Addr4882_t addr);
        $GPIB->Register("gpib-32.dll","DevClear","i=uu","f=s");
        
        //extern void __stdcall Receive       (int boardID, Addr4882_t addr, PVOID buffer, long cnt, int Termination);
        $GPIB->Register("gpib-32.DLL", "Receive", "i=uullu", "f=s");
        
        //extern void __stdcall Send          (int boardID, Addr4882_t addr, PVOID databuf, long datacnt, int eotMode);
        $GPIB->Register("gpib-32.DLL", "Send", "i=uuslu", "f=s");
        
        //extern void __stdcall SendIFC       (int boardID);
        $GPIB->Register("gpib-32.DLL", "SendIFC", "i=u", "f=s");
        
        
        function AddIbcnt()
        {
        	global $GPIB;
        	$len=$GPIB->ThreadIbcnt;
            echo "ibcnt = 0x" . dechex($len) . "\r\n";
        	return $len;
        }
        
        function AddIberr()
        {
        	global $GPIB;
            if($GPIB->ThreadIbsta() & ERR)
        	{
        		$iberr=$GPIB->ThreadIberr();
        		echo "iberr = &H" . dechex($iberr) . "\r\n";
                if($iberr == EDVR) echo "iberr = EDVR <DOS Error>\r\n";
                if($iberr == ECIC) echo "iberr = ECIC <Not CIC>\r\n";
                if($iberr == ENOL) echo "iberr = ENOL <No Listener>\r\n";
                if($iberr == EADR) echo "iberr = EADR <Address Error>\r\n";
                if($iberr == EARG) echo "iberr = EARG <Invalid argument>\r\n";
                if($iberr == ESAC) echo "iberr = ESAC <Not Sys Ctrlr>\r\n";
                if($iberr == EABO) echo "iberr = EABO <Op. aborted>\r\n";
                if($iberr == ENEB) echo "iberr = ENEB <No GPIB board>\r\n";
                if($iberr == EOIP) echo "iberr = EOIP <Async I/O in prg>\r\n";
                if($iberr == ECAP) echo "iberr = ECAP <No capability>\r\n";
                if($iberr == EFSO) echo "iberr = EFSO <File sys. error>\r\n";
                if($iberr == EBUS) echo "iberr = EBUS <Command error>\r\n";
                if($iberr == ESTB) echo "iberr = ESTB <Status byte lost>\r\n";
                if($iberr == ESRQ) echo "iberr = ESRQ <SRQ stuck high>\r\n";
                if($iberr == ETAB) echo "iberr = ETAB <Table overflow>\r\n";
        	}
        	else
        	{
        		echo "iberr = " . $GPIB->ThreadIberr() . "\r\n";
        	}
        }
        
        function AddIbsta()
        {
        	global $GPIB;
            $sta="";
        	$ibsta=$GPIB->ThreadIbsta();
            $sta = "ibsta = &H" . dechex($ibsta) . " <";
            if($ibsta & ERR) $sta = $sta . " ERR";
            if($ibsta & TIMO) $sta = $sta . " TIMO";
            if($ibsta & END) $sta = $sta . " END";
            if($ibsta & SRQI) $sta = $sta . " SRQI";
            if($ibsta & RQS) $sta = $sta . " RQS";
            if($ibsta & CMPL) $sta = $sta . " CMPL";
            if($ibsta & LOK) $sta = $sta . " LOK";
            if($ibsta & REM) $sta = $sta . " REM";
            if($ibsta & CIC) $sta = $sta . " CIC";
            if($ibsta & ATN) $sta = $sta . " ATN";
            if($ibsta & TACS) $sta = $sta . " TACS";
            if($ibsta & LACS) $sta = $sta . " LACS";
            if($ibsta & DTAS) $sta = $sta . " DTAS";
            if($ibsta & DCAS) $sta = $sta . " DCAS";
            $sta = $sta . ">";
            return $sta;
        }
        
        function GpibErr($msg)
        {
            echo "GPIB ERROR: " . $msg . "\r\n";
            echo AddIbsta() . "\r\n";
            AddIberr();
            AddIbcnt();
        }
        
        function SendIFC($ud)
        {
        	global $GPIB;
            $GPIB->SendIFC(0);
            if($GPIB->ThreadIbsta() & ERR)
        		GPIBERR("SendIFC ERROR");
        }
        
        function DevClear($ud,$addr)
        {
        	global $GPIB,$GPIBRemoteArray;
            $GPIB->DevClear($ud,$addr);
        
        //add to list for GPIBRemote and GPIBLocal calls
        for($i=0;$i<32;$i++)
        {
        	if($GPIBRemoteArray[$i]==NOADDR)
        		$GPIBRemoteArray[$i]=$addr;
        	else if($GPIBRemoteArray[$i]==$addr)
        		break;
        }
        if($GPIB->ThreadIbsta() & ERR)
        	GPIBERR("DevClear ERROR");
        }
        
        function GPIBSend($ud,$addr,$buf,$term)
        {
        	global $GPIB;
        	$cnt=strlen($buf);
            $GPIB->Send($ud, $addr, $buf, $cnt, $term);
            if($GPIB->ThreadIbsta() & ERR)
        		GPIBERR("GPIBSend ERROR");
        }
        
        function GPIBReceive($ud,$addr,&$buf,$term)
        {
        	global $GPIB;
        	$blen=strlen($buf)+1;
        	$sReturnedString=new VARIANT(sprintf("%" . $blen . "s", " "));
        	$dwBSTRAddr=$GPIB->GetBSTRAddr($sReturnedString);
        	if($GPIB->Receive($ud, $addr, $dwBSTRAddr, $blen, $term)==0)
        	{
        		$buf=$GPIB->GetMemInBSTRAddr($dwBSTRAddr,0,0);
        	}
        	else
        	{
        		echo "GPIBReceive: read failed.";
        	}
            if($GPIB->ThreadIbsta() & ERR)
        		GPIBERR("GPIBReceive ERROR");
        	return $buf;
        }
        ?>
        

          cst_GPIB.php

          <?php
          /*
                20081009  PH  Cut down for posting
          */
          
          //*************************************************************************
          //    HANDY CONSTANTS FOR USE BY APPLICATION PROGRAMS ...                  
          //************************************************************************* define("UNL",0x3f); // GPIB unlisten command
          define("UNT",0x5f); // GPIB untalk command
          define("GTL",0x01); // GPIB go to local
          define("SDC",0x04); // GPIB selected device clear
          define("PPC",0x05); // GPIB parallel poll configure
          define("GET",0x08); // GPIB group execute trigger
          define("TCT",0x09); // GPIB take control
          define("LLO",0x11); // GPIB local lock out
          define("DCL",0x14); // GPIB device clear
          define("PPU",0x15); // GPIB parallel poll unconfigure
          define("SPE",0x18); // GPIB serial poll enable
          define("SPD",0x19); // GPIB serial poll disable
          define("PPE",0x60); // GPIB parallel poll enable
          define("PPD",0x70); // GPIB parallel poll disable // // GPIB status bit vector :
          // global variable ibsta and wait mask define("ERR",(1<<15)); // Error detected
          define("TIMO",(1<<14)); // Timeout
          define("END",(1<<13)); // EOI or EOS detected
          define("SRQI",(1<<12)); // SRQ detected by CIC
          define("RQS",(1<<11)); // Device needs service
          define("CMPL",(1<<8)); // I/O completed
          define("LOK",(1<<7)); // Local lockout state
          define("REM",(1<<6)); // Remote state
          define("CIC",(1<<5)); // Controller-in-Charge
          define("ATN",(1<<4)); // Attention asserted
          define("TACS",(1<<3)); // Talker active
          define("LACS",(1<<2)); // Listener active
          define("DTAS",(1<<1)); // Device trigger state
          define("DCAS",(1<<0)); // Device clear state // Error messages returned in global variable iberr define("EDVR",0); // System error
          define("ECIC",1); // Function requires GPIB board to be CIC define("ENOL",2); // Write function detected no Listeners
          define("EADR",3); // Interface board not addressed correctly define("EARG",4); // Invalid argument to function call
          define("ESAC",5); // Function requires GPIB board to be SAC define("EABO",6); // I/O operation aborted
          define("ENEB",7); // Non-existent interface board
          define("EDMA",8); // Error performing DMA
          define("EOIP",10); // I/O operation started before previous
          // operation completed
          define("ECAP",11); // No capability for intended operation
          define("EFSO",12); // File system operation error
          define("EBUS",14); // Command error during device call
          define("ESTB",15); // Serial poll status byte lost
          define("ESRQ",16); // SRQ remains asserted
          define("ETAB",20); // The return buffer is full.
          define("ELCK",21); // Address or board is locked. // EOS mode bits define("BIN", (1<<12)); // Eight bit compare
          define("XEOS", (1<<11)); // Send END with EOS byte
          define("REOS", (1<<10)); // Terminate read on EOS // Timeout values and meanings define("TNONE", 0); // Infinite timeout (disabled)
          define("T10us", 1); // Timeout of 10 us (ideal)
          define("T30us", 2); // Timeout of 30 us (ideal)
          define("T100us", 3); // Timeout of 100 us (ideal)
          define("T300us", 4); // Timeout of 300 us (ideal)
          define("T1ms", 5); // Timeout of 1 ms (ideal)
          define("T3ms", 6); // Timeout of 3 ms (ideal)
          define("T10ms", 7); // Timeout of 10 ms (ideal)
          define("T30ms", 8); // Timeout of 30 ms (ideal)
          define("T100ms", 9); // Timeout of 100 ms (ideal)
          define("T300ms", 10); // Timeout of 300 ms (ideal)
          define("T1s", 11); // Timeout of 1 s (ideal)
          define("T3s", 12); // Timeout of 3 s (ideal)
          define("T10s", 13); // Timeout of 10 s (ideal)
          define("T30s", 14); // Timeout of 30 s (ideal)
          define("T100s", 15); // Timeout of 100 s (ideal)
          define("T300s", 16); // Timeout of 300 s (ideal)
          define("T1000s", 17); // Timeout of 1000 s (ideal) // IBLN Constants
          define("NO_SAD", 0); define("ALL_SAD", -1); // The following constants are used for the second parameter of the // ibconfig function. They are the "option" selection codes. define("IbcPAD", 0x0001); // Primary Address
          define("IbcSAD", 0x0002); // Secondary Address
          define("IbcTMO", 0x0003); // Timeout Value
          define("IbcEOT", 0x0004); // Send EOI with last data byte?
          define("IbcPPC", 0x0005); // Parallel Poll Configure
          define("IbcREADDR", 0x0006); // Repeat Addressing
          define("IbcAUTOPOLL", 0x0007); // Disable Auto Serial Polling
          define("IbcCICPROT", 0x0008); // Use the CIC Protocol?
          define("IbcIRQ", 0x0009); // Use PIO for I/O
          define("IbcSC", 0x000A); // Board is System Controller?
          define("IbcSRE", 0x000B); // Assert SRE on device calls?
          define("IbcEOSrd", 0x000C); // Terminate reads on EOS
          define("IbcEOSwrt", 0x000D); // Send EOI with EOS character
          define("IbcEOScmp", 0x000E); // Use 7 or 8-bit EOS compare
          define("IbcEOSchar", 0x000F); // The EOS character.
          define("IbcPP2", 0x0010); // Use Parallel Poll Mode 2.
          define("IbcTIMING", 0x0011); // NORMAL, HIGH, or VERY_HIGH timing.
          define("IbcDMA", 0x0012); // Use DMA for I/O
          define("IbcReadAdjust", 0x0013); // Swap bytes during an ibrd.
          define("IbcWriteAdjust", 0x014); // Swap bytes during an ibwrt.
          define("IbcSendLLO", 0x0017); // Enable/disable the sending of LLO.
          define("IbcSPollTime", 0x0018); // Set the timeout value for serial polls. define("IbcPPollTime", 0x0019); // Set the parallel poll length period.
          define("IbcEndBitIsNormal", 0x001A); // Remove EOS from END bit of IBSTA.
          define("IbcUnAddr", 0x001B); // Enable/disable device unaddressing.
          define("IbcSignalNumber", 0x001C); // Set UNIX signal number - unsupported define("IbcBlockIfLocked", 0x001D); // Enable/disable blocking for locked boards/devices
          define("IbcHSCableLength", 0x001F); // Length of cable specified for high speed timing. define("IbcIst", 0x0020); // Set the IST bit.
          define("IbcRsv", 0x0021); // Set the RSV byte. // // Constants that can be used (in addition to the ibconfig constants) // when calling the ibask() function. define("IbaPAD", IbcPAD); define("IbaSAD", IbcSAD); define("IbaTMO", IbcTMO); define("IbaEOT", IbcEOT); define("IbaPPC", IbcPPC); define("IbaREADDR", IbcREADDR); define("IbaAUTOPOLL", IbcAUTOPOLL); define("IbaCICPROT", IbcCICPROT); define("IbaIRQ", IbcIRQ); define("IbaSC", IbcSC); define("IbaSRE", IbcSRE); define("IbaEOSrd", IbcEOSrd); define("IbaEOSwrt", IbcEOSwrt); define("IbaEOScmp", IbcEOScmp); define("IbaEOSchar", IbcEOSchar); define("IbaPP2", IbcPP2); define("IbaTIMING", IbcTIMING); define("IbaDMA", IbcDMA); define("IbaReadAdjust", IbcReadAdjust); define("IbaWriteAdjust", IbcWriteAdjust); define("IbaSendLLO", IbcSendLLO); define("IbaSPollTime", IbcSPollTime); define("IbaPPollTime", IbcPPollTime); define("IbaEndBitIsNormal", IbcEndBitIsNormal); define("IbaUnAddr", IbcUnAddr); define("IbaSignalNumber", IbcSignalNumber); define("IbaBlockIfLocked", IbcBlockIfLocked); define("IbaHSCableLength", IbcHSCableLength); define("IbaIst", IbcIst); define("IbaRsv", IbcRsv); define("IbaBNA", 0x0200); // A device's access board. // Values used by the Send 488.2 command. define("NULLend", 0x00); // Do nothing at the end of a transfer. define("NLend", 0x01); // Send NL with EOI after a transfer.
          define("DABend", 0x02); // Send EOI with the last DAB. // Value used by the 488.2 Receive command. define("STOPend", 0x0100); define("NOADDR",0xFFFF); ?>
            a month later

            paulcsf
            Thank you very much!
            Very, very interesting solution but may be do you have any solution for useing visa32.dll instead gpib-32.dll for controling of instrument?
            I think useing of the visa32.dll is more universal and can be used for different instruments and com (serial) ports too.
            And do you have any experience for control instruments through any ActiveX on the web page (by Javascript)?
            It would be fine to write web operator interfaces/applications on the PHP/Javascript instead useing NI TestStand...

              6 months later

              A bug update:

              $sReturnedString=new VARIANT(sprintf("%" . $blen . "s", " "));
              $dwBSTRAddr=$GPIB->GetBSTRAddr($sReturnedString);
              

              doesn't always seem to work properly, building the sprintf string.

              $sReturnedString=new VARIANT(sprintf("%" . (string)($blen) . "s", " "));
              $dwBSTRAddr=$GPIB->GetBSTRAddr($sReturnedString);
              

              does properly create an arbitrary length string. I was not able to edit the original solution, so please make sure to make the change.

              I haven't ever used visa32.dll, though I may have to soon, and I will reply if I do.

                Write a Reply...