Skip to content

GNU Smalltalk with C callouts #2

July 7, 2013

This is part 2 of a tutorial on C Callouts from GNU Smalltalk.

Part 1 showed how to compile a library and test program, and then call the library from GNU Smalltalk. In this post, we’re going to add more data types.

A full listing of all code is included at the bottom of this post

String arguments

The C library code, header and function:-

extern char* echoStr(char *str);
...
char* echoStr(char *str) {
printf("\n Inside echoStr: %s\n",str);
return (str);
}

And here are relevant snippets of the Smalltalk script. You can refer to the previous post and to the full listing at the bottom of the page to get the class declaration.

LibCall class >> echoString: str [
   <cCall: 'echoStr' returning: #string args: #(#string)> ]
...
Transcript showCr: (LibCall echoString: 'Yo. Off to see the c').

As you would expect, very similar to the addTen example of the previous post. The callout library is doing the heavy lifting to automatically convert C strings (pointers to null terminated char arrays) to Smalltalk strings.

A function call with two arguments

And lets look at an example with two parameters in the C function call. The example is a function that compares the length of two strings, and returns the length of the longest.

extern unsigned int longestStr(char *str1, char *str2);
unsigned int longestStr(char *str1, char *str2) {
    printf("\n Inside copyString: %s to %s\n",inStr, outStr);
    return ((strlen(str1) > strlen(str2)) ? strlen(str1) : strlen(str2));
}

And the smalltalk script

LibCall class >> longestString: source to: dest [
   <cCall: 'longestStr' returning: #uint args: #(#string #string)> 
]
...
length := LibCall lenStr: str1.
Transcript show: 'Length of %1 is: %2' % {str1. length asString}; nl.

Passing structs

GNU Smalltalk has a CStruct type for declaring C structs in Smalltalk. Here is the C struct we’re going to work with:-

struct      Record {
    int     id;
    char*   memo;     
    long    modified; 
}

And here are two C functions making use of the “Record” struct. A function to return a new person “Record”, and a function to change the text of the Record memo.

//function headers
extern struct Record* newBlankRecord(unsigned int *resultCode);
extern unsigned int replaceMemo(struct Record *rec, char* memo);

struct Record* newBlankRecord(unsigned int *resultCode) {
  //return a pointer to a new blank person Record
  struct Record *aRec = malloc(sizeof(struct Record));
  printf("\n Inside newRecord: Size in bytes allocated to struct 'Record': %lu\n",sizeof(sizeof(struct Record)));
  aRec->id = 0;
  aRec->memo = NULL;
  aRec->modified = 0;
  *resultCode = 0; //0 indicates success, error handling omitted
  return (aRec);  
}

unsigned int replaceMemo(struct Record *rec, char* memo) {
    //update the Record memo 
    char* recordMemo;
    recordMemo = (char*)malloc(strlen(memo));
    if (rec->memo != NULL) {
        free(rec->memo);
    }
    strcpy(recordMemo, memo);
    rec->memo = recordMemo;
    printf("\n Inside recMemo. Memo is '%s' \n",rec->memo);
    return (strlen(rec->memo));
}

And now the Smalltalk side. First a declaration of a structure in Smalltalk that will map to the C structure, and then two Smalltalk methods to access the two C function calls above. Note the declaration of the CRecord structure inside the Smalltalk class. Doing this enables us to pass a reference to the class (#self) in each place that a reference to the C structure is required.

CStruct subclass: CRecord [
         <declaration: #( (#id  #int)
                          (#memo #string)
                          (#modified #long))>
                      
CRecord class >> newBlankRecord: record [
   "class method to create a new #CRecord"
   "(#ptr #uint) is a single argument, an unsigned int pointer"
   <cCall: 'newBlankRecord' returning: #{CRecord} args: #((#ptr #uint))> 
]                     
replaceMemo: aMemo [
   "instance method to update the Record memo"
   <cCall: 'replaceMemo' returning: #uint args: #(#self #string)> 
]         
] "CRecord"

And here is the Smalltalk code to call the two functions in CRecord. As you can see the call to “replaceMemo” looks like standard Smalltalk on the right hand side even though there is a call to a C function implementing this code behind the scenes. In fact, the replaceMemo method could be wrapped in another method to check if retVal is non-zero, and if so, an exception would be thrown.

"newBlankRecord argument maps to an unsigned int pointer"
record := CRecord newBlankRecord: (CUInt value:10).
retVal := record replaceMemo: 'Lives at Bedrock'.

Now you may wondering about the #self in mapping from Smalltalk to C, and, is this the way it has to be done. In fact, the CStruct could have been declared in another class instead of in CRecord. For instance in the Smalltalk snippet below, I’ve separated the CStruct declaration from the function declarations; they are in separate classes. (There is no working example for this, but rather just to show how it could have been done). As you can see, the use of #self enables a nicer Smalltalk mapping of the C library.

CStruct subclass: CRecord [
         <declaration: #( (#id  #int)
                          (#memo #string)
                          (#modified #long))>
]                      

Cobject subclass: CRecordLib
CRecord class >> newBlankRecord: record [ 
   <cCall: 'newBlankRecord' returning: #{CRecord} args: #((#ptr #uint))> 
]                     
CRecordLib class >> replaceMemo: aMemo [
   <cCall: 'replaceMemo' returning: #uint args: #(#{CRecord} #string)> 
]         
] "CRecord"

And to wrap it up, here is a real world example, a post showing how to access the LDAP API from GNU Smalltalk.

http://lists.gnu.org/archive/html/help-smalltalk/2009-01/msg00112.html

Code listing below for the examples in this tutorial…

ccall2.h

struct       Record {
    int         id;
    char*       memo;     //variable length
    long        modified; //timestamp
};

extern unsigned int add(unsigned int a, unsigned int b);
extern unsigned int lenStr(char *str);
extern char*        echoStr(char *str);
extern unsigned int longestStr(char *str1, char *str2);
extern unsigned int replaceMemo(struct Record *record, char* memo);
extern unsigned int initializeRecord(struct Record *record);
extern struct Record* newBlankRecord(unsigned int *resultCode);

ccall2.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "ccall2.h"

unsigned int add(unsigned int a, unsigned int b) {
    printf("\n Inside add()\n");
    return (a+b);
}

unsigned int lenStr(char *str) {
    printf("\n Inside lenStr: %s\n",str);
    return (strlen(str));
}

char* echoStr(char *str) {
    printf("\n Inside echoStr: %s\n",str);
    return (str);
}

unsigned int longestStr(char *str1, char *str2) {
    printf("\n Inside longestString: '%s' and '%s'\n",str1,str2);
    return ((strlen(str1) > strlen(str2)) ? strlen(str1) : strlen(str2));
}

struct Record* newBlankRecord(unsigned int *resultCode) {
  struct Record *rec =  malloc(sizeof(struct Record));
  printf(" Inside newBlankRecord: Size in bytes allocated to struct 'Record': %lu\n",sizeof(sizeof(struct Record)));
  rec->id = 90;
  rec->memo = NULL;
  rec->modified = 90;  
  *resultCode = 0;  //error handling omitted for clarity
  return (rec);  
}
 
unsigned int replaceMemo(struct Record *rec, char* memo) {
    char* recordMemo;
    recordMemo = (char*)malloc(strlen(memo));
    if (rec->memo != NULL) {
        free(rec->memo);
    }
    strcpy(recordMemo, memo);
    rec->memo = recordMemo;
    printf("\n Inside recMemo. Memo is '%s' \n",rec->memo);    
    return (strlen(rec->memo));
}

unsigned int initializeRecord(struct Record *rec) {
  rec->id =100;
  rec->memo = NULL;
  rec->modified = 100; 
  return (0); //error handling omitted for clarity 
}

ccall2test.c – the test program

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include "ccall2.h"

int main(void)
{
    unsigned int a = 1;
    unsigned int b = 2;
    unsigned int result = 0;
    char string1[] = "hello";
    char *string2  = "universe";    

    result = add(a,b);
    printf("The result is [%u]\n",result);

    result = lenStr(string1);
    printf("Length of '%s' is [%u]\n",string1,result);

    printf("Echo '%s' is '%s'\n",string1,echoStr(string1));

    result = longestStr(string1, string2);
    printf("longestStr: Longest of '%s' and '%s' has length [%u]\n\n",string1, string2, result);
    
    unsigned int retVal2 = 9999;
    struct Record *rec2 = newBlankRecord(&retVal2);
    printf("newBlankRecord: id is [%u] with resultCode: [%u]\n\n",rec2->id, retVal2);

    struct Record *rec =  malloc(sizeof(struct Record));
    int retVal = initializeRecord( rec);
    printf("initializeRecord: id is [%u]\n",rec->id);

    char memo[] = "Lives at Bedrock";
    result = replaceMemo(rec, memo);
    printf("replaceMemo: id is [%u]\n", rec->id);
    printf("Result from replaceMemo is the length of the memo: [%u]\n",result);      
    
    return 0;
}

ccall2.st

#!/usr/local/bin/gst -f
"call a C library"

CObject subclass: LibCall [

LibCall class >> add: num1 with: num2 [
   <cCall: 'add' returning: #uint args: #(#uint #uint)> 
]
LibCall class >> lenStr: str [
   <cCall: 'lenStr' returning: #uint args: #(#string)> 
]
LibCall class >> echoString: str [
   <cCall: 'echoStr' returning: #string args: #(#string)> 
]
LibCall class >> longestString: source to: dest [
   <cCall: 'longestStr' returning: #uint args: #(#string #string)> 
]

] "LibCall"


CStruct subclass: CRecord [
         <declaration: #( (#id  #int)
                          (#memo #string)
                          (#modified #long))>
                          
   CRecord class >> newBlankRecord: retVal [
      "(#ptr #uint) is a single argument, an unsigned int pointer"
      <cCall: 'newBlankRecord' returning: #{CRecord} args: #((#ptr #uint))>
   ]
   initializeRecord: aRecord [
      <cCall: 'initializeRecord' returning: #uint args: #(#self)>
   ]
   replaceMemo: aMemo [
      <cCall: 'replaceMemo' returning: #uint args: #(#self #string)>
   ]        

] "CRecord"



"mainline --------------------------------"
| sum length str1 str2 retVal record |

DLD addLibrary: 'libccall2'.

str1 := 'hello'.
str2 := 'universe'.

sum := LibCall add: 1 with: 1.
Transcript showCr: ('Sum is: %1' bindWith: (sum asString)).

length := LibCall lenStr: str1.
Transcript show: 'Length of %1 is: %2' % {str1. length asString}; nl.

Transcript show: 'Echo %1 is %2' % {str1. (LibCall echoString: str1)}; nl.

length := LibCall longestString: str1 to: str2.
Transcript show: 'Longest of ''%1'' and ''%2'' has length [%3]' % {str1. str2. length printString}; nl.

retVal := CUInt value:10. "for C function requiring pointer to unsigned int"
record := CRecord newBlankRecord: retVal.
Transcript showCr: 'newBlankRecord: id is [%1]' % { record id value asString}.

record := CRecord new.
retVal := record initializeRecord: record.
Transcript showCr: 'initializeRecord: id is [%1]' % { record id value asString}.

retVal := record replaceMemo: 'Lives at Bedrock'.
Transcript showCr: 'replaceMemo: id is [%1] with memo ''%2''' % {record id value asString. record memo value asString}.

Makefile

CC=gcc

all: libccall2.so ccall2test.o
       $(CC) -o ccall2test ccall2test.o -L. -lccall2

libccall2.so: ccall2.c
       $(CC) -fPIC -c ccall2.c -o  ccall2.o
       $(CC) -shared -Wl,-soname,libccall2.so -o libccall2.so ccall2.o

clean:
       rm *.o *.so
Advertisements

From → GNU Smalltalk

Leave a Comment

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: