Hey all,
I have two Nim GUI programs running and would like to know the easiest way to get them to communicate
Gui 1. Is 3d physics environment using opengl, all wrapped in nim, I have the mainloop
Gui 2. Is a nim IUP gui (uses nim IUP nimble package) with panels, buttons etc
I would like the buttons & controls in Gui 2 to affect what's happening in Gui 1
Not sure of the best way - I can spawn gui2 from gui1, but data passing is limited to simple global vars.
Ideally I would like some kind of event queue / message queue, I'm happy to write all my own event types, but not sure where to start ?
There's threads, threadpool, asyncnet, sockets and all kinds of nim libs. There's also packages/wrappers for nanomsg.nim and zmq etc
where to begin? what's the easiest way? I just need to pass events like eg. ['rotate', item 1, 1.5] or ['colour', item 4, 0,255,0] etc, simple stuff..
Many thanks
but data passing is limited to simple global vars.
Er, no, use a Channel for that. And usually multi threading is easier to setup than multi processing, that rules out nanomsg, zmq etc.
That said, for your use case multiple processes sound better, you can start and stop Gui 2 independently from Gui 1, that rules out Nim's channels and threadpool and threads etc leaving you with any kind of sockets based package: Nim's async, nanomsg, zmq all fit. I would likely pick nanomsg because it seems easiest to get you started and Nim's async seems overkill for when you don't have thousands and millions of requests per second. Disclaimer: I know nothing about the nanomsg wrapper for Nim.
>So its better and safer to use pipes, shared memory, fifos etc.
By pipes, do you mean nim Channels?
Shared memory / fifos - are there any examples? - I know I could use a seq for the messages or the nim queues module, or maybe even a one-size fits all shared struct, but it's the sharing of the struct/queue that's problematic.
I will continue tinkering, many thanks all...
pipes - OS specific pipes, for Posix systems this is https://linux.die.net/man/3/pipe. For Windows this called Named Pipes https://msdn.microsoft.com/en-us/library/windows/desktop/aa365590(v=vs.85).aspx.
fifos - OS specific mechanism for Posix systems, https://linux.die.net/man/3/mkfifo
shared memory - OS specific also, https://linux.die.net/man/3/shm_open. For Windows https://msdn.microsoft.com/en-us/library/windows/desktop/aa366551(v=vs.85).aspx
As far as i know, currently there only async variant of IPC (which uses mkfifo for posix systems and shared memory for windows systems) is available https://github.com/cheatfate/asynctools/blob/master/asynctools/asyncipc.nim.
Success!
Cobbled a client/server solution in windows using Named Pipes.
Gui's can now run as separate processes with events passing between them.
Thanks all, especially Mr. Ka whose links were not only interesting, but they got me to a solution.
[...] any kind of sockets based package: Nim's async, nanomsg, zmq all fit. I would likely pick nanomsg because it seems easiest to get you started [...]
As the resident anti-license-complexity zealot, I'd like to very highly recommend standardizing on nanomsg (MIT) over zmq (LGPL).
I am promoting Nim as (among its many other virtues) the most unencumbered / lawyer-free programming language, thanks to such a large percentage of its nimble ecosystem sticking with the default MIT license. I hope this percentage will grow even higher. This makes Nim the ideal candidate to be the userland language of our future BSD-based OS projects.
Any project that has any non-copyfree dependency isn't copyfree also. This is a big concern in the Go ecosystem, for example, where a large project with lots of dependencies typically sucks in dozens of pages of legalese that few people really fully understand...
I have written several 3D applications with GUI. Not a single time did I need multithreading for that. Of course you need to establish some communication.
You could create two message queues for bidirectional communication, but I think it is overkill:
var queueDirectionA: seq[Message]
var queueDirectionB: seq[Message]
In one GUI you read from A and write to B and in the other GUI you do the opposite. But that requires you to do design a message for whatever action you want to do. It is simple to put all the effects you want to have in a function, and just call that function directly. That would be unsafe, if you would do multithreading, but when you just have a single thread, there is nothing you need to worry.
With more detail I could give more information. But right now I don't know if my feedback helps you or not.
Thank you for your reply Krux02, I appreciate it. Not quite there however because what I am interested in is processes not threads and how to set up communication between two. Also I am looking for pipes, shared memory, ffs and differences among them, performance etc. I do not want to setup communication through sockets. Nothing to do with GUI applications.
god, thank you I think exactly what I am looking for. Will check your examples.
So I modified god's example. The main differrence is that server receives data and client pushes and data should be string. Unfortunatelly after many many hours i gave up and would like to ask you for help again. I feel I am doing something terribly wrong with strings and their places in memory.
server
import winlean, os
type Msg = cstring
import strutils
import typetraits
# Auxilary ----------------------------------------
proc rr[T](v: ptr T) =
echo "Type: ", name(T), " Addr: ", toHex(cast[int](v)), " Val: ", v[]
proc rr(v: pointer, t: typedesc) =
echo "Type: ", name(pointer), " Addr: ", toHex(cast[int](v)), " val ",
name(t), ": ", (cast[ptr t](v))[]
template leave =
if(true): return
#----------------------------------------
proc connectNamedPipe(hNamedPipe: Handle, lpOverlapped: pointer): WINBOOL
{.importc: "ConnectNamedPipe", stdcall, dynlib: "kernel32".}
const
pipeHeaderName = r"\\.\pipe\dbgpipe"
const
PIPE_READMODE_BYTE = 0x00000000'i32
ERROR_PIPE_BUSY = 231
var pipe:Handle
proc createPipe() =
echo "Creating an instance of a named pipe ..."
var pipeName = newWideCString(pipeHeaderName)
pipe = createNamedPipe(pipeName, # name of the pipe
PIPE_ACCESS_INBOUND, # 1-way pipe -- receive only
PIPE_READMODE_BYTE,
1, # only allow 1 instance of this pipe
0, # no outbound buffer
0, # no inbound buffer
0, # use default wait time
nil)
if pipe == INVALID_HANDLE_VALUE:
echo "Failed to create inbound pipe instance."
let err = osLastError()
if err.int32 != ERROR_PIPE_BUSY:
raiseOsError(err)
echo "Waiting for a client to connect to the pipe..."
# This call blocks until a client process connects to the pipe
var res = connectNamedPipe(pipe, nil)
if res == 0:
echo "Failed to make connection on named pipe."
# look up error code here using GetLastError()
discard closeHandle(pipe)
# close the pipe
proc recvMessage(buffer: ptr Msg ):bool =
result = false
var
msglen = 0.int32
res = 0.int32
try:
discard pipe.peekNamedPipe(cast[pointer](buffer), (4).int32, msglen.addr)
except:
return false
echo("Expected msg bytes: ", 4)
echo("Bytes to read in pipe: ", msglen)
if msglen == 0:
return false
var numBytesRead:int32 = 0
try:
res = readFile(pipe,
cast[pointer](buffer), # the data from the pipe will be put here
(4).int32,
numBytesRead.addr, # this will store number of bytes actually read
nil)
except:
return false
if res > 0:
echo "Number of bytes read: ", numBytesRead
echo "Message: "
rr(buffer, Msg)
return true
else:
echo "Failed to read data from the pipe."
proc test() =
createPipe()
var b:Msg = "ABC"
rr(addr b)
var i = 4
while i > 0:
i -= 1
if recvMessage(addr b):
rr(addr b)
sleep(500)
echo "ver ", 40
test()
client
import winlean, os
type Msg = cstring
const
pipeHeaderName = r"\\.\pipe\dbgpipe"
const
ERROR_PIPE_BUSY = 231
var pipe:Handle
proc connectPipe() =
echo "Connecting to pipe..."
var pipeName = newWideCString(pipeHeaderName)
var sa = SECURITY_ATTRIBUTES(nLength: sizeof(SECURITY_ATTRIBUTES).cint,
lpSecurityDescriptor: nil, bInheritHandle: 1)
pipe = createFileW(pipeName, GENERIC_WRITE,
0, sa.addr, OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL, 0)
if pipe == INVALID_HANDLE_VALUE:
echo "Failed to connect to pipe."
let err = osLastError()
if err.int32 != ERROR_PIPE_BUSY:
raiseOsError(err)
proc closePipe*() =
# Close the pipe (automatically disconnects client too)
discard closeHandle(pipe)
proc sendMessage*(data: ptr Msg) =
echo "Sending data to pipe..."
var
numBytesWritten:int32 = 0
res:int32 = 0
echo "Writing: ", len(data[]), " chars, sizeof ", sizeof(data[])
try:
res = writeFile(pipe, # handle to our outbound pipe
cast[pointer](data), # data to send
(sizeof(data[])).int32,
numBytesWritten.addr, # will store actual amount of data sent
nil)
except:
return
if res > 0:
echo "Number of bytes sent: ", numBytesWritten
else:
echo "Failed to send data."
proc test() =
var t:Msg = "XYZ"
connectPipe()
sendMessage(addr t)
closePipe()
echo "ver ", 9
test()
It seems to connect and transfer some data, but on the server side I see empty string or part of string used in echoes...
Any help much appreciated.
There's a couple of superfluous cast[pointer] in my original, in both send and recv message. They were left over from developing (they have no effect because they were just casting pointer -> pointer), code looks a bit cleaner when just directly using the buffer pointer passed in (no cast needed).
In the server prog I changed the type of Msg to: type Msg = array[50,char] and remove init of var b:Msg #= "ABC"
This gets the strings being passed across, but some more understanding and re-factoring are probably in order
Hope this helps