Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

C arg in Tcl proc is cut out if not long enough

60 views
Skip to first unread message

Kevin Walzer

unread,
Jul 12, 2015, 8:44:16 AM7/12/15
to
I am trying to put together a Tcl extension that, among other things,
constructs and calls a Tcl proc via Tcl_DString and Tcl_VarEval. The
procedure is supposed to take a single parameter.

The very odd thing I'm seeing is that the procedure is called correctly
if the parameter is greater than a certain length--about nine or ten
characters, if I am observing correctly. If the parameter is smaller
than this, it is omitted entirely, and the interp returns an error of
"wrong # args".

Here are the relevant parts of my C code:

long size = AEGetDescDataSize(&directParameter);
Tcl_DStringInit(&as_arg);

if (size) {
UInt8 paramstring[size];

AEGetDescData(&directParameter, paramstring, size);
AEDisposeDesc(&directParameter);

/*Need to convert paramstring to something Tcl can handle
gracefully. Direct conversion of AE params to char does not seem to work
well, produces garbage.*/

CFStringRef arg = CFStringCreateWithBytes(0, paramstring,
sizeof(paramstring), kCFStringEncodingUTF8, false);
int len;
len = CFStringGetMaximumSizeForEncoding(CFStringGetLength(arg),
kCFStringEncodingUTF8);

Tcl_DStringSetLength(&as_arg, len);
Tcl_DStringValue(&as_arg) = CFStringGetCStringPtr(arg,
kCFStringEncodingUTF8);
CFRelease(arg);
} else {
Tcl_DStringValue(&as_arg) = " ";
}

---

char *applescript_tcl_cmd;

/* Convert the string reference into a C string. */
applescript_tcl_cmd = CFStringGetCStringPtr(cfcmd,
kCFStringEncodingUTF8);

/* Eval the Tcl command. */
Tcl_VarEval(AS_Interp, applescript_tcl_cmd, " ",
Tcl_DStringValue(&as_arg), NULL);

---

In the code above, the CFStringRef bits are just conversions between
various string formats on the Mac on the way to passing Tcl a buffer it
can understand.

If I pass a value of "Archive::Tar" to Tcl_DstringValue(&as_arg), my
code runs correctly. If I pass a smaller string, such as "open," I get
the "wrong # args" error.

I would understand if the arg were truncated in some way that I would
need to increase the size of the string buffer, but it is baffling to me
why it is being cut out altogether.

Is anyone familiar with situations where args are not truncated but
omitted together? I realize Tcl_VarEval is deprecated but it is perfect
for this use case. If someone can suggest a different way to call a Tcl
proc with a single arg from C, constructed with Tcl_DStrings, I am also
happy to try that out.

Advice is appreciated.

--Kevin
--
Kevin Walzer
Code by Kevin/Mobile Code by Kevin
http://www.codebykevin.com
http://www.wtmobilesoftware.com

Christian Gollwitzer

unread,
Jul 12, 2015, 9:36:25 AM7/12/15
to
Am 12.07.15 um 14:44 schrieb Kevin Walzer:
> Tcl_DStringSetLength(&as_arg, len);
> Tcl_DStringValue(&as_arg) = CFStringGetCStringPtr(arg,
> kCFStringEncodingUTF8);

I'm pretty sure this is wrong - Tcl_DString is for *readout* of the
value. Not for putting something in. You are manipulating the internals
and effectively destroying the Tcl_DString (remember, that C has no real
strings, just pointers to the beginning of a memory block).

For putting something in the DString, use Tcl_DStringAppend.

> CFRelease(arg);
> } else {
> Tcl_DStringValue(&as_arg) = " ";
> }
>
> ---
>
> char *applescript_tcl_cmd;
>
> /* Convert the string reference into a C string. */
> applescript_tcl_cmd = CFStringGetCStringPtr(cfcmd,
> kCFStringEncodingUTF8);
>
> /* Eval the Tcl command. */
> Tcl_VarEval(AS_Interp, applescript_tcl_cmd, " ",
> Tcl_DStringValue(&as_arg), NULL);

Here the question remains - why do you need DString at all? In your
example, you are using the code as it is. Yould have done just

Tcl_VarEval(...., CFStringGetCStringPtr(...))

instead. Or are you building up the script step-by-step?

>
> ---
>
> In the code above, the CFStringRef bits are just conversions between
> various string formats on the Mac on the way to passing Tcl a buffer it
> can understand.
>
> If I pass a value of "Archive::Tar" to Tcl_DstringValue(&as_arg), my
> code runs correctly. If I pass a smaller string, such as "open," I get
> the "wrong # args" error.

But it creates memory leaks or double free etc. It is highly suspicious.

> I would understand if the arg were truncated in some way that I would
> need to increase the size of the string buffer, but it is baffling to me
> why it is being cut out altogether.

Probably a short string optimization - for short strings the DString
stores the string somewhere else than in the pointer, I guess (haven't
verified the code)

Christian

Kevin Walzer

unread,
Jul 12, 2015, 10:09:58 AM7/12/15
to
Thanks for the feedback on this--I will review my code and post back a
bit later. K

Kevin Walzer

unread,
Jul 12, 2015, 10:35:30 AM7/12/15
to

Kevin Walzer

unread,
Jul 13, 2015, 3:13:14 AM7/13/15
to
I was able to get this working, first, by using a different method to
get data into a char format for part of the code. CFStringGetCStringPtr
is fast but sometimes returns NULL for no obvious reason, so it required
a different call here:

/* Get direct parameter, convert to char. */
err = AEGetKeyDesc(theAppleEvent, keyDirectObject, typeUTF8Text,
&directParameter);

long size = AEGetDescDataSize(&directParameter);
Tcl_DStringInit(&as_cmd);

if (size) {
UInt8 paramstring[size];

/*Need to convert paramstring to something Tcl can handle gracefully.
Direct conversion of AE params to char does not seem to work well,
produces garbage.*/

CFStringRef arg = CFStringCreateWithBytes(0, paramstring,
sizeof(paramstring), kCFStringEncodingUTF8, false);
CFDataRef data;
data = CFStringCreateExternalRepresentation(NULL, arg,
kCFStringEncodingUTF8, 0);
if (data) {
as_arg = (const char *)CFDataGetBytePtr(data);
fprintf(stdout, "The parameter is %s", as_arg);
}
CFRelease(arg);
} else {
as_arg = " ";
}


And then, later, I used Tcl_DStringAppend to build up a single command
string to feed to the interpreter:

/* Build the command to eval. */

Tcl_DStringAppend(&as_cmd, applescript_tcl_cmd, -1);
Tcl_DStringAppend(&as_cmd, " ", -1);
Tcl_DStringAppend(&as_cmd, as_arg, -1);

/* Eval the Tcl command. */
Tcl_Eval(AS_Interp, Tcl_DStringValue(&as_cmd));
Tcl_DStringFree(&as_cmd);

Now things work as expected.

Thanks for helping me sort this out.

Ralf Fassel

unread,
Jul 13, 2015, 10:29:25 AM7/13/15
to
* Kevin Walzer <k...@codebykevin.com>
| And then, later, I used Tcl_DStringAppend to build up a single command
| string to feed to the interpreter:
>
| /* Build the command to eval. */
>
| Tcl_DStringAppend(&as_cmd, applescript_tcl_cmd, -1);
| Tcl_DStringAppend(&as_cmd, " ", -1);
| Tcl_DStringAppend(&as_cmd, as_arg, -1);
>
| /* Eval the Tcl command. */
| Tcl_Eval(AS_Interp, Tcl_DStringValue(&as_cmd));
| Tcl_DStringFree(&as_cmd);
>
| Now things work as expected.

Are 'applescript_tcl_cmd' and 'as_arg' are always one word each?
In this case I'd rather use Tcl_EvalObjv() directly:

Tcl_Obj *cmd[2];

cmd[0] = Tcl_NewStringObj(applescript_tcl_cmd, -1);
Tcl_IncrRefCount(cmd[0]);

cmd[1] = Tcl_NewStringObj(as_arg, -1);
Tcl_IncrRefCount(cmd[1]);

Tcl_EvalObjv(interp, 2, cmd, TCL_EVAL_GLOBAL);

Tcl_DecrRefCount(cmd[0]);
Tcl_DecrRefCount(cmd[1]);

My $0.02
R'
0 new messages