[ENH] Partial support for C++ code generation in CCodeGenerator

Robert M. Fuhrer rfuhrer at watson.ibm.com
Thu Apr 20 19:59:49 UTC 2000


Here's a very small file-in that gives C++-generation capability to the 
CCodeGenerator and friends.

I wrote it while developing a plug-in that interfaces to a C++ DLL.

How does it differ from the ordinary mode? Mostly just two things:

   1) Places generated code in a file with a ".cpp" suffix.
   2) Puts "extern "C"" in the right places so that the plug-in's exported 
routines have the names that the Squeak plug-in mechanism expects them to.

To create a C++ plug-in, just do the usual thing, adding the trivial class 
method "isCPP" to your plug-in class (the one that actually gets translated 
into C/C++). In case it's of use to anyone, I appended below my partial 
recipe for writing Squeak plug-ins using the "named primitive" mechanism. 
What I found on the Web a month or two ago didn't have enough detail for my 
purposes.

The file-in is a delta on the Squeak 2.7 source base. Hope that's still 
useful (I haven't had time to read the mailing list in weeks, so for all I 
know everyone's at 3.0 already :-)).

I invite comments/criticisms on the file-in or the half-recipe below (or 
contributions to make the recipe more accurate/complete). If it's useful, I 
might even put the recipe and other stuff on my Web page (or on the Swiki).

BTW, although the CCodeGenerator mechanism is way cool, I found that I 
ended up writing code fragments like the following, much of which looks 
anything but intuitive:

primitiveDoStuff
	| a b c |
	self export: true.
	self var: #a declareC: 'int a'.
	self var: #b declareC: 'int b'.
	self var: #c declareC: 'int c'.
	myGlobalVar = nil ifTrue: [^ interpreterProxy primitiveFail].
	a _ interpreterProxy stackIntegerValue: 2.
	b _ interpreterProxy stackIntegerValue: 1.
	c _ interpreterProxy stackIntegerValue: 0.
	interpreterProxy failed ifTrue: [^ nil].
	self cCode: 'myGlobalVar->SomeMethod(a, b, c)'.
	interpreterProxy pop: 4
	"rcvr + args"

So, two questions:
   1) Am I doing things oddly?
   2) If not, is anyone working on a mechanism to generate C/C++ code from 
more natural-looking (and safer) Smalltalk code?

=============================================================
Recipe for Plug-in Primitives
=============================================================

On the Squeak side:

  1) Create a "plug-in class" that will house the Smalltalk code from which 
the C/C++ primitive code will be generated. Let's say the class name is 
"FooPlugin".

  2) Plug-in class vars/methods:
     2a) Define the module name by overriding the method 'moduleName':

           FooPlugin class>>moduleName
             ^ 'SqFoo'

     2b) If this is to be a C++ plugin, override the class method isCPP to 
return true:

           FooPlugin class>>isCPP
             ^ true

         This makes the generated file name end in ".cpp", and triggers a 
few other changes in the generated code (like putting an "extern "C"" 
declaration around the routine prototypes).

     2c) For each static global variable that you need to define in the 
generated C/C++ code, add an instance variable to the class definition.

     2d) Override the declareCVarsIn: method in your plug-in class to 
provide C declarations for each static global variable. E.g., add code like 
the following:

           cg var: 'myGlobal' declareC: 'static int *myGlobal = 0'

     2e) If the plug-in DLL needs any header files to compile, add code 
like the following to declareCVarsIn:

           cg addHeaderFile: '"myheaderfile.h"'

  3) Plug-in instance vars/methods:

     3a) For each DLL routine/method you want to expose to Squeak, write a 
corresponding routine that will generate the bridging code. I.e., write 
something like the following:

         primitiveMonoPressure
             | pressure channel |
             self export: true.
             self var: #pressure declareC: 'int pressure'.
             self var: #channel declareC: 'int channel'.
             yumi = nil ifTrue: [^ interpreterProxy primitiveFail].
             pressure := interpreterProxy stackIntegerValue: 1.
             channel := interpreterProxy stackIntegerValue: 0.
             interpreterProxy failed ifTrue: [^ nil].
             self cCode: 'yumi->TMonoPressure(0, channel, pressure, yumiPort)'.
             interpreterProxy pop: 3 "rcvr + args"

         Notes:
           - The statement "self export: true" is a signal to the code 
generator that this routine should be exported.  Otherwise, the routine 
will not be visible to Squeak.  You might not want the routine exposed if 
it's an implementation aid.

	  - The statement "self var: #pressure declareC: 'int pressure'" declares 
a local variable in the C/C++ code that is also a local var to this Squeak 
method. This allows you to write more-or-less (accent on "less") normal 
Smalltalk code to manipulate the variable, and have corresponding code 
generated for C/C++.  Technically, such a declaration isn't necessary, but 
it helps you write more normal-looking Squeak code that gets translated in 
mostly-reasonable ways to C/C++.

           - Note that certain control constructs such as ifTrue:ifFalse:, 
whileTrue:, and so on, are translated to C/C++ by the Squeak code 
generator, so you can use them to do non-trivial work in the bridging code. 
[In fact, this also allows you to write plug-in DLL's that aren't bridges 
at all, but simply compiled to native object code for performance reasons!]

           - The statement "self cCode: 'yumi->TMonoPressure(0, channel, 
pressure, yumiPort)'" is an example of how to pass C/C++ code fragments 
verbatim into the generated C/C++ side.

           - The statement "interpreterProxy pop: 3" is for stack 
clean-up.  It seems that the callee (the Squeak code you write) must do 
this manually :-(.  The rule seems to be that one must pop N+1 stack items 
if the primitive takes N args.

           - There is no explicit declaration of how many parameters this 
primitive will take.  Calls to obtain certain stack items (i.e. parameters) 
are type-specific, and look something like "pressure := interpreterProxy 
stackIntegerValue: 1" or "pressure := interpreterProxy stackObject: 
1".  The index passed to such a stack operation for the i'th [1-based] 
primitive parameter is N-i.  E.g., the first of 2 parameters is accessed 
via "interpreterProxy stackObject: 1".


  4) Create a "plug-in interface class" that will wrap calls to the C++ 
primitives. Let's say it's called "FooInterface", corresponding to 
"FooPlugin" above.

     4a) Write the "plug-in interface" instance methods:

         For each primitive method in the plug-in, write a method of the form:

         FooInterface>>doStuff: value withArg: arg
	<primitive: 'primitiveDoStuffWithArg' module:'sqFoo'>
	^ self

         Take care to make sure that the number and order of the args 
matches the referencing code in your primitive implementation. N.B.: Pay 
attention to the above note that talks about parameters.

         [Here's a safety issue that's waiting to bite people!!! Great 
opportunity to make it easier to write plug-ins by making sure that one 
can't get stack/arg referencing wrong.]


On the C/C++ side (basically, you just need to get the code to compile; the 
description below assumes you're doing so under Win32):

  1) Create a normal empty Win32 DLL project (you can use the VC++ wizard).

  2) Add the Squeak-generated plugin C/C++ source file to the project.

  3) Get or generate the relevant header files

     3a) Call InterpreterSupportCode>>writeSupportFiles, or grab the 
relevant VM source bundle from somewhere. For Win32, I couldn't find all of 
the necessary header files (specifically sqWin32.h and sqWin32Alloc.h) 
anywhere else, nor could I find a way to generate them, so I pulled from 
the VM source archive.

     3b) I found it necessary to tweak sqPlatformSpecific.h so that it 
didn't attempt to (improperly) re-define GetTickCount(). [There was a 
mismatch between the definition there and the canonical one in the Win32 
API header files from Microsoft Visual C++ v.6, which caused a compilation 
error.]

  4) Add the directories containing the VM's header files to your 
compiler's C++ include path.

  5) Make any tweaks to the project file that you need to get your code to 
compile (e.g. additional include/link paths).

  6) Add a post-build step to copy the DLL to E:\Squeak. [Not really 
necessary, but makes one's life easier.]

  7) Compile the project. If you didn't add the post-build step, manually 
copy the DLL to wherever your Squeak VM lives.

  8) Run Squeak, and watch the fur fly!
-------------- next part --------------
A non-text attachment was scrubbed...
Name: Plugin Enhancements.1.cs
Type: application/octet-stream
Size: 2688 bytes
Desc: not available
Url : http://lists.squeakfoundation.org/pipermail/squeak-dev/attachments/20000420/b29226d0/PluginEnhancements.1.obj


More information about the Squeak-dev mailing list