C++ Externals
Introduction
Before making C++ calls in EiffelStudio, you may want to learn the basics of making C calls , since these basics are not repeated here but they might be useful for C++ users as well and are necessary to fully benefit from this document.
Overview
Eiffel software must interact with software written in other languages. In this document you will learn about how to incorporate C++ classes in your Eiffel software.
The C++/Eiffel interface offers the following mechanisms:
- You can create instances of C++ classes from Eiffel, using the C++ "constructor" of your choice.
- You can apply to these objects all the corresponding operations from the C++ class: executing functions ("methods"), accessing data members, executing destructors.
- On Windows, you can use the Legacy++ tool to produce an Eiffel "wrapper class" encapsulating all the features of a C++ class, so that the result will look to the rest of the Eiffel software as if it had been written in Eiffel.
The discussion concentrates on using C++ software from Eiffel. In the other direction, you can use the Cecil library (C-Eiffel Call-In Library) .
Syntax specification
In the following specification, the syntax for External and External_name is retained unchanged from Eiffel: The Language, where Language_name is defined simply as Manifest_string; the rest covers the additional facilities. External == external Language_name [External_name]
Language_name ==
'"'
Basic_language_name
[Special_external_declaration]
[Signature]
[Include_files]
'"'
Basic_language_name == "C" | "C++"
Special_external_declaration == "[" Special_feature "]"
Special_feature == Special_C_feature | Special_C++_feature
Special_C_feature == -- .... See appendix E of Eiffel: The Language...
Special_C++_feature ==
Member_function_call|
Static_function_call |
Object_creation |
Object_deletion |
Data_member_access |
Member_function_call == C++_Class
C++_Class == Class_name File_name
Class_name == Identifier
Static_function_call == static C++_Class
Object_creation == new C++_Class
Object_deletion == delete C++_Class
Data_member_access == data_member C++_Class
-- The remaining elements are repeated
-- from the C interface specification (page 291 in Eiffel: The Environment).
File_name == User_file_name | System_file_name
User_file_name == '%"' Manifest string '%"'
System_file_name == "<" Manifest_string ">"
Signature == "(" Type_list ")" [Result_type]
Type_list == {Type "," ...}
Result_type == ":" Type
Include_files == "|" File_list
File_list == {File_name "," ...}
External_name == alias Manifest_string
As with the C extensions of the previous appendix, the syntax description conventions are those of Eiffel: The Language. == means "is defined as". The vertical bar | separates alternative choices, such as the three possibilities for Special_feature. The brackets [...] enclose optional components; for example a Language_name is made of required quotes, a required Basic_language_name and three further components, any or all of which may be absent. The notation {Element Separator ...} means: 0 or more occurrences ("specimens") of Element separated, if more than one, by Separator.
Several of the symbols of the syntax notation, such as the brackets or the vertical bar, also appear in the language; to avoid any confusion they are given in double quotes. For example the specification for Include_files begins with "|" to indicate that an Include_files part starts with a vertical bar. Single quotes may be used instead of double quotes when one of the quoted characters is itself a double quote; for example '%"' appearing in the production for User_file_name denotes the percent character followed by the double quote character. Special words such as static in bold italics stand for themselves, unquoted.
Available possibilities
Processing C++ features
A Special_C++_feature, if present, indicates one of the following, all illustrated by examples in the next sections:
- If the special feature's declaration simply starts with a C++ class name, followed by the associated file name, it indicates that the Eiffel feature will call a C++ member function (also known as a "method") from that class. The name of the member function is by default the same as the name of the Eiffel feature; as usual, you can specify a different name through the alias clause of the external declaration.
- If the declaration starts with
static
, it indicates a call to a C++ static function. - If the declaration starts with
new
, it indicates a call to one of the constructors in the C++ class, which will create a new instance of that class and apply to it the corresponding constructor function. - If the declaration starts with
delete
, it indicates a call to a destructor from the C++ class. In this case the Eiffel class may inherit MEMORY and redefine the dispose procedure to execute the destructor operations whenever the Eiffel objects are garbage-collected. - If the declaration starts with
data_member
, it indicates access to a data member (attribute in Eiffel terminology) from the C++ class.
The rest of the possible components are the same as in the C interface: Signature to specify types for arguments and results; possible Include file.
Extra argument
For a non-static C++ member function or destructor, the corresponding Eiffel feature should include an extra argument of type POINTER, at the first position. This argument represents the C++ object to which the function will be applied.
For example, a C++ functionvoid add(int new_int);
should have the Eiffel counterpart cpp_add (obj: POINTER; new_int: INTEGER)
-- Encapsulation of member function add.
external
"C++ [IntArray %"intarray.h%"] (IntArray *, int)"
end
This scheme, however, is often inconvenient because it forces the Eiffel side to work on objects in a non-object-oriented way. (The object-oriented way treats the current object, within a class, as implicit.) A better approach, used by Legacy++, is to make a feature such as cpp_add secret, and to export a feature whose signature corresponds to that of the original C++ function, with no extra object argument; that feature will use a secret attribute object_ptr to access the object. In the example this will give the feature add (new_int: INTEGER)
-- Encapsulation of member function add.
do
cpp_add (object_ptr, new_int)
end
where object_ptr is a secret attribute of type
There is no need for an extra argument in the case of static member functions and constructors.
Wrapping C++ classes: Legacy++
Before taking a look at examples of the various facilities mentioned, it is useful to consider the tool that will help you, in many cases, avoid worrying about their details.
The role of Legacy++
Often you will want to provide an Eiffel encapsulation of all the facilities -- member functions, static functions, constructors, destructors, data members -- of a C++ class. This means producing an Eiffel class that will provide an Eiffel feature for each one of these C++ facilities, using external declarations based on the mechanisms listed in the preceding section.
Rather than writing these external declarations and the class structure manually, you can use the Legacy++ tool to produce the Eiffel class automatically from the C++ class.
Calling Legacy++
Legacy++ is called with an argument denoting a .h file that must contain C++ code: one or more classes and structure declarations. It will translate these declarations into Eiffel wrapper classes. Legacy++ is only available on Windows. It is located in the $ISE_EIFFEL\studio\spec\windows\bin directory, under the name legacy.exe.
The following options are available:
- -E: apply the C preprocessor to the file, so that it will process #include, #define, #ifdef and other preprocessor directives. This is in fact the default, so that you do not need to specify -E explicitly (see next option).
- -NE: do not apply the C preprocessor to the file.
- -p directories: use directories as include path.
- -c compiler: use compiler as the C++ compiler.
- -g: treat the C++ code as being intended for the GNU C++ compiler.
Result of applying Legacy++
Running Legacy++ on a C++ file will produce the corresponding Eiffel classes. Legacy++ processes not only C++ classes but also C++ "structs"; in both cases it will generate an Eiffel class.
Legacy++ knows about default specifiers: public for structs, private for classes.
Legacy++ will generate Eiffel features for member functions (static or not).
It will also handle any constructors and destructors given in the C++ code, yielding the corresponding Eiffel creation procedures. If there is no explicit constructor, it will produce a creation procedure with no arguments and an empty body.
For any non-static member function or destructor, Legacy++ will generate a secret feature with an extra argument representing the object, as explained in the preceding section in this page. It will also produce a public feature with the same number of arguments as the C++ function, relying on a call to the secret feature, as illustrated for
The char* type is translated into
Legacy++ limitations
It is up to you to supply Eiffel equivalents of all the needed types. If Legacy++ encounters the name of a C++ class or type that it does not know -- i.e. it is neither a predefined type nor a previously translated class -- it will use the Eiffel type name
Legacy++ does not handle inline function declarations.
Legacy++ does not handle C++ templates.
Legacy++ makes no effort to understand the C++ inheritance structure.
More generally, given the differences in the semantic models of C++ and Eiffel, Legacy++ can only perform the basic Eiffel wrapping of a C++ class, rather than a full translation. You should always inspect the result and be prepared to adapt it manually.
Legacy++'s contribution is to take care of the bulk of the work, in particular the tedious and repetitive parts. The final details are left to the Eiffel software developer.
Legacy++ is only supported on the windows platform.
Example
Consider the following C++ class, which has an example of every kind of facility that one may wish to access from the Eiffel side:class IntArray {
public:
IntArray(int size);
~IntArray();
void output();
void add(int new_int);
static char * type();
protected:
int *_integers;
};
Here is the result of applying Legacy++ to that class, which will serve as an illustration of both the C++ interface mechanisms and Legacy++:note
description: "Eiffel encapsulation of C++ class IntArray";
date: "$Date: 2006-10-12 03:18:50 +0200 (Thu, 12 Oct 2006) $";
revision: "$Revision: 64319 $"
class
INTARRAY
inherit
MEMORY
redefine
dispose
end
create
make
feature -- Initialization
make (size: INTEGER)
-- Create Eiffel and C++ objects.
do
object_ptr := cpp_new (size)
end
feature-- Removal
dispose
-- Delete C++ object.
do
cpp_delete (object_ptr)
end
feature
output
-- Call C++ counterpart.
do
cpp_output (object_ptr)
end
add (new_int: INTEGER)
-- Call C++ counterpart.
do
cpp_add (object_ptr, new_int)
end
feature {INTARRAY}
underscore_integers: POINTER
-- Value of corresponding C++ data member.
do
Result := underscore_integers (object_ptr)
end
feature {NONE} -- Externals
cpp_new (size: INTEGER): POINTER
-- Call single constructor of C++ class.
external
"C++ [new IntArray %"INTARRAY.H%"] (EIF_INTEGER)"
end
cpp_delete (cpp_obj: POINTER)
-- Call C++ destructor on C++ object.
external
"C++ [delete IntArray %"INTARRAY.H%"] ()"
end
cpp_output (cpp_obj: POINTER)
-- Call C++ member function.
external
"C++ [IntArray %"INTARRAY.H%"] ()"
alias
"output"
end
cpp_add (cpp_obj: POINTER; new_int: INTEGER)
-- Call C++ member function.
external
"C++ [IntArray %"INTARRAY.H%"] (EIF_INTEGER)"
alias
"add"
end
cpp_underscore_integers (cpp_obj: POINTER): POINTER
-- Value of C++ data member
external
"C++ [data_member IntArray %"INTARRAY.H%"]: EIF_POINTER"
alias
"_integers"
end
feature {NONE} -- Implementation
object_ptr: POINTER
end -- class INTARRAY