i-GetTcpinfo broken in 11.7

Posted by OctavioOlguin on 11-Apr-2017 15:20

Gettings.. just trying 11.7, and it looks great.....

Just one thing....

I use, A LOT, a function from good'ol Jurgen Dikstra (don't ask me why I rmember exactly his name's spell after 10 years) that also is described here http://knowledgebase.progress.com/articles/Article/P5265?q=i-GetTcpInfo&l=en_US&fs=Search&pn=1 .

It now gets an error 13712.   on my development machine.. (I haven't upgraded user base)... So it has been 64 bits last couple of years.

The problem is triggered by 

    /* Ask Win32 for winsock usage */
    RUN WSAStartup (INPUT  257,        /* requested version 1.1 */
        INPUT  GET-POINTER-VALUE(ptr-WsaData),
        OUTPUT w-Return).

(whatever happended to Jurgen, and it's great work on interfasing windows??)

Any clue?   

I bet .NET has tons of options to get same functionality... but where to start?

Posted by Garry Hall on 11-Apr-2017 21:38

Here is a quick port to do both 32-bit and 64-bit. I chose to use MEMPTRs as input parameters where I could instead of &IF &THEN. It saved some copying pointers in an out of INT/INT64s. This demonstrates the structure changes Matt pointed out. Also note that you have to be aware of datatype alignment within structures. The concern about r-code is still yours to manage, there is nothing I can do about that.

/* *****************************************************************************/
&IF {&PROCESS-ARCHITECTURE} = 64 &THEN
&SCOPED-DEFINE PTRTYPE INT64
&SCOPED-DEFINE PTRDLLTYPE INT64
&ELSE
&SCOPED-DEFINE PTRTYPE INT
&SCOPED-DEFINE PTRDLLTYPE LONG
&ENDIF
&SCOPED-DEFINE WSADESCRIPTION_LEN 256
&SCOPED-DEFINE WSASYS_STATUS_LEN 128

&SCOPED-DEFINE WSADATA_VERSION_LOW 1 /* WORD(2) */
&SCOPED-DEFINE WSADATA_VERSION_HIGH 3 /* WORD(2) */
&SCOPED-DEFINE WSADATA_DESCRIPTION 5 /* CHAR(WSADESCRIPTION_LEN + 1) */
&SCOPED-DEFINE WSADATA_SYSTEM_STATUS 262 /* CHAR(WSASYS_STATUS_LEN + 1) */
&SCOPED-DEFINE WSADATA_MAX_SOCKETS 391 /* SHORT(4) */
&SCOPED-DEFINE WSADATA_MAX_UDP 395 /* SHORT(4) */
&SCOPED-DEFINE WSADATA_VENDOR_INFO 399 /* CHAR*(4) */
&IF {&PROCESS-ARCHITECTURE} = 64 &THEN
&SCOPED-DEFINE WSADATA_LENGTH 407
&ELSE
&SCOPED-DEFINE WSADATA_LENGTH 403
&ENDIF

&IF {&PROCESS-ARCHITECTURE} = 64 &THEN
&SCOPED-DEFINE HOSTENT_NAME 1 /* CHAR*(8) */
&SCOPED-DEFINE HOSTENT_ALIASES 9 /* CHAR**(8) */
&SCOPED-DEFINE HOSTENT_ADDR_TYPE 17 /* SHORT(2) */
&SCOPED-DEFINE HOSTENT_ADDR_LENGTH 19 /* SHORT(2) */
&SCOPED-DEFINE HOSTENT_ADDR_LIST 25 /* CHAR**(8), and alignment */
&SCOPED-DEFINE HOSTENT_LENGTH 33
&ELSE
&SCOPED-DEFINE HOSTENT_NAME 1 /* CHAR*(4) */
&SCOPED-DEFINE HOSTENT_ALIASES 5 /* CHAR**(4) */
&SCOPED-DEFINE HOSTENT_ADDR_TYPE 9 /* SHORT(2) */
&SCOPED-DEFINE HOSTENT_ADDR_LENGTH 11 /* SHORT(2) */
&SCOPED-DEFINE HOSTENT_ADDR_LIST 13 /* CHAR**(4) */
&SCOPED-DEFINE HOSTENT_LENGTH 16
&ENDIF

DEF VAR cHostName AS CHAR INIT "pcHostName" NO-UNDO.
DEF VAR cHostAddress AS CHAR NO-UNDO.

RUN i-GetTcpInfo ( INPUT-OUTPUT cHostName, OUTPUT cHostAddress).

MESSAGE cHostname SKIP cHostAddress VIEW-AS ALERT-BOX.

PROCEDURE i-GetTcpInfo:
/*------------------------------------------------------------------------
Procedure : i-GetTcpInfo

Description : Return the windows TCP host name and address of any PC.

Parms : - Host name. (INPUT-OUTPUT, CHARACTER)
- Host address. (OUTPUT, CHARACTER):

Sample usage: RUN i-GetTcpInfo (OUTPUT w-TcpName,
OUTPUT w-TcpAddr).

Notes : -
------------------------------------------------------------------------*/
DEFINE INPUT-OUTPUT PARAMETER p-TcpName AS CHARACTER NO-UNDO.
DEFINE OUTPUT PARAMETER p-TcpAddr AS CHARACTER NO-UNDO.

DEFINE VARIABLE w-TcpName AS CHARACTER NO-UNDO.
DEFINE VARIABLE w-Length AS INTEGER NO-UNDO.
DEFINE VARIABLE w-Return AS INTEGER NO-UNDO.
DEFINE VARIABLE ptr-WsaData AS MEMPTR NO-UNDO.
DEFINE VARIABLE w-Hostent AS INTEGER NO-UNDO.
DEFINE VARIABLE ptr-Hostent AS MEMPTR NO-UNDO.
DEFINE VARIABLE ptr-AddrString AS MEMPTR NO-UNDO.
DEFINE VARIABLE ptr-AddrList AS MEMPTR NO-UNDO.
DEFINE VARIABLE ptr-ListEntry AS MEMPTR NO-UNDO.
DEFINE VARIABLE w-TcpPtr AS {&PTRTYPE} NO-UNDO.

/* Initialize return values */
ASSIGN p-TcpAddr = ?
.

/* Allocate work structure for WSADATA */
SET-SIZE(ptr-WsaData) = {&WSADATA_LENGTH}.

/* Ask Win32 for winsock usage */
RUN WSAStartup (INPUT 257, /* requested version 1.1 */
INPUT ptr-WsaData,
OUTPUT w-Return).
/* Release allocated memory */
SET-SIZE(ptr-WsaData) = 0.

/* Check for errors */
IF w-Return NE 0 THEN DO:
MESSAGE "Error accessing WINSOCK support." VIEW-AS ALERT-BOX.
RETURN.
END.

w-TcpName = p-TcpName.

/* Call Win32 routine to get host address */
RUN gethostbyname (INPUT w-TcpName,
OUTPUT ptr-Hostent).

/* Check for errors */
IF get-pointer-value(ptr-Hostent) EQ 0 THEN DO:
MESSAGE "Error resolving host name." VIEW-AS ALERT-BOX.
RUN WSACleanup (OUTPUT w-Return).
RETURN.
END.

/* Set pointer to HostEnt data structure */

/* "Chase" pointers to get to first address list entry */
&IF {&PROCESS-ARCHITECTURE} = 64 &THEN
SET-POINTER-VALUE(ptr-AddrList) = GET-INT64(ptr-Hostent,
{&HOSTENT_ADDR_LIST}).
SET-POINTER-VALUE(ptr-ListEntry) = GET-INT64(ptr-AddrList, 1).
w-TcpPtr = GET-INT64(ptr-ListEntry, 1).
&ELSE
SET-POINTER-VALUE(ptr-AddrList) = GET-LONG(ptr-Hostent,
{&HOSTENT_ADDR_LIST}).
SET-POINTER-VALUE(ptr-ListEntry) = GET-LONG(ptr-AddrList, 1).
w-TcpPtr = GET-LONG(ptr-ListEntry, 1).
&ENDIF

RUN inet_ntoa (INPUT w-TcpPtr ,
OUTPUT ptr-AddrString).

/* Pass back gathered info */
p-TcpAddr = GET-STRING(ptr-AddrString, 1).

/* Terminate winsock usage */
RUN WSACleanup (OUTPUT w-Return).

END PROCEDURE.

PROCEDURE gethostbyname EXTERNAL "wsock32.dll" :
DEFINE INPUT PARAMETER p-Name AS CHARACTER.
DEFINE RETURN PARAMETER p-Hostent AS MEMPTR.
END PROCEDURE.

PROCEDURE inet_ntoa EXTERNAL "wsock32.dll" :
DEFINE INPUT PARAMETER p-AddrStruct AS {&PTRDLLTYPE}.
DEFINE RETURN PARAMETER p-AddrString AS MEMPTR.
END PROCEDURE.

PROCEDURE WSAStartup EXTERNAL "wsock32.dll" :
DEFINE INPUT PARAMETER p-VersionReq AS SHORT.
DEFINE INPUT PARAMETER ptr-WsaData AS HANDLE TO MEMPTR.
DEFINE RETURN PARAMETER p-Return AS LONG.
END PROCEDURE.

PROCEDURE WSACleanup EXTERNAL "wsock32":
DEFINE RETURN PARAMETER p-Return AS LONG.
END PROCEDURE.

All Replies

Posted by onnodehaan on 11-Apr-2017 15:32

Hi,

I'm sorry to say, you did not remember his name exactly. It´s Jurjen Dijkstra. He is a co-worker at my company.

:-)

Regards,

Posted by Garry Hall on 11-Apr-2017 15:46

This code is 32-bit-centric. WSAStartup's second parameter is a pointer, which is 8 bytes on 64-bit, but is defined as a LONG. I am assuming you are using 64-bit, and you are trying to put an 8 byte pointer into this LONG, hence the message. This code would need to be refactored with PROCESS-ARCHITECTURE to make it portable. If I get a few spare minutes in the next day or so, I might get around to doing it, but others should feel free to chime in.

I have not explored .NET options. And it seems Jurjen has been found.  

Posted by OctavioOlguin on 11-Apr-2017 15:58

I'm really pleased to know about him... I admired global shared stuff back then.. (it was sometime between 1997 and 2001)

give my best regards to him please... (partly,.i'm progress developer because of him)  (And I meant that I remember how his name sounds "in spanish" all this years)

And Gary...Thanks for that.. hope you could fix it for us that use it.   it's not urgent, so to your best time suit would be great from you!

Thanks

Posted by onnodehaan on 11-Apr-2017 16:02

Hi Octavio,

Nice to see he inspired you! I'll point him to this thread.

Regards

Posted by OctavioOlguin on 11-Apr-2017 16:04

And yes...

The message states that  "Value does not fit in DLL datatype. (13712)"

Posted by Matt Gilarde on 11-Apr-2017 18:00

You have to change the external procedure declaration to accept a 64-bit pointer when running on a 64-bit AVM;

PROCEDURE WSAStartup EXTERNAL "wsock32.dll" :
    DEFINE INPUT PARAMETER p-VersionReq AS SHORT.
&IF {&PROCESS-ARCHITECTURE} = 32 &THEN
    DEFINE INPUT PARAMETER ptr-WsaData AS LONG.
&ELSE
    DEFINE INPUT PARAMETER ptr-WsaData AS INT64.
&ENDIF
    DEFINE RETURN PARAMETER p-Return AS LONG.
END PROCEDURE.

The code you have may require other changes too. You need to look at Microsoft's documentation for all of the functions called as external procedures to see if any parameters have changed. Some functions may take pointers to C structures as parameters so you also have to look at the structures to see if they have changed. The offsets and datatypes used in the GET- and SET- functions may need to change to match changes to the structures used by the functions.

Another issue is that if you need to support both 32-bit and 64-bit AVMs you may have to have separate r-code for them if the external procedure declarations are platform-specific.

As you can see, this is a complicated topic. Updating this code requires an understanding of how the WIn32 API works.

Posted by OctavioOlguin on 11-Apr-2017 19:16

Also  this is broken!!!!

METHOD PRIVATE STATIC "System.Byte[]" MemptrToByteArray( pmptr AS MEMPTR ):
        DEFINE VARIABLE nPtr   AS System.IntPtr   NO-UNDO.
        DEFINE VARIABLE vInt   AS INTeger         NO-UNDO.
        DEFINE VARIABLE nBytes AS "System.Byte[]".

        vInt = GET-SIZE(pmPtr).
        nBytes = NEW "System.Byte[]"(vInt).
        nPtr = NEW System.IntPtr(GET-POINTER-VALUE(pmPtr)).
        System.Runtime.InteropServices.Marshal:Copy(nPtr, nBytes, 0, vInt).

        RETURN nBytes.

        FINALLY:
            nPtr = ?.
            SET-SIZE(pmPtr) = 0.
            nBytes = ?.
        END.

    END METHOD.

That's really some problem for me...

I use this in a fingerprint scanner routine, and came from 

https://community.progress.com/community_groups/openedge_development/f/19/p/30025/100235#100235 

The error that is thrown appears in the -clientlog as:

[17/04/11@18:06:11.073-0500] P-018148 T-012000 1 4GL -- (Procedure: 'USER-INTERFACE-TRIGGER procs\Nom\Nom07001.w' Line:492) System.OverflowException: Value was either too large or too small for an Int32.

Posted by Garry Hall on 11-Apr-2017 21:38

Here is a quick port to do both 32-bit and 64-bit. I chose to use MEMPTRs as input parameters where I could instead of &IF &THEN. It saved some copying pointers in an out of INT/INT64s. This demonstrates the structure changes Matt pointed out. Also note that you have to be aware of datatype alignment within structures. The concern about r-code is still yours to manage, there is nothing I can do about that.

/* *****************************************************************************/
&IF {&PROCESS-ARCHITECTURE} = 64 &THEN
&SCOPED-DEFINE PTRTYPE INT64
&SCOPED-DEFINE PTRDLLTYPE INT64
&ELSE
&SCOPED-DEFINE PTRTYPE INT
&SCOPED-DEFINE PTRDLLTYPE LONG
&ENDIF
&SCOPED-DEFINE WSADESCRIPTION_LEN 256
&SCOPED-DEFINE WSASYS_STATUS_LEN 128

&SCOPED-DEFINE WSADATA_VERSION_LOW 1 /* WORD(2) */
&SCOPED-DEFINE WSADATA_VERSION_HIGH 3 /* WORD(2) */
&SCOPED-DEFINE WSADATA_DESCRIPTION 5 /* CHAR(WSADESCRIPTION_LEN + 1) */
&SCOPED-DEFINE WSADATA_SYSTEM_STATUS 262 /* CHAR(WSASYS_STATUS_LEN + 1) */
&SCOPED-DEFINE WSADATA_MAX_SOCKETS 391 /* SHORT(4) */
&SCOPED-DEFINE WSADATA_MAX_UDP 395 /* SHORT(4) */
&SCOPED-DEFINE WSADATA_VENDOR_INFO 399 /* CHAR*(4) */
&IF {&PROCESS-ARCHITECTURE} = 64 &THEN
&SCOPED-DEFINE WSADATA_LENGTH 407
&ELSE
&SCOPED-DEFINE WSADATA_LENGTH 403
&ENDIF

&IF {&PROCESS-ARCHITECTURE} = 64 &THEN
&SCOPED-DEFINE HOSTENT_NAME 1 /* CHAR*(8) */
&SCOPED-DEFINE HOSTENT_ALIASES 9 /* CHAR**(8) */
&SCOPED-DEFINE HOSTENT_ADDR_TYPE 17 /* SHORT(2) */
&SCOPED-DEFINE HOSTENT_ADDR_LENGTH 19 /* SHORT(2) */
&SCOPED-DEFINE HOSTENT_ADDR_LIST 25 /* CHAR**(8), and alignment */
&SCOPED-DEFINE HOSTENT_LENGTH 33
&ELSE
&SCOPED-DEFINE HOSTENT_NAME 1 /* CHAR*(4) */
&SCOPED-DEFINE HOSTENT_ALIASES 5 /* CHAR**(4) */
&SCOPED-DEFINE HOSTENT_ADDR_TYPE 9 /* SHORT(2) */
&SCOPED-DEFINE HOSTENT_ADDR_LENGTH 11 /* SHORT(2) */
&SCOPED-DEFINE HOSTENT_ADDR_LIST 13 /* CHAR**(4) */
&SCOPED-DEFINE HOSTENT_LENGTH 16
&ENDIF

DEF VAR cHostName AS CHAR INIT "pcHostName" NO-UNDO.
DEF VAR cHostAddress AS CHAR NO-UNDO.

RUN i-GetTcpInfo ( INPUT-OUTPUT cHostName, OUTPUT cHostAddress).

MESSAGE cHostname SKIP cHostAddress VIEW-AS ALERT-BOX.

PROCEDURE i-GetTcpInfo:
/*------------------------------------------------------------------------
Procedure : i-GetTcpInfo

Description : Return the windows TCP host name and address of any PC.

Parms : - Host name. (INPUT-OUTPUT, CHARACTER)
- Host address. (OUTPUT, CHARACTER):

Sample usage: RUN i-GetTcpInfo (OUTPUT w-TcpName,
OUTPUT w-TcpAddr).

Notes : -
------------------------------------------------------------------------*/
DEFINE INPUT-OUTPUT PARAMETER p-TcpName AS CHARACTER NO-UNDO.
DEFINE OUTPUT PARAMETER p-TcpAddr AS CHARACTER NO-UNDO.

DEFINE VARIABLE w-TcpName AS CHARACTER NO-UNDO.
DEFINE VARIABLE w-Length AS INTEGER NO-UNDO.
DEFINE VARIABLE w-Return AS INTEGER NO-UNDO.
DEFINE VARIABLE ptr-WsaData AS MEMPTR NO-UNDO.
DEFINE VARIABLE w-Hostent AS INTEGER NO-UNDO.
DEFINE VARIABLE ptr-Hostent AS MEMPTR NO-UNDO.
DEFINE VARIABLE ptr-AddrString AS MEMPTR NO-UNDO.
DEFINE VARIABLE ptr-AddrList AS MEMPTR NO-UNDO.
DEFINE VARIABLE ptr-ListEntry AS MEMPTR NO-UNDO.
DEFINE VARIABLE w-TcpPtr AS {&PTRTYPE} NO-UNDO.

/* Initialize return values */
ASSIGN p-TcpAddr = ?
.

/* Allocate work structure for WSADATA */
SET-SIZE(ptr-WsaData) = {&WSADATA_LENGTH}.

/* Ask Win32 for winsock usage */
RUN WSAStartup (INPUT 257, /* requested version 1.1 */
INPUT ptr-WsaData,
OUTPUT w-Return).
/* Release allocated memory */
SET-SIZE(ptr-WsaData) = 0.

/* Check for errors */
IF w-Return NE 0 THEN DO:
MESSAGE "Error accessing WINSOCK support." VIEW-AS ALERT-BOX.
RETURN.
END.

w-TcpName = p-TcpName.

/* Call Win32 routine to get host address */
RUN gethostbyname (INPUT w-TcpName,
OUTPUT ptr-Hostent).

/* Check for errors */
IF get-pointer-value(ptr-Hostent) EQ 0 THEN DO:
MESSAGE "Error resolving host name." VIEW-AS ALERT-BOX.
RUN WSACleanup (OUTPUT w-Return).
RETURN.
END.

/* Set pointer to HostEnt data structure */

/* "Chase" pointers to get to first address list entry */
&IF {&PROCESS-ARCHITECTURE} = 64 &THEN
SET-POINTER-VALUE(ptr-AddrList) = GET-INT64(ptr-Hostent,
{&HOSTENT_ADDR_LIST}).
SET-POINTER-VALUE(ptr-ListEntry) = GET-INT64(ptr-AddrList, 1).
w-TcpPtr = GET-INT64(ptr-ListEntry, 1).
&ELSE
SET-POINTER-VALUE(ptr-AddrList) = GET-LONG(ptr-Hostent,
{&HOSTENT_ADDR_LIST}).
SET-POINTER-VALUE(ptr-ListEntry) = GET-LONG(ptr-AddrList, 1).
w-TcpPtr = GET-LONG(ptr-ListEntry, 1).
&ENDIF

RUN inet_ntoa (INPUT w-TcpPtr ,
OUTPUT ptr-AddrString).

/* Pass back gathered info */
p-TcpAddr = GET-STRING(ptr-AddrString, 1).

/* Terminate winsock usage */
RUN WSACleanup (OUTPUT w-Return).

END PROCEDURE.

PROCEDURE gethostbyname EXTERNAL "wsock32.dll" :
DEFINE INPUT PARAMETER p-Name AS CHARACTER.
DEFINE RETURN PARAMETER p-Hostent AS MEMPTR.
END PROCEDURE.

PROCEDURE inet_ntoa EXTERNAL "wsock32.dll" :
DEFINE INPUT PARAMETER p-AddrStruct AS {&PTRDLLTYPE}.
DEFINE RETURN PARAMETER p-AddrString AS MEMPTR.
END PROCEDURE.

PROCEDURE WSAStartup EXTERNAL "wsock32.dll" :
DEFINE INPUT PARAMETER p-VersionReq AS SHORT.
DEFINE INPUT PARAMETER ptr-WsaData AS HANDLE TO MEMPTR.
DEFINE RETURN PARAMETER p-Return AS LONG.
END PROCEDURE.

PROCEDURE WSACleanup EXTERNAL "wsock32":
DEFINE RETURN PARAMETER p-Return AS LONG.
END PROCEDURE.

Posted by OctavioOlguin on 12-Apr-2017 20:03

From top of my head, I imagine that on calling procedure I should do something like this :

IF PROCESS-ARCHITECTURE = 32 THEN

  RUN getConnInfo32.p(output connInfo).

ELSE

  RUN getConnInfo64.p(output connInfo).

shouldn't I?

Posted by Garry Hall on 13-Apr-2017 08:16

That would work. Provide a simple wrapper procedure getConnInfo.p which does this for you, so you don't have to pollute your application code with platform-specific details.

Posted by OctavioOlguin on 18-Apr-2017 10:49

Yeah...!!!!!

Thanks.

This thread is closed