From: Robert M. Fuhrer [rfuhrer@watson.ibm.com] Sent: Donnerstag, 20. April 2000 22:00 To: squeak@cs.uiuc.edu Subject: [ENH] Partial support for C++ code generation in CCodeGenerator 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 ^ 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!