I'd like to apologize ahead of time that this may be a very long post but want to get all of this out there. Also, this post really only pertains to those with WSTP/MathLink experience.
My problem is that I have written (note: many parts are straight from the documentation) a working program that creates a link server (i.e. WSLinkServer type) and starts listening for connecting links. Once the server receives one, it should then return control flow to my program and then activate, write to, and finally close the link and shutdown the server. Using the Command Prompt netstat
command, I can clearly see my program listening, but when I try to connect to the server from a Mathematica notebook (or kernel for that matter) to test the server, such as
link = LinkConnect["8000@127.0.0.1", LinkProtocol->"TCPIP"];
If[LinkReadyQ[link], LinkRead[link]]
I then receive an error message and a $Failed
return as seen here:
LinkConnect::linkc: Unable to connect to LinkObject[8000@127.0.0.1,2243,12].
Out[57]= $Failed
From my initial debugging, it seems that the C WSTP API function WSNewLinkServerWithPortAndInterface
does not return when any link connection is attempted on the server from within Mathematica's notebook FE or a kernel. This API function is a blocking function by design (note: I've tried the async coded version too with the same result --- no connection), which waits for a connection and should return a WSLINK
object when a link is received but it just keeps blocking and never returns. (note: the WSTP docs say words like "object" when referring to these API types but in the wstp.h file, you can see their definitions up to a point; so things like WSENV, WSLINK, WSLinkServer are nearly always some pointer to a struct or something similar).
Question(s):
(1) If my understanding is right that the server's listening link is akin to the so-called "main link", then do I need to create any additional links or loopback links for buffering that could be causing this problem? As a background, Mathematica's structure of having a main link, preemptive link, and service link corresponding to the variables $ParentLink
, MathLink`$PreemptiveLink
, and MathLink`$ServiceLink
, where the main link is for the front end (my server program in this case) sending packets to the kernel (a connecting client in this case), the preemptive link is for the kernel sending results of requests back to the front end, and the service link is for the kernel making requests of the front end (opposite of main link).
(2) Is there another issue with the way I'm using the API functions here such as not correctly setting up the WSENV variable for say thread-safety, etc.? (Note: I've already tried this with the same result too. Be default, all WSLINK objects are not thread-safe unless specified prior to initializing the WSENV from which the links will be created).
(3) Am I compiling this program improperly? Could someone else try compiling it? (see below for my host info and compiler details)
(4) Is this just a bug in the WSTP API? It seems odd given the amount of fully documented C code for the link server feature and emphasis on the feature set found at Changes in WSTP for Interface 4.
Here is my C source code for a file called linkserver.c
corresponding to the Makefile below (save them both in the same directory and ensure the Makefile variables are set correctly for your environment or compile manually):
#include "wstp.h"
void operate_link_server(WSENV env, unsigned short port, const char *interface)
{
int error;
WSLinkServer server = (WSLinkServer)0;
WSLINK link = (WSLINK)0;
server = WSNewLinkServerWithPortAndInterface(env, port, interface, NULL, &error);
link = WSWaitForNewLinkFromLinkServer(server, &error);
WSActivate(link);
WSPutFunction(link, "Print", 1);
WSPutString(link, "Hello client program.");
WSEndPacket(link);
WSFlush(link);
WSClose(link);
WSShutdownLinkServer(server);
return;
}
int main(int argc, char **argv)
{
WSEnvironmentParameter ep = (WSEnvironmentParameter)0;
WSENV env = (WSENV)0;
unsigned short port = 8000;
const char *interface = "127.0.0.1";
ep = WSNewParameters(WSREVISION, WSAPIREVISION);
env = WSInitialize(ep);
WSReleaseParameters(ep);
operate_link_server(env, port, interface);
WSDeinitialize(env);
return 0;
}
Host and compiler info:
- I am running Mathematica v11.2 for Windows-x86-64 (Windows 10).
- I am using Mingw-w64 toolchain for the gcc compiler so I can compile from Cygwin 64-bit for Cygwin 64-bit or Windows 64-bit (I've also tried just gcc from Cygwin causing the cygwin1.dll dependency issue and have that built into my Makefile below as an option for people to try).
Here is the output of the command x86_64-w64-mingw32-gcc -v
to get the compiler version info (can also do gcc -v
if using the native compiler):
Using built-in specs.
COLLECT_GCC=x86_64-w64-mingw32-gcc
COLLECT_LTO_WRAPPER=/usr/lib/gcc/x86_64-w64-mingw32/6.4.0/lto-wrapper.exe
Target: x86_64-w64-mingw32
Configured with: /cygdrive/i/szsz/tmpp/cygwin64/mingw64-x86_64/mingw64-x86_64-gcc-6.4.0-2.x86_64/src/gcc-6.4.0/configure --srcdir=/cygdrive/i/szsz/tmpp/cygwin64/mingw64-x86_64/mingw64-x86_64-gcc-6.4.0-2.x86_64/src/gcc-6.4.0 --prefix=/usr --exec-prefix=/usr --localstatedir=/var --sysconfdir=/etc --docdir=/usr/share/doc/mingw64-x86_64-gcc --htmldir=/usr/share/doc/mingw64-x86_64-gcc/html -C --build=x86_64-pc-cygwin --host=x86_64-pc-cygwin --target=x86_64-w64-mingw32 --without-libiconv-prefix --without-libintl-prefix --with-sysroot=/usr/x86_64-w64-mingw32/sys-root --with-build-sysroot=/usr/x86_64-w64-mingw32/sys-root --disable-multilib --disable-win32-registry --enable-languages=c,c++,fortran,lto,objc,obj-c++ --enable-fully-dynamic-string --enable-graphite --enable-libgomp --enable-libquadmath --enable-libquadmath-support --enable-libssp --enable-version-specific-runtime-libs --enable-libgomp --enable-libada --with-dwarf2 --with-gnu-ld --with-gnu-as --with-tune=generic --with-cloog-include=/usr/include/cloog-isl --with-system-zlib --enable-threads=posix --libexecdir=/usr/lib
Thread model: posix
gcc version 6.4.0 (GCC)
Here is my Makefile for compiling this program which people can change a few settings/variables to compile using a different compiler if needed. Note the directories where wsprep.exe, wstp.h, and wstp64i4.dll need to (or can) be located. Change as needed as I am using this from Cygwin 64-bit with directories ~/bin contains wsprep.exe, ~/lib contains wstp64i4.dll, ~/devel/include containing wstp.h, and ~/devel/lib containing wstp64i4.lib and any other .lib files generated using the dlltool as needed depending on your platform.
# DESCRIPTION:
# This makefile can be used to build one or more programs using Cygwin
# and the GNU Compiler Collection. Specifically, it is a cross setup to
# build the program on an x86_64-pc-cygwin machine that will be run on a
# host x86_64-pc-cygwin machine but with the compiler setup to produce
# code for an x86_64-w64-mingw32 (the [Mingw-w64][2] project which allows
# GCC to compile on a Windows system and in my case run without dependency
# on the cygwin1.dll so we can run this program in Windows by develop it in
# Cygwin on Windows---if this is confusing read [here][2]). The CC variable
# to gcc, the DLLTOOL to dlltool, and the IMPORTLIB_TYPE variable to cyg if
# you want to compile for cygwin to create a so-called 'native' build where
# build, host, and target are all the same, creating a program dependent on
# the cygwin1.dll that will run on Windows as long as the cygwin1.dll is
# visible to the program---something not required with the below settings.
# USAGE:
# To build all programs, use the command 'make all'. To build the
# linkserver program, use the command 'make linkserver'. Ensure this
# file is called 'Makefile' and in the same directory as the source code
# file 'linkserver.c'.
# NOTE:
# Ensure that any appropriate read, write, and/or execute permissions are
# set for any of the directories and/or binaries handled by this Makefile.
# Portions of this makefile require the use of GNU make.
# see http://www.gnu.org/software/make for more information.
# Compiler variables.
CC = x86_64-w64-mingw32-gcc
DLLTOOL = x86_64-w64-mingw32-dlltool
#EXTRA_CFLAGS = -mwindows -mwin32
#EXTRA_CFLAGS = -mconsole -mwin32
#EXTRA_CFLAGS = -mwin32
# Compiler additions/resources directory variables.
CADDSDIR = $(HOME)/devel
INCDIR = $(CADDSDIR)/include
LIBDIR = $(CADDSDIR)/lib
# Other binaries/preprocessor executables directory variables.
BINDIR = $(HOME)/bin
# System additions/resources directory variables.
# This directory should contain the export/shared/runtime library .dll file and the
# directory should be included in the PATH environment variable during program
# execution time.
EXPORTLIBDIR = $(HOME)/lib
# WSTP developer kit export/shared/runtime library variables.
EXPORTLIB_PREFIX = wstp
EXPORTLIB_ARCH = 64
EXPORTLIB_INTERFACE_NUMBER = 4
EXPORTLIB_NAME = $(EXPORTLIB_PREFIX)$(EXPORTLIB_ARCH)i$(EXPORTLIB_INTERFACE_NUMBER)
EXPORTLIB = $(EXPORTLIB_NAME).dll
# WSTP developer kit import libary file name variable (e.g. a .lib file).
# Can be removed for default, s for static, m for Microsoft, or cyg for Cygwin.
IMPORTLIB_TYPE =
IMPORTLIB_NAME = $(EXPORTLIB_NAME)$(IMPORTLIB_TYPE)
IMPORTLIB = $(IMPORTLIB_NAME).lib
# WSTP developer kit .tm file preprocessor tool executable name variable (e.g. 'wsprep.exe$
WSPREP = $(BINDIR)/wsprep.exe
# TODO: Add support for building up this command from a make command.
# Extra command line flags and arguments for 'wsprep.exe'.
#WSPREP_EXTRA_CFLAGS = -lines
# Command used to clean/remove files from the build directory.
RM = rm
# Array of all of the names of the binaries to build using the command 'make all'.
BINARIES = linkserver
all : $(BINARIES)
linkserver : linkserver.o $(LIBDIR)/$(IMPORTLIB)
$(CC) $(EXTRA_CFLAGS) -I$(INCDIR) linkserver.o -L$(LIBDIR) -l$(IMPORTLIB_NAME) -o $
%.o : %.c
$(CC) -c $(EXTRA_CFLAGS) -I$(INCDIR) $<
%tm.c : %.tm
$(WSPREP) $? -o $@
%cyg.lib :
gendef $(EXPORTLIBDIR)/$(EXPORTLIB) #TODO: Add support for any flags.
$(DLLTOOL) --as-flags=--64 -m i386:x86-64 -k --input-def $(EXPORTLIB_NAME).def --o$
@ $(RM) $(EXPORTLIB_NAME).def
clean :
@ $(RM) -rf *.o *tm.c $(BINARIES)
Note: This question only pertains to Interface 4 as the Link Server features were added with that release.
Comments
Post a Comment