Remote Workplace Server v. 0.80 (3/7/2007, Richard L Walsh) |
Readme/What's new |
_______________________________________________________________________________
_______________________________________________________________________________
== REMOTE WORKPLACE SERVER ==
_______________________________________________________________________________
_______________________________________________________________________________
Beta version 0.80 released July 3, 2007
(C)Copyright 2004-2007 R.L.Walsh - all rights reserved
An open-source project licensed under the Mozilla Public License
Please send your comments & questions to: Rich Walsh <rws@e-vertise.com>
_______________________________________________________________________________
== Contents of UsingRws.Txt ==
_______________________________________________________________________________
* INTRODUCTION
- About RWS
| - Changes in v0.80
* USING RWS
| - Overview
- Calling RWS
Type RWSP_*
- Getting the Results
- Cleanup
- RWS Types
Type RWSI_*
Type RWSO_*
Type RWSR_*
Types for Object Conversion
- RwsConvert
Type RWSC_*
* API
| - Functions
- RwsBuild / RwsCall Arguments
| - RWS Commands
- RWS Macros
- Server Request Block
* FILE LIST
_______________________________________________________________________________
_______________________________________________________________________________
== INTRODUCTION ==
_______________________________________________________________________________
_______________________________________________________________________________
This document is for programmers who wish to use Remote Workplace Server
(RWS) to access the WPS. To take full advantage of RWS, you should be
familiar with the basics of writing code for the WPS. However, some
aspects of RWS are generic enough - in particular, RwsConvert, the object
conversion feature - that almost all programmers should be able to use it
in their applications.
For examples of how RWS is used in a working program, please look at
the source code for the RWS demo program "Iconomize". It uses most
RWS functions and its comments explain every aspect of its operation.
If you'd like to experiment with RWS, you can try "RwsTest" which lets
you call almost any procedure that uses six or fewer arguments.
Currently, there is no formal documentation for RWS's components, RwsClient
and RwsServer. However, the information provided here along with the
extensive comments in their source code should leave few questions about
how they work. And of course, in all cases you should review the primary
header, rws.h, and the header containing RWS's error codes, rwserr.h.
_______________________________________________________________________________
- About RWS -
_______________________________________________________________________________
Remote Workplace Server enables a program to invoke any class or object
method or any SOM function in the WPS process without the use of SOM or
DSOM. All types of arguments and return values are supported. Data that
is accessible or meaningful only in the WPS process (e.g. a buffer or
object pointer) can be copied or converted to make it available to the
calling process.
On input, objects can be referenced by fully-qualified name or title,
object ID, handle, container record pointer, or the handle of an open view.
Classes can be specified by name or reference to an object of that class.
On output, an object pointer can be converted to any of these, or to an
unqualified name or title, path, icon, and more.
After a successful call to RWS, all data that was used in the transaction
is available for use by the calling program: its original input, the
arguments that were actually passed to the WPS procedure, the output of
the procedure exactly as it was returned, and that output after being
copied or converted by RwsServer. Clients can reuse WPS-specific data
to improve efficiency or as input to functions that require such data
(e.g. enumeration functions).
RWS also provides RwsConvert, an object conversion feature which transforms
one type of object reference to another, e.g. object handle to object title.
Multiple objects can be converted with a single call. In addition, RWS
offers a command feature to handle operations that require extra support
within the WPS process (e.g. displaying an object's menu).
RWS is designed so it can be used on a program's primary (UI) thread without
blocking the message queue. This is not a requirement - it can be used on
any of a program's first 32 threads provided it has a message queue.
In the WPS process, RwsServer is hosted by the "RWSxx" object class, a
subclass of WPTransient. Although it must be registered with the WPS,
it will not be loaded until needed by a client program. The class creates
no permanent objects and provides no functionality beyond creating a
separate thread upon which RwsServer will run. When the last program
using RWS terminates, the dll containing RwsServer will be unloaded.
A final note: RwsServer and RwsClient contain no runtime environment that
might conflict with your code, and both dlls perform extensive parameter
checks. While they are unlikely to have problems with obvious errors like
null pointers, slightly more subtle coding errors may cause unexpected
results. RwsClient provides no exception handling, so crashes are possible.
The exception handling in RwsServer makes crashes in the WPS unlikely but
access violations may leave the WPS in an inconsistent state. Be careful.
_______________________________________________________________________________
|- Changes in v0.80 -
_______________________________________________________________________________
| v0.80, the fourth release of RWS, addresses design flaws in previous
versions that could lead to lost messages and unexpected results.
It also adds new functions for tracking and cancelling requests to
RWSServer, as well as some smaller enhancements.
| == New and Revised Features ==
| Dispatch Status Functions: These enable other windows in an app
to track a request to the server asynchronously. They can be
used to popup non-modal "wait or cancel" dialogs, or to avoid
calling RWS recursively.
- RwsDispatchStatus() determines if the current thread is in a
dispatch and whether it is blocked by another message loop.
- RwsNotify() posts WM_CONTROL notifications to a window when
RWSClient enter or exits its message loop.
- RwsCancelDispatch() causes RWSClient to time-out a dispatch
and return immediately if possible.
| RWSCMD_DELETE: This new command will optionally force the deletion
of non-deletable objects.
| RwsConvert: The ability to convert a WPS object pointer into something
usable by your program has been extended to objects used in method
calls and RWS Commands. Your app can both perform a task involving
an object and gather info about it in a single call.
| Extended Version Information: RWS08 provides more precise versioning
via a new function, RwsQueryVersion(), & a new macro, RWSFULLVERSION.
| == Bugs Fixed ==
| Message Handling: In previous versions, replies from the server could
be lost if another message loop overrode RwsClient's (e.g. while the
user moved or resized a window). Message handling has been changed
to prevent this. However, RWS's dispatch function is still unable
to return control to its caller until the overriding message loop is
removed and its own loop is active again.
| Recursive Calls to RwsDispatch():
A call to RwsDispatch() when the current thread was already in a
dispatch would create a new message loop that overrode RWS's existing
loop. In v0.80, these calls now return RWSCLI_RECURSIVECALL. The new
dispatch status functions can be used to avoid this error.
_______________________________________________________________________________
_______________________________________________________________________________
== USING RWS ==
_______________________________________________________________________________
_______________________________________________________________________________
- Overview -
_______________________________________________________________________________
After you've created a message queue, you must call RwsClientInit() to
prepare RWS for use in the current process. This call will cause the
server to be loaded; optionally, it will also register the RWSxx class
| if needed. Like most RWS functions, it will return zero if successful
or a descriptive error code if it fails.
When you need to call a WPS object method, a SOM function, an RWS command,
or RwsConvert, you will usually use one of two functions: RwsCall() or
RwsCallIndirect(). They operate the same and vary only in the way you pass
parameters to them. These parameters include the data that will be passed
to the procedure along with a description of that data so RWS knows how to
handle it.
Calls to these functions will not return until the server replies or a
timeout occurs. While waiting for a reply, RwsClient will enter a message
| loop so the queue doesn't block. During the wait, your app may handle
| other messages. If it does, it should not call RwsCall() again nor use
| code that installs its own message loop (e.g. WinDlgBox()). Functions
| like RwsDispatchStatus() will identify whether RWS is in use and waiting.
Upon return, RWS provides a pointer to a block of memory containing all
of the data used in the transaction: your input, any intermediate values,
and the final result. To retrieve the final value of the return or any
argument, you can call RwsGetResult(). To access any of the data directly,
you can use RwsGetArgPtr(). When you're done with this memory block, you
must release it using RwsFreeMem().
Before your program ends, it should call RwsClientTerminate() to ensure an
orderly shutdown. An entry in your program's Exit List will handle this if
your program terminates prematurely.
_______________________________________________________________________________
- Calling RWS -
_______________________________________________________________________________
RwsClient provides two primary functions for calling RwsServer: RwsCall()
and RwsCallIndirect(). Both of them build a request from the parameters
you supply, dispatch it to the server, then return when a reply is received
or a timeout occurs (currently 20 seconds). The only difference between the
two is how you pass parameters to them.
RwsCall() takes individual arguments that are pushed onto the stack, while
RwsCallIndirect() takes a pointer to an RWSBLD structure followed by zero or
more RWSARG structures. These structures mimic the layout of the stack that
RwsCall() would see and contain exactly the same arguments you'd pass to it.
Which function you use depends largely on what you are doing. In most cases,
it's simpler to use RwsCall(). However, if you're calling the same function
repeatedly or you're using RwsConvert on a large number of objects, then
RwsCallIndirect() is probably the better choice. Here are their prototypes:
ULONG _System RwsCall( PRWSHDR* ppHdr, ULONG callType, ULONG callValue,
ULONG rtnType, ULONG rtnSize, ULONG argCnt, ...);
ULONG _System RwsCallIndirect( PRWSBLD pBld);
The arguments to these functions are described in detail in the API section,
below. The next section, "Type RWSP_*" describes two of them in detail:
callType and callValue.
RwsClient provides additional functions that you may wish to use in special
circumstances. RwsBuild() and RwsBuildIndirect() construct a server request
block but do not dispatch it. Instead, they return immediately with a
pointer to the request. This enables you to modify its data in a way that
RwsClient can not - for example, updating pointers embedded in a structure.
After you've made your changes, you can then call RwsDispatch() to dispatch
the request. Here is its prototype:
ULONG _System RwsDispatch( PRWSHDR pHdr);
RwsClient also offers various ...Async() calls if you'd prefer to handle
replies from the server yourself. All of these calls return immediately
after dispatching your request. Their signatures are the same as those
above except that the first argument is the handle of the window to which
RwsServer should post its reply. Use RwsGetServerMsgID() to obtain the
ID of the server's reply message.
_______________________________________________________________________________
Type RWSP_*
-----------
RwsServer supports 4 distinct operations: method calls, SOM function
calls, built-in RwsCommands, and RwsConvert, the object converter. An
RWSP_ constant in the 'callType' argument to RwsCall/RwsBuild identifies
which one you want. Data placed in the 'callValue' argument provides the
information needed to execute it. Here's a list of the RWSP_ constants,
the type of data each takes, and an explanation of how each operates:
callType callValue
--------- -----------
RWSP_MNAM Method Name
you're calling a class or object method and have supplied its name
as a string; the name is identical to that shown in the WPS and SOM
references and does not contain a leading underscore; the first
argument to the call must be the object whose method is being invoked.
RWSP_KORD SOM Function Ordinal
you're calling a function contained in the SOM Kernel (i.e. som.dll)
and have supplied its ordinal; you can include rwssomk.h to refer to
functions by a constant (e.g. SOMK_somPrintf) rather than by number.
RWSP_CMD RWS Command ID
you're calling one of RWS's built-in commands and have supplied its
| ID (e.g. RWSCMD_POPUPMENU); currently, there are six commands.
RWSP_CONV Zero
you're calling RwsConvert to translate one form of object identifier
into another (e.g. to convert an object's handle into its title);
callValue is ignored and should be set to zero; each argument
identifies a single conversion - you can perform as many conversions
with each call as available memory and good sense permit.
RWSP_MPFN Method PFN
you're calling a class or object method and have supplied a pointer
to the function which implements it; this pointer was obtained by
an earlier invocation of this method using RWSP_MNAM; if you reuse
a method pointer, you must be certain that it will only be used with
objects whose class is the same as the object used in the original
call - using it with other classes may crash the WPS; the first
argument to the call must be the object whose method is being invoked.
RWSP_KPFN SOM function PFN
you're calling a function contained in som.dll and have supplied a
pointer to the function; since som.dll is never unloaded, the pfn
should remain valid for the duration of the current WPS session.
Because RWS may have to copy or convert data after a procedure returns, it
has to know whether the call succeeded or failed. If it failed, RWS will
skip any output conversions and return RWSSRV_FUNCTIONFAILED. By default,
it assumes that a return value of zero (i.e. FALSE, NULL, etc.) indicates
failure.
In cases where this assumption isn't valid, you can add an 'I' at the end
of the RWSP_ constant to have it Ignore the return value and perform output
processing regardless, or you can add a 'Z' to tell it that Zero indicates
success.
Ignore Return: RWSP_MNAMI RWSP_KORDI RWSP_MPFNI RWSP_KPFNI
Zero is Success: RWSP_MNAMZ RWSP_KORDZ RWSP_MPFNZ RWSP_KPFNZ
Of course, none of this applies to methods or functions that return void,
nor does it apply to RWSP_CONV or RWSP_CMD.
_______________________________________________________________________________
- Getting the Results -
_______________________________________________________________________________
After a successful call to RWS returns, all of the data that was used in
the transaction is available to you:
- the data that you provided
- the data RwsServer generated by converting your input
- the data returned by the procedure you were calling
- the data RwsServer generated by converting the procedure's output
Even a pointer to the method or function that was called is available.
There are currently two ways to access this data:
ULONG _System RwsGetResult( PRWSHDR pHdr, ULONG ndxArg, PULONG pSize);
ULONG _System RwsGetArgPtr( PRWSHDR pHdr, ULONG ndxArg, PRWSDSC* ppArg);
For both functions, arguments are numbered starting at 1; to get the
return value, set argNdx to zero.
RwsGetResult() returns the final value of an argument, after any copying or
conversion by RwsServer. Unlike other RwsClient functions, RwsGetResult()
returns a value rather than a result code. Errors are signaled by a return
of (ULONG)-1. pSize is optional (it can be null), and its name is somewhat
misleading. Beside providing size info, it attempts to signal the type of
data being returned. If the entire result fits in the return value, *pSize
is zero; if the return is a pointer to a string, *pSize is (ULONG)-1.
Otherwise, the return is a pointer to a buffer, and *pSize contains the
calculated length of its data.
RwsGetArgPtr() retrieves a pointer to the RWSDSC structure for the specified
argument (or return). Using it, you have access to all data associated with
that argument. Refer to the "Server Request Block" section below and to the
source code for RwsClient & RwsServer for specifics on what data goes where.
_______________________________________________________________________________
- Cleanup -
_______________________________________________________________________________
After you've retrieved whatever data you need from the server request block,
you must free it by calling RwsFreeMem().
ULONG _System RwsFreeMem( PRWSHDR pHdr);
Calling RwsFreeMem() with a null pointer is not an error. To avoid leaks,
always initialize pHdr to zero before calling RwsCall() or RwsBuild() and
then always call RwsFreeMem() at an appropriate point, regardless of whether
or not the call succeeded. In some cases, RwsFreeMem() may return
RWSCLI_MEMINUSE. This error can be ignored - RWS will free the memory
automatically when it is safe to do so.
_______________________________________________________________________________
- RWS Types -
_______________________________________________________________________________
In order to pass data across process boundaries, RWS has to copy it
to shared memory. RWS types provide the info it needs to handle this
correctly. They also identify the conversions RwsServer has to perform
to make your input usable by the procedure you're calling, and to make
its output usable by your program. Their only purpose is to inform RWS
what it has to do with your data. RWS doesn't know or care what you or
the procedure are going to do with it. Understanding this will make it
far easier to decide which type to use.
RWS types are identified by symbolic constants defined in rws.h.
They fall into 5 broad categories, identified by their prefix:
RWSI_ arguments that only need handling before the procedure call
(note that all arguments need some sort of handling)
RWSO_ arguments that need handling both before and after the call
(typically, in/out args used to return a pointer to some data)
RWSR_ the return value from a procedure
RWSC_ arguments to RWS's conversion feature - by their nature,
they usually require both before and after processing
RWSP_ the type of procedure you are calling
While rws.h lists almost 200 types, the number of basic types is really
very small. Most of the rest are for converting WPS object pointers to
and from data that's meaningful outside the WPS. They were defined to
ensure that almost every possibility is covered - you'll probably only
use a small subset.
Below is a review of the basic types. Types for converting WPS objects
and classes are described under "Types for Object Conversion".
_______________________________________________________________________________
Type RWSI_*
-----------
RWSI_ASIS a DWORD that requires no special handling (i.e. use as-is)
RWSI_PSTR a pointer to a string
RWSI_PBUF a pointer to a fixed-length buffer - you must supply its size
RWSI_PPVOID a pointer to a 4-byte buffer - you can also use RWSI_PULONG
You'll also see types like RWSI_PSOMBUF and RWSI_POBJSTR. These are for
special situations where the input data must be copied to memory allocated
by SOM or by an object before it can be passed to the procedure (e.g. a
USEITEM).
| Note: RWSI_PBUF & RWSI_PSTR can be used to pass an empty buffer to a
| function. Specify the size of the buffer needed (RWSI_PSTR defaults
| to 260), and use zero for the argument's value (i.e. a null pointer).
_______________________________________________________________________________
Type RWSO_*
-----------
RWSO_PPSTR the procedure will be returning a pointer to a string in an
in/out argument; RWS has to copy that string into its own
buffer so you can access it; if you don't specify the size
| of the buffer, RWS will allocate 260 bytes (i.e. CCHMAXPATH)
RWSO_PPBUF the procedure returns a pointer to a fixed-length buffer
in an in/out argument; RWS has to copy it into its own
buffer so you can access it; you must specify the size of
the buffer - RWS will copy exactly that many bytes into it
RWSO_PPBUFCNTRTN, RWSO_PPBUFCNTARG1, RWSO_PPBUFCNTULONG, etc.
these are similar to RWSO_PPBUF except that they instruct
RWS to look elsewhere for the number of bytes to copy,
e.g. the count is in the return value, in another in/out
arg, in a ULONG at the start of data to be copied, etc;
you must still specify the maximum size of the buffer
You'll also see types like RWSO_PPBUFSOMFREE and RWSO_PPSTROBJFREE. These
tell RWS to free the SOM or object memory holding the data after copying it.
_______________________________________________________________________________
Type RWSR_*
-----------
RWSR_ASIS returns a DWORD that needs no special handling
RWSR_VOID returns void
RWSR_PSTR returns a pointer to a string
RWSR_PPSTR returns a pointer to a pointer to a string
RWSR_PBUF returns a pointer to a fixed-length buffer
RWSR_PPBUF returns a pointer to a pointer to a fixed-length buffer
All return types except RWSR_ASIS and RWSR_VOID require a buffer into
which RWS can copy the data. For pointers to string, RWS will default
to 260 bytes if you set size to zero; for pointers to a buffer, the
size must be supplied. Just like RWSO_*, there are addition RWSR_ types
that tell RwsServer where to look for the exact number of bytes to copy.
_______________________________________________________________________________
Types for Object Conversion
---------------------------
All WPS methods and most SOM functions require at least one class or
object pointer as input, and many return one or more as output. Since
these pointers are meaningless outside the WPS process, RWS will convert
them to and from external formats usable by your program (e.g. name,
object handle, etc.).
RWS supports 5 external formats that can be converted into object pointers.
It also provides several additional formats for converting object pointers
back into something your program can use (e.g. title, path, icon, etc.).
The RWSI_, RWSO_, & RWSR_ constants below identify the conversion needed:
Input (RWSI_) - object & class references you supply to a method:
RWSI_OPATH a file or folder's fully-qualified name (#)
RWSI_OFTITLE an object's fully-qualified title; if the title itself
contains backslashes they must be escaped by doubling (#$)
RWSI_OHWND the HWND of an object's open view
RWSI_OHNDL an object handle
RWSI_OPREC an object's minirecordcore pointer (obtained via d&d)
RWSI_SFTITLE if this is a shadow's fully-qualified title, use the
original object instead (#$)
RWSI_SHNDL if this is a shadow's handle, use the original object instead
RWSI_SPREC if this is a shadow's minirecordcore, use the original object
RWSI_CNAME the name of a class
RWSI_COPATH the class of the object whose path was supplied (#)
RWSI_COHNDL the class of the object whose handle was supplied
etc.
# - for input, OPATH & OFTITLE also accept an object's <Object ID>
$ - locating an object using its title is a relatively "expensive"
operation and should be avoided if the object can be identified
another way (e.g. by its Object ID or handle)
Output (RWSO_ & RWSR_) - object & class references returned by a method:
RWSx_OPATH as above, fails if not a file or folder
RWSx_OFTITLE as above, backslashes in the title will not be escaped
RWSx_OHWND as above, fails if no view is open
RWSx_OHNDL as above
RWSx_OPREC as above
RWSx_CNAME as above, works with both class & object pointers
RWSx_ONAME a file or folder's real name, the title of any other object
RWSx_OTITLE an object's title - this may not match a file's real name
RWSx_OID an object's Object ID, fails if none is assigned
RWSx_OFLDR the fully-qualified name of the folder containing an object
RWSx_OICON an object's full-sized icon
RWSx_OMINI an object's mini icon
RWSx_CICON a class' default icon, works with both class & object ptrs
RWSx_CMINI a class' default mini-icon, works with class & object ptrs
_______________________________________________________________________________
- RwsConvert -
_______________________________________________________________________________
Often, a program's primary reason for accessing the WPS will be to convert
an object reference from one external format to another. For example:
you have a list of object handles and want to get each one's title; or,
an object was dropped on your window and you'd like to retrieve its icon.
RwsConvert was designed to handle these tasks.
RwsConvert permits you to perform as many conversions in a single call
as seems reasonable. Each conversion is handled individually: the call
proceeds even if some conversions fail and only returns an error code if
all fail.
You can determine whether a particular conversion failed in two ways:
if you call RwsGetResult(), it will return (ULONG)-1 for a failure; or,
if you call RwsGetArgPtr(), the 'rc' field for that RWSDSC structure will
contain a non-zero return code. You can also identify whether any of the
conversions failed by examining the call's return value (i.e. arg0) which
will contain a count of the number of successful conversions.
For each argument to RwsCall...() or RwsBuild...(), you'll supply one of
the RWSC_ constants described below, the object data to be converted (cast
to a ULONG, as needed), and optionally, the size of the output buffer if
the result will be a string (the default size is 260 bytes, enough for a
fully-qualified path).
When converting the same object into multiple formats (e.g. handle to title,
object ID, and icon), you can improve RwsServer's efficiency by using the
appropriate RWSC_PREV_* constants. These tell RWS to reuse the previous
argument's object pointer rather than performing an input conversion for
each item. For example, the first argument would use RWSC_OHNDL_OTITLE
to convert the handle to an object pointer, and then to the object's title.
The next two would use RWSC_PREV_OID and RWSC_PREV_OICON to reuse the
previous argument's object pointer. If the initial conversion from handle
to object pointer fails, all three will fail and the error code for each
will be the same.
_______________________________________________________________________________
Type RWSC_*
-----------
All the RWSC_ constants follow a simple naming convention that should make
it easy to deduce the one you want: RWSC_Input_Output. Rather than listing
every one, here's a listing of Input and Output - just mix-and-match:
Input Output
----- ------
OBJ OBJ
OPATH OPATH
OFTITLE OFTITLE
OHWND OHWND
OHNDL OHNDL
OPREC OPREC
SFTITLE ONAME
SHNDL OTITLE
SPREC OID
PREV OFLDR
CLASS OICON
OMINI
CLASS
CNAME
CICON
CMINI
Note that these lists include three types not mentioned previously:
OBJ pointer to an object (usable only within the WPS process)
CLASS pointer to a class (usable only within the WPS process)
PREV reuse the previous argument's object pointer
Here are a few examples:
RWSC_OPATH_OHWND in: an object's f/q name; out: an open view's hwnd
RWSC_OPREC_OICON in: a minirecordcore ptr; out: the object's icon
RWSC_SHNDL_CNAME in: handle of an object or its shadow;
out: the name of the original object's class
Two additional RWSC_ types are defined that aren't for object conversion.
Rather, they retrieve buffers or strings from an address in the WPS process
space. They're useful when you have a structure containing a pointer and
need to access the data it references.
RWSC_ADDR_PSTR in: pointer to a string; out: a copy of the string
RWSC_ADDR_PBUF in: pointer to a buffer; out: a copy of the buffer
_______________________________________________________________________________
_______________________________________________________________________________
== API ==
_______________________________________________________________________________
_______________________________________________________________________________
- Functions -
_______________________________________________________________________________
RwsBuild:
ULONG _System RwsBuild( PRWSHDR* ppHdr,
ULONG callType, ULONG callValue,
ULONG rtnType, ULONG rtnSize,
ULONG argCnt, ...);
Constructs a server request block in shared memory from arguments passed
on the stack. Returns as soon as the request is built; the application
must call RwsDispatch() or RwsDispatchAsync() to submit the request to
RwsServer. Provided to enable the application to modify arguments in ways
that RwsClient can not. Can also be used to create a skeleton request
block that the app will repeatedly reuse with minor modifications.
Returns zero for success or one of the error codes generated by RwsClient.
See below for a description of its arguments.
_______________________________________________________________________________
RwsBuildIndirect:
ULONG _System RwsBuildIndirect( PRWSBLD pBld);
Provides exactly the same functionality as RwsBuild(). Its single
argument is a pointer to an RWSBLD structure that may be followed by
zero or more RWSARG structures. The address of the first RWSARG must
be &pBld[1]. Use the CALCARGPTR(p) macro to calculate this address and
cast it as a PRWSARG. See below for a description of the members of
RWSBLD and RWSARG.
_______________________________________________________________________________
RwsCall:
ULONG _System RwsCall( PRWSHDR* ppHdr,
ULONG callType, ULONG callValue,
ULONG rtnType, ULONG rtnSize,
ULONG argCnt, ...);
Constructs a server request block in shared memory from arguments passed
on the stack, then dispatches it to RwsServer. Returns after a reply has
been received from the server or a timeout has occurred. Provides the
simplest way for an application to invoke RWS functionality. Internally,
it calls RwsBuildIndirect(), and if that is successful, it then calls
RwsDispatch(). Returns zero for success or any of the error codes
generated by RwsClient or RwsServer, depending on where the failure
occurred. See below for a description of its arguments.
_______________________________________________________________________________
RwsCallIndirect:
ULONG _System RwsCallIndirect( PRWSBLD pBld);
Provides exactly the same functionality as RwsCall(). Its only argument
is a pointer to an RWSBLD structure that may be followed by zero or more
RWSARG structures. The address of the first RWSARG must be &pBld[1].
Use the CALCARGPTR(p) macro to calculate this address and cast it as a
PRWSARG. See below for a description of the members of RWSBLD and RWSARG.
_______________________________________________________________________________
RwsCallAsync:
ULONG _System RwsCallAsync( HWND hwnd, PRWSHDR* ppHdr,
ULONG callType, ULONG callValue,
ULONG rtnType, ULONG rtnSize,
ULONG argCnt, ...);
Similar to RwsCall() except that the server's reply message is posted
to a window provided by the application. Unlike RwsCall(), it returns
immediately after dispatching the request to RwsServer. It is the
application's responsibility to capture the server's reply message and
handle it appropriately. Failure to do so will result in a memory leak
that will quickly exhaust RwsClient's shared memory pool. Provided to
give applications additional design flexibility, particularly in cases
where it would be inappropriate for RwsClient to enter its own message
loop while waiting for a reply. Returns zero for success or one of the
error codes generated by RwsClient. Error codes generated by RwsServer
will be returned in mp2 of the reply message. The first argument to
this function is the window to which the reply should be posted. See
below for a description of the other arguments.
_______________________________________________________________________________
RwsCallIndirectAsync:
ULONG _System RwsCallIndirectAsync( HWND hwnd, PRWSBLD pBld);
Provides exactly the same functionality as RwsCallAsync(). Its single
argument is a pointer to an RWSBLD structure that may be followed by
zero or more RWSARG structures. The address of the first RWSARG must
be &pBld[1]. Use the CALCARGPTR(p) macro to calculate this address and
cast it as a PRWSARG. The first argument to this function is the window
to which the reply should be posted. See below for a description of the
members of RWSBLD and RWSARG.
_______________________________________________________________________________
RwsClientInit:
ULONG _System RwsClientInit( BOOL fRegister);
Initializes RWS for the current process and causes RwsServer to be loaded
in the WPS process if necessary. In particular, it allocates the gettable
shared memory that will be used to pass requests between client & server,
and creates an object window that the server will post its replies to.
To ensure RwsServer gets loaded and stays loaded, this call creates a WPS
object of class RWSxx. If fRegister is TRUE, it will register the class
if needed. To permit RwsServer to be unloaded, clients should call
RwsClientTerminate() before exiting to delete the object. In any case,
the object will cease to exist when the WPS terminates. Returns zero
for success or one of the relevant error codes generated by RwsClient.
_______________________________________________________________________________
RwsClientTerminate:
ULONG _System RwsClientTerminate( void);
Deletes the WPS object created by RwsClientInit() so that RwsServer can
be unloaded when the last program using it terminates. While the server
remains loaded, users will be unable to move or delete the dll that
contains it. If this function isn't called, an entry in the program's
Exit List will handle termination (note: calling this function will
remove the Exit List entry). Returns RWSCLI_TERMINATEFAILED or zero
for success.
_______________________________________________________________________________
RwsDispatch:
ULONG _System RwsDispatch( PRWSHDR pHdr);
Posts your request to RwsServer then waits for its reply. If a reply
is not received within the timeout period, it will return with an error.
This timeout can be changed using RwsSetTimeout(). Before posting the
request, this function confirms the server is active and restarts it if
needed (as described under RwsClientInit()). While waiting for a reply,
this function enters a message loop and dispatches any messages it
receives. If a WM_QUIT is encountered, it reposts that message then
exits with an error. Your application's design should take into account
the possibility that other parts of it may become active while its WPS-
related code is waiting for a reply. Returns zero for success of an
error code generated by RwsServer.
_______________________________________________________________________________
RwsDispatchAsync:
ULONG _System RwsDispatchAsync( HWND hwnd, PRWSHDR pHdr);
Posts your request to RwsServer then returns immediately. The hwnd you
provide identifies the application-supplied window to which the server
should reply. The ID of the reply message can be obtained by calling
RwsGetServerMsgID(). Regardless of how the application handles this
message, it must always free the server request block identified in mp1.
Failure to do so will result in a memory leak that will quickly exhaust
RwsClient's shared memory pool. Provided to give applications additional
design flexibility, particularly in cases where it would be inappropriate
for RwsClient to enter its own message loop while waiting for a reply.
Returns zero for success or an error code generated by RwsClient. The
return value from RwsServer can be found in mp2 of the reply message.
_______________________________________________________________________________
RwsFreeMem:
ULONG _System RwsFreeMem( PRWSHDR pHdr);
Frees the server request block returned by the RwsBuild... and RwsCall...
functions. Supplying a null pointer is not an error. This function
must always be called to avoid depleting RwsClient's shared memory pool
(currently, 64k-64). Note that this is a wrapper for DosSubFreeMem()
and will fail if the Size member of the RWSHDR structure has been changed
from its original value. Returns zero for success or RWSCLI_BADMEMPTR if
pHdr is invalid. Returns RWSCLI_MEMINUSE when called after a server reply
has timed-out. This error can be ignored because RwsClient will free the
request block itself when the next server reply is received.
_______________________________________________________________________________
RwsGetArgPtr:
ULONG _System RwsGetArgPtr( PRWSHDR pHdr, ULONG ndxArg, PRWSDSC* ppArg);
Provides a pointer to the RWSDSC structure specified in ndxArg. Arguments
are indexed from one; the return value is index zero. Each RWSDSC
structure contains all of the data associated with an argument or return:
your input, any intermediate values, and the final value. For an in-only
argument, the intermediate value is your input after RwsServer has
performed the requested conversion (e.g. object handle to object pointer).
For the return and in/out args, it also provides whatever data was
returned by the procedure in its original form; the final value is
the procedure's output after it has been copied or converted by RwsServer.
Provided to enable easy access to data that is only valid within the WPS
process (i.e. pointers) so that it can be reused in subsequent calls.
Returns zero for success or RWSCLI_BADARGNDX or RWSCLI_MISSINGARGS.
_______________________________________________________________________________
RwsGetResult:
ULONG _System RwsGetResult( PRWSHDR pHdr, ULONG ndxArg, PULONG pSize);
Returns the final value of the procedure's return or any of its arguments.
Unlike most other RWS function, the return contains the requested value,
not an error code. Errors are signaled by a return value of (ULONG)-1.
Arguments are indexed from one; the return value is index zero. pSize is
optional and may be null. If supplied, it signals the type of value being
returned: zero if the return is a DWORD (e.g. a ULONG or object pointer);
(ULONG)-1 if the return is a pointer to a string; or a positive number if
the return is a pointer to a buffer. In this last case, the number
indicates how many bytes RwsServer copied into the buffer. Returns the
requested value for success or (ULONG)-1 if ndxArg is out of range or the
return value is requested for a procedure that returns void.
_______________________________________________________________________________
| RwsDispatchStatus:
|
| ULONG _System RwsDispatchStatus( PBOOL pfReady);
|
| Determines whether RWS is currently in a dispatch, and optionally,
| whether an event is ready. Returns zero if RWS is in a dispatch or
| RWSCLI_NOTINDISPATCH if it isn't. pfReady is optional and indicates
| whether a reply was received from the server or a timeout occurred.
| If TRUE, it implies that another message loop has overridden RWS's
| and that RWS will be unable to return to its caller until its own
| message loop is active again. This function can also be used to avoid
| making recursive calls to RWS's dispatch functions which are guaranteed
| to fail. Returns zero, RWSCLI_NOTINDISPATCH, or RWSCLI_BADTID.
_______________________________________________________________________________
| RwsCancelDispatch:
|
| ULONG _System RwsCancelDispatch( HWND hNotify, ULONG idNotify);
|
| Attempts to cancel a dispatch on the current thread by causing an
| immediate timeout. The server may continue to process the dispatch
| but its reply will be discarded when received. If fully successful,
| RwsDispatch() will return to its caller when the code that's calling
| RwsCancelDispatch() completes. hNotify and idNotify are optional.
| If both are supplied, a WM_CONTROL message will be posted to hNotify
| to identify the result. The low word of mp1 contains idNotify, the
| high word contains RWSN_CANCEL. mp2 is a boolean; if TRUE, RWS will
| have returned by the time this message is received. If FALSE, another
| message loop has overridden RWS's: RWS won't be able to return while
| it is still in place. Note: if RWS was blocked by another message
| loop when the server's reply was received, this function may cause
| the reply to be returned rather than a timeout. Returns zero for
| success, or RWSCLI_NOTINDISPATCH, or one of several PM-related errors.
_______________________________________________________________________________
| RwsNotify:
|
| ULONG _System RwsNotify( HWND hNotify, ULONG idNotify);
|
| Posts WM_CONTROL notifications to hNotify when these events occur:
| RWSN_ENTER - RWS has entered its message loop
| RWSN_EXIT - RWS has exited its message loop
| RWSN_BLOCKED - a reply has been received or a timeout has occurred
| but RWS is blocked by another message loop
| These notification codes appear in the high-order word of mp1. The
| low-order word contains idNotify; it can be any value that doesn't
| conflict with IDs assigned to hNotify's dialog controls. For RWSN_EXIT,
| mp2 contains the result code returned by RwsDispatch. Both hNotify and
| idNotify must be supplied; if either or both are zero, notifications
| will stop. Notifications can be used to prevent actions or clear
| conditions that might block RWS (e.g. displaying a modal dialog) or to
| give the app greater control over timeouts. For example, an app might
| set a long timeout but popup a *non-modal* wait-or-cancel dialog after
| a few seconds delay. Returns zero for success or RWSCLI_MISSINGARGS
| or RWSCLI_BADTID.
_______________________________________________________________________________
| RwsQueryVersion:
|
| ULONG _System RwsQueryVersion( PULONG pulReserved);
|
| Returns the the value that RWSFULLVERSION (defined in rws.h) was set to
| when RwsCliXX.Dll was built. For RWS v0.80 GA, this value is 0x08000100.
| Applications can compare the return to the current value of RWSFULLVERSION
| to confirm that the dll in use meets the app's minimum requirements.
| Unlike other RWS functions, this can be called before RwsClientInit().
| pulReserved is ignored and can be a null pointer. This function does
| not return any errors.
_______________________________________________________________________________
RwsGetServerMsgID:
ULONG _System RwsGetServerMsgID( PULONG pulMsgID);
Gets the ID of the message that RwsServer uses when posting its reply to
a client. Applications that use any of the ...Async() functions must
obtain this ID after calling RwsClientInit() so their window procedures
will be able to capture and handle replies from RwsServer. The MPARAMS
for this message are:
(PRWSHDR) mp1 - the address of the server request block
(ULONG) mp2 - RwsServer's return code; zero indicates success
This function returns zero for success or RWSCLI_NOATOM.
_______________________________________________________________________________
RwsGetTimeout:
ULONG _System RwsGetTimeout( PULONG pulSecs, PULONG pulUser);
Gets the current timeout value in seconds used by RwsCall() and
RwsDispatch(). pulUser is optional: if it is not NULL, it returns
the timeout set by the user via the RWSTIMEOUT environment variable.
If the user hasn't specified a timeout, its value will be zero.
_______________________________________________________________________________
RwsSetTimeout:
ULONG _System RwsSetTimeout( ULONG ulSecs);
Sets the current timeout value used by RwsCall() and RwsDispatch()
in seconds. If ulSecs is set to zero, restores the initial value
(by default, 20 seconds unless the user has set it via RWSTIMEOUT).
_______________________________________________________________________________
- RwsBuild / RwsCall Arguments -
_______________________________________________________________________________
Required Arguments / RWSBLD structure
-------------------------------------
All versions of the RwsBuild and RwsCall functions require the same
parameters. RwsBuild(), RwsCall(), and RwsCallAsync() take them as
individual arguments that are pushed onto the stack. The various
...Indirect() functions take them as a pointer to an RWSBLD structure
whose members are identical to the individual arguments.
PRWSHDR* ppHdr address of a pointer to an RWSHDR structure;
on return, that pointer will have the address
of the server request block
ULONG callType one of the RWSP_ constants
ULONG callValue a value appropriate for the callType, cast to
ULONG (string, ordinal, pfn, or command ID);
ignored for RWSP_CONV, should be zero
ULONG rtnType one of the RWSR_ constants
ULONG rtnSize size of the buffer needed to hold the procedure's
return value after conversion (if any); zero if
the return is a DWORD
ULONG argCnt the number of arguments passed to the procedure,
or the number of objects to be converted
Optional Arguments / RWSARG structure
-------------------------------------
For each argument you pass to a procedure, RWS needs three parameters:
type, size, and value. RwsBuild(), RwsCall(), and RwsCallAsync() take them
as individual arguments that are pushed onto the stack in the order listed.
The ...Indirect() functions take them as an array of zero or more RWSARG
structures that immediately follow RWSBLD, i.e. [RWSBLD][RWSARG]...[RWSARG]
The CALCARGPTR() macro will return the address of the first RWSARG.
ULONG type one of the RWSI_, RWSO_, or RWSC_ constants
ULONG size for input data (RWSI_), only required for fixed
length buffers, zero otherwise; for output data
(RWSO_ & RWSC_), required when the return is a
fixed length buffer, optional if it's a string
(defaults to 260 bytes), zero otherwise
ULONG value a value appropriate for the data type, cast to
ULONG
_______________________________________________________________________________
- RWS Commands -
_______________________________________________________________________________
| Currently, six commands have been implemented.
Important!! In order to make command syntax easier to understand, commands
are presented below as though they were function calls - they are not!
Map a command's arguments and return value to RwsCall/RwsBuild parameters
the same way you would with a method call.
_______________________________________________________________________________
RWSCMD_POPUPMENU:
Pseudo-code syntax
------------------
HWND hMenu = RWSCMD_POPUPMENU( WPObject Obj, HWND hOwner,
POINTL* pptlOwner)
Parameter Type Size Value
--------- --------- ---- ----------------------
hMenu RWSR_ASIS 0 hwnd of the popup menu
Obj RWSI_O* 0 object whose menu will be shown
hOwner RWSI_ASIS 0 hwnd that "owns" the menu
pptlOwner RWSI_PBUF sizeof(POINTL) menu position in owner coordinates
Displays the popup menu for the specified object. hOwner will get the
focus before (and possibly after) the menu appears. ptlOwner is the
location of the lower-left corner of the menu in hOwner coordinates.
Both hOwner and ptlOwner are optional and may be set to zero. If so,
the Desktop will get the focus and the menu will be positioned at the
mouse pointer's current location. Provided because menus require support
in the WPS process to map menu commands to object methods. Returns zero
for success or an error code generated by RwsClient or RwsServer.
_______________________________________________________________________________
RWSCMD_OPEN:
Pseudo-code syntax
------------------
HWND hwnd = RWSCMD_OPEN( WPObject Obj, ULONG ulView, BOOL fNew)
Parameter Type Size Value
--------- --------- ---- ----------------------
hwnd RWSR_ASIS 0 hwnd of the opened window
Obj RWSI_O* 0 object to be opened
ulView RWSI_ASIS 0 view to open (OPEN_* constant)
fNew RWSI_ASIS 0 force new window
Opens the requested view for the specified object. ulView is the numeric
value of the desired view; zero opens the default view. If fNew is FALSE
the command will only open a new window if the requested view is not open
already. When fNew is TRUE, it will always open a new window. On return,
hwnd contains the handle of the frame containing the desired view. This
command should be used rather than wpOpen/wpSwitchTo/wpViewObject because
any windows it opens will be created on the WPS's primary (UI) thread
rather than RwsServer's. This helps preserve the integrity of the RWS
thread & avoids potential problems when sending messages between threads.
See RWSCMD_OPENUSING for an alternate way to open files.
_______________________________________________________________________________
RWSCMD_LOCATE:
Pseudo-code syntax
------------------
HWND hwnd = RWSCMD_LOCATE( WPObject Obj)
Parameter Type Size Value
--------- --------- ---- ----------------------
hwnd RWSR_ASIS 0 hwnd of the folder window
Obj RWSI_O* 0 object to be located
Locates an object in a manner similar to a shadow's 'Locate' menuitem.
It opens or switches to the folder containing the requested object, then
selects the object and scrolls it into view if needed. Unlike a shadow's
implementation, it will open the target folder in Details view if the
folder's default is Tree or Details. Like RWSCMD_OPEN, any new windows
will be created on the WPS's primary (UI) thread.
_______________________________________________________________________________
RWSCMD_LISTOBJECTS:
Pseudo-code syntax
------------------
ULONG ulCnt = RWSCMD_LISTOBJECTS( WPFolder * Folder, PBYTE pBuf,
PULONG pulFlags)
Parameter Type Size Value
--------- --------- ---- ----------------------
ulCnt RWSR_ASIS 0 number of object handles returned
Folder RWSI_O* 0 folder containing objects
pBuf RWSI_PBUF ? (e.g. 1024) in: contents ignored
out: array of ulCnt handles
pulFlags RWSI_PULONG 0 in: zero or LISTOBJ_FILESHADOWS
out: in | LISTOBJ_OVERFLOW
If *pulFlags is zero, returns the handles of all Abstract & Transient
objects in a folder. If *pulFlags is LISTOBJ_FILESHADOWS, returns the
handles of all files & folders which have shadows in a folder (these are
the original objects' handles, not the shadows'). pBuf should be large
enough to hold all possible handles (e.g. 1024 bytes for 256 objects).
If it isn't, pBuf will contain as many handles as will fit, and the
initial value in *pulFlags will be ORed with the LISTOBJ_OVERFLOW flag.
On return, ulCnt will contain the the number of handles in pBuf (which
may be zero).
_______________________________________________________________________________
RWSCMD_OPENUSING:
Pseudo-code syntax
------------------
BOOL fSuccess = RWSCMD_OPENUSING( WPObject Source, WPObject Target)
Parameter Type Size Value
--------- --------- ---- ----------------------
fSuccess RWSR_ASIS 0 indicates success or failure
Source RWSI_O* 0 file or object to open
Target RWSI_O* 0 object that will open Source
Opens a file (Source) using a specific program object/file (Target).
This command simulates a drag and drop operation and can be used
wherever it's legal to drop one object (Source) on another (Target).
Avoid using this command for operations where the WPS could popup a
confirmation or error dialog that the user doesn't expect (e.g. moving
or deleting files).
_______________________________________________________________________________
| RWSCMD_DELETE:
|
| Pseudo-code syntax
| ------------------
| BOOL fSuccess = RWSCMD_DELETE( WPObject * somSelf, BOOL fForce)
|
| Parameter Type Size Value
| --------- --------- ---- ----------------------
| fSuccess RWSR_ASIS 0 indicates success or failure
| somSelf RWSI_O* 0 file or object to delete
| fForce RWSI_ASIS 0 force object's deletion
|
| Deletes an object. Optionally, forces deletion by setting the object's
| NODELETE flag to 'NO'. For filesystem objects, it also clears the
| object's file attributes.
_______________________________________________________________________________
- RWS Macros -
_______________________________________________________________________________
These are the macros that you are most likely to use in your own code.
Addtional function-like macros are described in rws.h.
CALCARGPTR(p):
Takes a pointer to an RWSBLD structure, returns a pointer to the first
RWSARG structure following it. Used when constructing the argument
block that will be passed to RwsBuildIndirect() or RwsCallIndirect().
Implemented as: ((PRWSARG)(PVOID)(&((PRWSBLD)p)[1]))
CALCPROCPTR(p):
Takes a pointer to a server request block's RWSHDR structure, returns
a pointer to the first RWSDSC following it. That structure describes
the procedure being called.
Implemented as: ((PRWSDSC)(PVOID)(&((PRWSHDR)p)[1]))
CALCGIVEPTR(p):
Takes a pointer to an RWSDSC structure in a server request block,
returns a pointer to the give buffer which follows it. This buffer
contains the data for all input types except RWSI_ASIS.
Implemented as: ((PBYTE)(PVOID)(&((PRWSDSC)p)[1]))
_______________________________________________________________________________
- Server Request Block -
_______________________________________________________________________________
Server Request Block - a series of concatenated
Layout of a Server structures and buffers created by RwsClient
Request Block and passed to RwsServer for processing. It
================= always contains an RWSHDR followed by RWSDSC
RWSHDR structs describing the procedure & its return
- - - - - - - - - value. It may have additional RWSDSCs, one
RWSDSC for proc for each argument. Upon a successful return,
(give buffer) it contains all data used in the transaction.
- - - - - - - - -
RWSDSC for rtn Give Buffer - except for RWSI_ASIS, contains data
( get buffer) the client gives to the server. When present,
----------------- it always starts at &RWSDSC[1]; CALCGIVEPTR()
RWSDSC for arg1 will provide this address. Where a pointer
(give buffer) to data is required, the give buffer contains
( get buffer) the data & RWSDSC.value points to it. Other-
- - - - - - - - - wise, it contains input data the server must
- - - - - - - - - copy or convert before a call. The result of
RWSDSC for argN this processing then goes into RWSDSC.value.
(give buffer)
( get buffer) Get Buffer - contains data the client gets from
================= the server after it has copied or converted
RWSDSC.value. If present, it starts on a
DWORD boundary immediately following the
Give buffer. RWSDSC.pget points to it.
_______________________________________________________________________________
_______________________________________________________________________________
== FILE LIST ==
_______________________________________________________________________________
_______________________________________________________________________________
The following files are contained in the Rws development distribution.
| All files are timestamped July 3, 2007 at 00:08:00 except those
| associated with 'oo' - their timestamp is July 3, 2007 at 01:10:00.
RWS08\
|- FPos.Exe
|- FPos.Txt
|- Iconomize.Exe
|- Iconomize.Txt
|- LICENSE
| |- OO.Exe
| |- OO.Txt
|- Rws.H
| |- Rws08.Cmd
| |- RwsCli08.Dll
| |- RwsCli08.Lib
|- RwsErr.H
|- RwsSomK.H
| |- RwsSrv08.Dll
|- RwsTest.Exe
|- UsingRws.Txt
|
|- FPos\
|- FPos.Def
|- FPos.Dlg
|- FPos.H
|- FPos.Ico
|- FPos.Mak
|- FPos.Rc
|- FPosChk.Ico
|- FPosCmd.C
|- FPosCol.C
|- FPosDown.Bmp
|- FPosMain.C
|- FPosRc.H
|- FPosSort.Ptr
|- FPosUp.Bmp
|- Iconomize\
|- Icnz.Def
|- Icnz.Dlg
|- Icnz.H
|- Icnz.Ico
|- Icnz.Mak
|- Icnz.Rc
|- IcnzCmd.C
|- IcnzCol.C
|- IcnzDown.Bmp
|- IcnzErr.Ico
|- IcnzMain.C
|- IcnzRc.H
|- IcnzSort.Ptr
|- IcnzUp.Bmp
| |- OO\
| |- oo.C
| |- oo.Def
| |- oo.Mak
|- RwsCli\
|- RwsCli.C
|- RwsCli.Def
|- RwsCli.H
|- RwsCli.Mak
|- RwsCli.Rc
|- RwsCUtil.C
|- RwsSrv\
|- RwsCls.C
|- RwsCls.H
|- RwsCls.Idl
|- RwsCls.Ih
|- RwsSCmd.C
|- RwsSrv.C
|- RwsSrv.Def
|- RwsSrv.H
| |- RwsSrv.Ico
|- RwsSrv.Mak
| |- RwsSrv.Rc
|- RwsSUtil.C
|- RwsTest\
|- RwsTest.C
|- RwsTest.Def
|- RwsTest.Dlg
|- RwsTest.H
|- RwsTest.Ico
|- RwsTest.Mak
|- RwsTest.Rc
_______________________________________________________________________________
_______________________________________________________________________________
Rich Walsh <rws@e-vertise.com>
Ft Myers, FL
July 3, 2007
_______________________________________________________________________________
_______________________________________________________________________________
|
Commenti
Tae
Mar, 19/01/2016 - 21:28
Collegamento permanente
The download link should use
muffetta
Mer, 20/01/2016 - 10:10
Collegamento permanente
Link corrected. Thanks for
Aggiungi un commento