C cli что это
Перейти к содержимому

C cli что это

  • автор:

Кратко и быстро разбираемся с C++ CLI

Так сложилось, что по мере рабочей необходимости мне приходится интенсивно использовать C++/CLI и, соответственно, довольно часто объяснять новичкам, что это, как работает, как использовать и, зачем вообще надо. Так что со временем появилось желание написать статью с обзором языка, ответами на некоторые распространенные вопросы и показать места, где могут удачно лечь грабли.

Что это?

Когда Microsoft создавала платформу .Net, то она решила дать программистам писать под нее на нескольких уже существующих языках, в синтаксис которых были внесены некоторые изменения — VB, C++. Речь пойдет именно о последнем. Точнее, если мне не изменяет память, в первой редакции язык назывался C++ with managed extensions. Название как бы само намекает на сущность — вот мы вам дали плюсы с расширениями и теперь вы сможете разрабатывать под .Net на уже известных C++, при этом оставив всю мощь исходного языка. Собственно, первая версия расширения синтаксиса была ужасна чуть более чем полностью, и делала код похожим на попытку закодировать и отправить в космос диалог Жириновского и Черномырдина:

К тому же, в этом синтаксисе не было отличий между указателем на нативный тип и на управляемый (в обоих случаях использовалась «*»), не было ключевого слова для обозначения нулевого указателя и прочее. Это и толкнуло Microsoft на создание новой ревизии языка, о которой и пойдет речь в данной статье.
Замечание: эти две версии синтаксиса называются, как ни странно, «old syntax» и «new syntax», и какую именно использовать можно выбирать в настройках компиляции проекта. Впрочем, при создании новых сборок лучше использовать новый синтаксис, так как старый помечен как устаревший и просто плох.

Зачем нужно?

1) С помощью данного языка сферический программист в вакууме сможет разрабатывать полноценное .Net-приложение на любимых плюсах. Честно говоря, мне сложно представить этого извращенца, да и Microsoft такому подходу явно не способствует хотя бы тем, что не делает под C++ дизайнера визуальных компонент. Собственно, правильно делает, ибо для таких целей есть более выразительные языки, тот же C#. Так что это возможность скорее теоретическая.

2) Можно вызывать уже написанный на плюсах код. Действительно, поскольку у нас остались все возможности обычного C++, то можно создавать управляемые обертки для существующих классов на нативных плюсах. Это дает намного большие возможности по вызову неуправляемого кода, нежели PInvoke, который с классами работать не умеет.

3) Можно писать на C++ CLI модули, где производительность критична. Действительно, среди всего зоопарка языков под .Net С++ уникален тем, что на нем можно писать код, который напрямую использует выделение и освобождение памяти, работу с указателями. Например, так:

//управляемый класс, который может потом использоваться любой .Net–сборкой
public ref Class1
<
public :
//этот метод так же можно использовать во всех .Net-сборках
void Method1();
<
BYTE *buff = new BYTE[100];

* This source code was highlighted with Source Code Highlighter .

Как работает?

Все очень просто. При компиляции кода на С++/СLI получается сборка, содержащая как MSIL код, так и машинные команды, в которые превратились строки, написанные на «чистых» плюсах. «Но как же быть с кроссплатформеностью?» — спросите вы, и будете совершенно правы. Никак. В частности, это означает, что не получится собрать один и тот же бинарник для 32 и 64 битных версий (собрать все под «Any CPU»). Такова расплата за использование всех возможностей С++. Естественно, это относится к тому варианта, когда используется микс из управляемого и неуправляемого кода. Всего есть несколько вариантов компиляции:
• /clr — поддержка управляемого и нативного кода с использованием нового синтаксиса.
• /сlr:pure — нативный код не поддерживается. Однако при этом можно использовать небезопасный ) код, в той мере, как это можно делать, к примеру, в С#-сборках при использовании директивы unsafe.
• /clr:safe — Только управляемый безопасный код. Аналог — компиляция C#-сборки без использования unsafe.
• /clr:oldSyntax — аналог /clr, только используется старый синтаксис.

Как выглядит?

Вот примеры сравнения основных конструкций для С# и C++/CLI.

Объявление класса

public sealed class Class1 : Class2

* This source code was highlighted with Source Code Highlighter .

public ref class Class1 sealed : Class2

* This source code was highlighted with Source Code Highlighter .

Объявление структуры

public struct Class1 : IEquatable

* This source code was highlighted with Source Code Highlighter .

public value class Class1 : IEquatable

* This source code was highlighted with Source Code Highlighter .

Объявление интерфейса

public interface ISomeInterface

* This source code was highlighted with Source Code Highlighter .

public interface class ISomeInterface

* This source code was highlighted with Source Code Highlighter .

Объявление перечисления
Создание управляемого объекта

* This source code was highlighted with Source Code Highlighter .

В C++/CLI для обозначения ссылок на управляемые объекты используется «^» вместо «*». Это очень удобно чтобы различать объекты, которые потом надо удалить и те, которые не надо. Также при создании локального ссылочного объекта можно использовать семантику стека:
Object obj();
Это имеет смысл либо при использовании объектов, реализующих IDisposable (речь об этом пойдет позже) либо для value-типов. Заметим, что в плане хранения и использования value-типов С++/CLI дает большую свободу, чем C#, поскольку программист может сам выбирать — использовать ссылку или значение. Таким образом вполне можно в некоторых ситуация сэкономить на количестве boxing/unboxing операций.

Создание управляемого массива

* This source code was highlighted with Source Code Highlighter .

* This source code was highlighted with Source Code Highlighter .

Неуправляемые массивы при этом создаются как обычно.

Передача параметров в метод

void Method( int byValue, ref int byRef, out int outValue);

* This source code was highlighted with Source Code Highlighter .

void Method( int byValue, int %byRef, [ out ] int %outValue);

* This source code was highlighted with Source Code Highlighter .

* This source code was highlighted with Source Code Highlighter .

Переопределение метода

* This source code was highlighted with Source Code Highlighter .

Реализация шаблона IDisposable

class Class1 : Disposable
<
public void Dispose()
<
this .Dispose( true );

protected virtual void Dispose( bool disposing)
<
if (disposing)
<
//release managed resources
>

//release unmanaged resources
>

Class1()
<
this .Dispose( false );
>
>

* This source code was highlighted with Source Code Highlighter .

Class1()
<
//release managed resources

//call finalizer
this ->!Class1();
>

//Аналог финализатора
!Class1()
<
//release unmanaged resources
>
>

* This source code was highlighted with Source Code Highlighter .

* This source code was highlighted with Source Code Highlighter .

B();
>

* This source code was highlighted with Source Code Highlighter .

using (Class1 obj = new Class1())
<
B();
>
>

* This source code was highlighted with Source Code Highlighter .

Что осталось за кадром?

Понятно, что в одну статью все поместить не удалось. Не рассмотренными остались такие вопросы как:
• Синтаксис делегатов, свойств, методов расширения, foreach и прочее
• Жонглирование из управляемого в неуправляемый и обратно объектами, массивами и прочим
• Что поддерживается и нет из того, что есть в С# и в обычном C++
• Особености компиляции приложений со сборками С++/CLI
• Вопросы производительности. Когда и в чем можно получить выигрыш? Где можно внезапно потерять?

Что почитать?
Заключение

Среди всех остальных языков под .Net C++/CLI является довольно специфичным. Вряд ли на нем имеет смысл разрабатывать стандартное .Net приложение, зато в качестве коммуникации с существующим C++ кодом или для задач оптимизации – самое оно.

1 Introduction

Command Line Interface (CLI) definition language is a domain-specific language (DSL) for defining command line interfaces of C++ programs. CLI definitions are automatically translated to C++ classes using the CLI compiler. These classes implement parsing of the command line arguments and provide a convenient and type-safe interface for accessing the extracted data.

Beyond this guide, you may also find the following sources of information useful:

  • CLI Compiler Command Line Manual
  • The INSTALL file in the CLI distribution provides build instructions for various platforms.
  • The examples/ directory in the CLI distribution contains a collection of examples and a README file with an overview of each example.
  • The cli-users mailing list is the place to ask technical questions about the CLI language and compiler. Furthermore, the cli-users mailing list archives may already have answers to some of your questions.

2 Hello World Example

In this chapter we will examine how to define a very simple command line interface in CLI, translate this interface to C++, and use the result in an application. The code presented in this chapter is based on the hello example which can be found in the examples/hello/ directory of the CLI distribution.

2.1 Defining Command Line Interface

Our hello application is going to print a greeting line for each name supplied on the command line. It will also support two command line options, —greeting and —exclamations , that can be used to customize the greeting line. The —greeting option allows us to specify the greeting phrase instead of the default «Hello» . The —exclamations option is used to specify how many exclamation marks should be printed at the end of each greeting. We will also support the —help option which triggers printing of the usage information.

We can now write a description of the above command line interface in the CLI language and save it into hello.cli :

While some details in the above code fragment might not be completely clear (the CLI language is covered in greater detail in the next chapter), it should be easy to connect declarations in hello.cli to the command line interface described in the preceding paragraphs. The next step is to translate this interface specification to C++.

2.2 Translating CLI Definitions to C++

Now we are ready to translate hello.cli to C++. To do this we invoke the CLI compiler from a terminal (UNIX) or a command prompt (Windows):

This invocation of the CLI compiler produces three C++ files: hello.hxx hello.ixx , and hello.cxx . You can change the file name extensions for these files with the compiler command line options. See the CLI Compiler Command Line Manual for more information.

The following code fragment is taken from hello.hxx ; it should give you an idea about what gets generated:

The options C++ class corresponds to the options CLI class. For each option in this CLI class an accessor function is generated inside the C++ class. The options C++ class also defines a number of overloaded constructs that we can use to parse the argc/argv array. Let’s now see how we can use this generated class to implement option parsing in our hello application.

2.3 Implementing Application Logic

At this point we have everything we need to implement our application:

At the beginning of our application we create the options object which parses the command line. The end variable contains the index of the first non-option argument. We then access the option values as needed during the application execution. We also catch and print cli::exception in case something goes wrong, for example, an unknown option is specified or an option value is invalid.

2.4 Compiling and Running

After saving our application from the previous section in driver.cxx , we are ready to build and run our program. On UNIX this can be done with the following commands:

We can also test the error handling:

2.5 Adding Documentation

As we have seen in the previous sections, the options C++ class provides the print_usage() function which we can use to display the application usage information. Right now this information is very basic and does not include any description of the purpose of each option:

To make the usage information more descriptive we can document each option in the command line interface definition. This information can also be used to automatically generate program documentation in various formats, such as HTML and man page. For example:

If we now save this updated command line interface to hello.cli and recompile our application, the usage information printed by the program will look like this:

We can also generate the program documentation in the HTML ( —generate-html CLI option) and man page ( —generate-man CLI option) formats. For example:

The resulting hello.html file contains the following documentation:

—help Print usage information and exit. —greeting text Use text as a greeting phrase instead of the default «Hello». —exclamations num Print num exclamation marks instead of 1 by default.

This HTML fragment can be combined with custom prologue and epilogue to create a complete program documentation ( —html-prologue/—html-epilogue options for the HTML output, —man-prologue/—man-epilogue options for the man page output). For an example of such complete documentation see the CLI Compiler Command Line Manual and the cli(1) man page. For more information on the option documentation syntax, see Section 3.3, Option Documentation.

3 CLI Language

This chapter describes the CLI language and its mapping to C++. A CLI file consists of zero or more Include Directives followed by one or more Namespace Definitions or Option Class Definitions. C and C++-style comments can be used anywhere in the CLI file except in character and string literals.

3.1 Option Class Definition

The central part of the CLI language is option class. An option class contains one or more option definitions, for example:

If we translate the above CLI fragment to C++, we will get a C++ class with the following interface:

An option class is mapped to a C++ class with the same name. The C++ class defines a set of public overloaded constructors, a public copy constructor and an assignment operator, as well as a set of public accessor functions and, if the —generate-modifier CLI compiler option is specified, modifier functions corresponding to option definitions. It also defines a public static print_usage() function that can be used to print the usage information for the options defined by the class.

The argc/argv arguments in the overloaded constructors are used to pass the command line arguments array, normally as passed to main() . The start argument is used to specify the position in the arguments array from which the parsing should start. The constructors that don’t have this argument, start from position 1, skipping the executable name in argv[0] . The end argument is used to return the position in the arguments array where the parsing of options stopped. This is the position of the first program argument, if any. If the erase argument is true , then the recognized options and their values are removed from the argv array and the argc count is updated accordingly.

The opt_mode and arg_mode arguments specify the parser behavior when it encounters an unknown option and argument, respectively. The unknown_mode type is part of the generated CLI runtime support code. It has the following interface:

If the mode is skip , the parser skips an unknown option or argument and continue parsing. If the mode is stop , the parser stops the parsing process. The position of the unknown entity is stored in the end argument. If the mode is fail , the parser throws the cli::unknown_option or cli::unknown_argument exception (described blow) on encountering an unknown option or argument, respectively.

Instead of the argc/argv arguments, the last overloaded constructor accepts the cli::scanner object. It is part of the generated CLI runtime support code and has the following abstract interface:

The CLI runtime also provides two implementations of this interface: cli::argv_scanner and cli::argv_file_scanner . The first implementation is a simple scanner for the argv array (it is used internally by all the other constructors) and has the following interface:

The cli::argv_file_scanner implementation provides support for reading command line arguments from the argv array as well as files specified with command line options. It is generated only if explicitly requested with the —generate-file-scanner CLI compiler option and has the following interface:

The file_option argument is used to pass the option name that should be recognized as specifying the file containing additional options. Such a file contains a set of options, each appearing on a separate line optionally followed by space and an option value. Empty lines and lines starting with # are ignored. The semantics of providing options in a file is equivalent to providing the same set of options in the same order on the command line at the point where the options file is specified, except that shell escaping and quoting is not required. Multiple files can be specified by including several file options on the command line or inside other files.

The parsing constructor (those with the argc/argv or cli::scanner arguments) can throw the following exceptions: cli::unknown_option , cli::unknown_argument , cli::missing_value , and cli::invalid_value . The first two exceptions are thrown on encountering unknown options and arguments, respectively, as described above. The missing_value exception is thrown when an option value is missing. The invalid_value exception is thrown when an option value is invalid, for example, a non-integer value is specified for an option of type int .

https://amdy.su/wp-admin/options-general.php?page=ad-inserter.php#tab-8

Furthermore, all scanners (and thus the parsing constructors that call them) can throw the cli::eos_reached exception which indicates that one of the peek() , next() , or skip() functions were called while more() returns false . Catching this exception normally indicates an error in an option parser implementation. The argv_file_scanner class can also throw the cli::file_io_failure exception which indicates that a file could not be opened or there was a reading error.

All CLI exceptions are derived from the common cli::exception class which implements the polymorphic std::ostream insertion. For example, if you catch the cli::unknown_option exception as cli::exception and print it to std::cerr , you will get the error message corresponding to the unknown_option exception.

The exceptions described above are part of the generated CLI runtime support code and have the following interfaces:

3.2 Option Definition

An option definition consists of four components: type, name, default value, and documentation. An option type can be any C++ type as long as its string representation can be parsed using the std::istream interface. If the option type is user-defined then you will need to include its declaration using the Include Directive.

An option of a type other than bool is expected to have a value. An option of type bool is treated as a flag and does not have a value. That is, a mere presence of such an option on the command line sets this option’s value to true .

The name component specifies the option name as it will be entered in the command line. A name can contain any number of aliases separated by | . The C++ accessor and modifier function names are derived from the first name by removing any leading special characters, such as — , / , etc., and replacing special characters in other places with underscores. For example, the following option definition:

Will result in the following accessor function:

While any option alias can be used on the command line to specify this option’s value.

If the option name conflicts with one of the CLI language keywords, it can be specified as a string literal:

The following component of the option definition is the optional default value. If the default value is not specified, then the option is initialized with the default constructor. In particular, this means that a bool option will be initialized to false , an int option will be initialized to 0 , etc.

Similar to C++ variable initialization, the default option value can be specified using two syntactic forms: an assignment initialization and constructor initialization. The two forms are equivalent except that the constructor initialization can be used with multiple arguments, for example:

The assignment initialization supports character, string, boolean, and simple integer literals (including negative integers) as well as identifiers. For more complex expressions use the constructor initialization or wrap the expressions in parenthesis, for example:

By default, when an option is specified two or more times on the command line, the last value overrides all the previous ones. However, a number of standard C++ containers are handled differently to allow collecting multiple option values or building key-value maps. These containers are std::vector , std::set , and std::map .

When std::vector or std::set is specified as an option type, all the values for this option are inserted into the container in the order they are encountered. As a result, std::vector will contain all the values, including duplicates while std::set will contain all the unique values. For example:

If we have a command line like this: -v 1 -v 2 -v 1 -s 1 -s 2 -s 1 , then the vector returned by the vector() accessor function will contain three elements: 1 , 2 , and 1 while the set returned by the set() accessor will contain two elements: 1 and 2 .

When std::map is specified as an option type, the option value is expected to have two parts: the key and the value, separated by = . All the option values are then parsed into key/value pairs and inserted into the map. For example:

The possible option values for this interface are: -m a=A , -m =B (key is an empty string), -m c= (value is an empty string), or -m d (same as -m d= ).

The last component in the option definition is optional documentation. It is discussed in the next section.

3.3 Option Documentation

Option documentation mimics C++ string array initialization: it is enclosed in <> and consists of one or more documentation strings separated by a comma, for example:

The option documentation consists of a maximum of three documentation strings. The first string is the value documentation string. It describes the option value and is only applicable to options with types other than bool (options of type bool are flags and don’t have an explicit value). The second string (or the first string for options of type bool ) is the short documentation string. It provides a brief description of the option. The last entry in the option documentation is the long documentation string. It provides a detailed description of the option. The short documentation string is optional. If only two strings are present in the option documentation (one string for options of type bool ), then the second (first) string is assumed to be the long documentation string.

Option documentation is used to print the usage information as well as to generate program documentation in the HTML and man page formats. For usage information the short documentation string is used if provided. If only the long string is available, then, by default, only the first sentence from the long string is used. You can override this behavior and include the complete long string in the usage information by specifying the —long-usage CLI compiler option. When generating the program documentation, the long documentation strings are always used.

The value documentation string can contain text enclosed in <> which is automatically recognized by the CLI compiler and typeset according to the selected output in all three documentation strings. For example, in usage the level value for the —compression option presented above will be displayed as <level> while in the HTML and man page output it will be typeset in italic as level . Here is another example using the std::map type:

The resulting HTML output for this option would look like this:

—map key=value Add the key, value pair to the map.

As you might have noticed from the examples presented so far, the documentation strings can span multiple lines which is not possible in C++. Also, all three documentation strings support the following basic formatting mechanisms. The start of a new paragraph is indicated by a blank line. A fragment of text can be typeset in monospace font (normally used for code fragments) by enclosing it in the \c<> block. Similarly, text can be typeset in bold or italic fonts using the \b<> and \i<> blocks, respectively. You can also combine several font properties in a single block, for example, \cb . If you need to include literal > in a formatting block, you can use the \> escape sequence, for example, \c> . The following example shows how we can use these mechanisms:

The resulting HTML output for this option would look like this:

—compression level Set compression to level instead of 5 by default.

With the higher compression levels the program may produce a smaller output but may also take longer and use more memory.

3.4 Include Directive

If you are using user-defined types in your option definitions, you will need to include their declarations with the include directive. Include directives can use < > or » » -enclosed paths. The CLI compiler does not actually open or read these files. Instead, the include directives are translated to C++ preprocessor #include directives in the generated C++ header file. For example, the following CLI definition:

Will result in the following C++ header file:

Without the #include directives the std::string and name_type types in the options class would be undeclared and result in compilation errors.

3.5 Namespace Definition

Option classes can be placed into namespaces which are translated directly to C++ namespaces. For example:

The above CLI namespace structure would result in the equivalent C++ namespaces structure:

What is the point of C++/CLI?

I am wondering what the uses of C++/CLI is. It seems to me that it is basically C++ running on .Net, am I wrong in this thinking? What is it good for? Why not just use C# or some other truly managed language instead?

4 Answers 4

Here are a couple of advantages of C++/CLI over simply C++ or say C#

  • It’s a great language for writing a large component which interops between native and managed code.
  • Provides a fast(er) conversion path from a purely native C++ code base to a purely managed one. Without C++/CLI your best option would be a rewrite

C++/CLI has a few interesting things that C# does not have:

Strongly-typed boxing. If you box an int to an object in C#, you lose any information about what the original type was. Not the case in C++/CLI.

A distinction between destructors and finalizers. In C# you have to manually implement IDisposable and remember to call Dispose . In C++/CLI, you just chuck the cleanup code in the destructor (which automatically gets compiled into a Dispose method), and put the managed-only cleanup code in the finalizer.

A distinction between stack and heap semantics for variables. Stack is the default, and stack-allocated reference types will automatically be destroyed (disposed) — you don’t have to remember to delete them, just let them go out of scope. To make a long story short, it’s a lot easier to deal with unmanaged resources in C++/CLI than any other .NET language.

Function pointers. This is less of a distinction now with anonymous delegates and lambda syntax in C#, but back in 2005 this was kind of a big deal.

Access to all of the access modifiers implemented in the CLR. You can explicitly specify public, protected or private for both the same assembly and external assemblies. All you have in C# (other than the obvious public , protected , private ) are the internal and protected internal modifiers, the former meaning «public internally, private externally» and the latter meaning «public internally, protected externally». In C++ you can make a class «protected AND internal», which means that the member is only accessible to derived classes in the same assembly.

C cli что это

Thank you for writing this article.

Thanks for your feedback.

I did not think of this as a complete project but rather snippets of code, that’s why I did not submit the source code as I do in my other article.

I can’t help it! 😛

Yes you’re right, some people reported leaks and problems w/ the code. It seems I was not very experienced back then (in 2007).

copy/paste? Quite possible there are some code examples modified and used from somewhere else, I don’t remember.

Or asking the staff for it. I.e. @Sean-Ewington

If something has a solution. Why do we have to worry about?. If it has no solution. For what reason do we have to worry about?
Help me to understand what I’m saying, and I’ll explain it better to you
Rating helpful answers is nice, but saying thanks can be even nicer.

StudentWrapper(String ^fullname, double gpa)
<
_stu = new Student((char *)
System::Runtime::InteropServices::Marshal::StringToHGlobalAnsi(fullname).ToPointer(), gpa);
>

The problem that I see is that StringToHGlobalAnsi() allocates memory which must later be released using FreeHGlobal(). So I believe a correct version of the above code would be:

StudentWrapper(String ^fullname, double gpa)
<
IntPtr p = System::Runtime::InteropServices::Marshal::StringToHGlobalAnsi(fullname);
_stu = new Student((char *)p.ToPointer(), gpa);
System::Runtime::InteropServices::Marshal::FreeHGlobal(p);
>

«Before you proceed, it is recommended that you warm-up your neurons and take a look at these two articles [1] & [8].»

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *