VASmalltalk – Primitives – EsSendMessage

Es kann Gründe geben, Arbeiten in sogenannte low-level Funktionen auszulagern, die in anderen Programmiersprachen geschrieben wurde – in der Regel externe Funktionen, die sich in shared Libraries befinden.

Neben der “normalen” Anbindung von C-Funktionen gibt es dann auch die sogenannten Primitives: C-Routinen, in denen man mittels “C” auf den Smalltalk-Datenstrukturen arbeitet. Ein mühsames Unterfangen und es ist oftmals fraglich, ob sich das wirklich lohnt.

Trotzdem kann es durchaus Spaß machen, sich mit der Materie zu beschäftigen. Aber Vorsicht: die Dokumentation lässt viele Fragen offen und man kann nur vermuten, dass diese Art der Programmierung nicht wirklich unterstützt wird/gewünscht wird.

Doch zum eigentlichen Thema. Es besteht die Möglichkeit aus diesen Primitives heraus eine Smalltalk Methode eines Objektes aufzurufen. Dies geschieht mittels der Funktion EsSendMessage. In der Online-Hilfe steht als Definition:

U_32 EsSendMessage(EsVMContext vmContext, EsObject * returnObject, EsObject receiver, EsObject selector, U_32 argumentCount, ...) 

Einiges ist klar: ‘receiver’ ist der Empfänger der Nachricht und da jede Methode ein Ergebnis zurückgibt, wird dieses in ‘returnObject’ gespeichert. Etwas schwieriger ist dabei der Parameter ‘selector’. Dieser unscheinbare Parameter erwartet eine Instanz von Symbol! Es besteht aber leider ohne weiteren Aufwand keine Möglichkeit Instanzen von Symbol zu erzeugen.

In den Beispielen der Onlinedoku wird daher der Weg gewählt, dass man eine Instanz von Symbol der Primitiven mitgibt, die es dann einfach benutzt.

Dieses Problem könnte man mit viel Code wohl dadurch lösen, dass man auf Low-Level Ebene das Methodendictionary von String Instanzen durchläuft und dann #asSymbol sucht. Zur Zeit keine Ahnung wie das geht – aber muss ziemlich ekelhaft aussehen.

Aufgabe

Ok, kommen wir zur Beschreibung einer Anwendung von EsSendMessage. Wir haben Instanzen der Klasse Integer und möchten die Quadratzahl dieser Instanzen ermitteln. Natürlich kann man das durch eine Multiplikation auf C Ebene erreichen, aber ich möchte das mal mittels aufwendiger EsSendMessage aufzeigen. Vom Prinzip möchten wir auf C-Ebene den Smalltalkausdruck “2 squared” ausführen.

Wir machen uns die Arbeit leichter, indem wir unserem Subsystem einfach das Symbol #asSymbol übergeben. Damit wir dieses Objekt auch unabhängig vom GarbageCollector benutzen können, packen wir das Objekt in den fixed Space und übergeben es mittels des folgenden Aufrufes:

DefineFixedAsSymbol
  self primitiveDefineAsSymbol: #asSymbol makeFixed

und

primitiveDefineAsSymbol: aSymbol
  primitive: 'mskcldtprim.dll':SetAsSymbolSelector
  ^self primitiveFailed

an die C-Primitive auf. Hier wird lediglich das Objekt in der Umgebung der Primitive gespeichert für weitere Nutzung:

// This primitive sets an object in this C environment to point 
// to an object in fixed space holding the symbol #asSymbol
static EsObject AsSymbolSelector = NULL;

EsUserPrimitive(SetAsSymbolSelector)
{
  if (EsPrimArgumentCount = 1)
  {
    AsSymbolSelector = EsPrimArgument(1);
    EsPrimSucceed(EsPrimReceiver);
  }
  else
    EsPrimFail(EsPrimErrInvalidArgCount, EsPrimArgNumNoArg);
}

Nachdem wir also die notwendigen Informationen der Primitive-Umgebung mitgeteilt haben, kommen wir nun zur eigentlichen Methode:

EsUserPrimitive(MSKSquareValueViaMessage)
{
  U_32 rc = EsPrimErrNoError;

  // object holder for the result
  EsObject integerValue;

  // object holder for the message symbol send here
  EsObject symbolObject;

  // A global info has to be stored, because we call getSelector, which is ONLY a function and
  // NOT a primitive and therefore several stuff is not defined here
  GlobalInfo = EsPrimVMContext->globalInfo;

  // we get a new object holding the symbol of the parameter - more or less the same in Smalltalk:
  //  "Hello" asSymbol
  // This operation can cause a garbage collection. 
  symbolObject = GetSelector("squared"); 

  // We check the receiver and if the overall environment is set ...
  if ((EsIsLargeInteger(EsPrimReceiver) || EsIsSmallInteger(EsPrimReceiver)) 
      && (AsSymbolSelector != NULL) 
      && (symbolObject != NULL))
  {
    // Here the Smalltalk message "squared" is send to the receiver - an integer
    // This operation can cause a garbage collection. 
    rc = EsSendMessage(EsPrimVMContext, &integerValue, EsPrimReceiver, symbolObject, 0);

    // It may fail - perhaps to a wrong spelled message name
    if (rc != EsPrimErrNoError)
      EsPrimFail(rc, EsPrimArgNumNoArg);
    else
      EsPrimSucceed(integerValue);
  }
  else
    EsPrimFail(rc, EsPrimArgNumNoArg);	
}

Ich hoffe, das ich diese Methode ausführlich dokumentiert habe. Wichtig dabei ist, dass man immer wissen muss, ob und wann ein Funktions-/Makroaufruf einen GC auslösen kann. Ist dies der Fall, dann muss man die über einen Funktionsaufruf hinaus zu nutzenden Objekte kurzeitig “sichern” und später wieder freigeben. In dem obigen Beispiel aber tritt dieser Fall nie auf, weil eventuell neue Objekte gleich im nächsten Aufruf genutzt werden können.

Die obige Funktion “MSKSquareValueViaMessage” ist eine “Primitive”. Dort ist automatisch die notwendige Smalltalkumgebung gesetzt. Wenn man aber – wie in dem obigen Beispiel – eine Funktion aufruft (um z.B. den Code zu strukturieren), dann muss man eine UserEnvironment setzen. Wie das gemacht wird, kann man im obigen Code sehen und es steht auch in der Dokumentation zu VASmalltalk.

Die obige Primitive ruft eine Funktion auf, die aus einem beliebigen String eine Instanz der Klasse Symbol macht – mittels des oben global gespeicherte Selektor/Symbol #asSymbol.

// This function produces a new object holding an instance of Symbol with a value from nameOfMethod
EsObject GetSelector(char * nameOfMethod)
{
  U_32 rc = EsPrimErrNoError;
  EsObject symbolObject;
  EsObject stringObject;

  // If the #asSymbol has been set ...
  if (AsSymbolSelector != NULL)
  {
    // define the user environment - remember: this is NOT a primitive, but only a function
    EsDefineUserPrimitiveEnvironment(GlobalInfo);

    // Conversion from a c string to an instance of String. New in 8.03
    // This operation can cause a garbage collection. 
    rc = EsCStringToString(nameOfMethod, &stringObject);
    if (rc != EsPrimErrNoError)
      return(NULL);

    // Conversion of an instance of String to an instance of Symbol
    // This operation can cause a garbage collection. 
    rc = EsSendMessage(EsPrimVMContext, &symbolObject, stringObject, AsSymbolSelector, 0);
    if (rc != EsPrimErrNoError)
      return(NULL);
		
    return symbolObject;
  }
  else
    return NULL;	
}

Nebenbei wird hier ebenfalls EsSendMessage genutzt. Der Aufruf im Smalltalk-System erfolgt übrigens dann mit

2 squaredViaMessageSend

und #squaredViaMessageSend ist auf der Instanzseite der Klasse Integer abgelegt:

Integer>>squaredViaMessageSend
  primitive: 'mskcldtprim.dll':MSKSquareValueViaMessage
  ^self primitiveFailed

Nun ja – das war’s für heute – diesmal in Deutsch.

This entry was posted in Smalltalk and tagged , , . Bookmark the permalink.

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