PChar — Тип Delphi


Разница между PAnsiChar и PChar

Есть ли разница и какой тип между PAnsiChar и PChar? (в Delphi в предыдущем 2007 году)

В D2009 и более поздних версиях: да, есть. PChar является указателем на Char , который является символом Unicode (a WideChar ). И PAnsiChar — это указатель на AnsiChar , который — как следует из названия — символ ANSI.

EDIT. Для версий Delphi PChar и PAnsiChar до 2009 года точно такие же. Оба они указывают на символ (Ansi).

Я обратил внимание на «In Delphi previous 2007» в вашем вопросе, который я подразумеваю «В Delphi 2007 и ранее», поэтому.

В Delphi 2007 и предыдущие PChar и PANSIChar являются синонимами. Они означают одно и то же — указатель на значение ANSIChar. Char является синонимом ANSIChar в этих версиях.

Однако в Delphi 2009 и более поздних версиях Char становится синонимом WideChar, поэтому PChar становится синонимом PWideChar.

Обратите внимание, что WideChar не является символом Unicode. Unicode просто не так прост. Любое значение WideChar может быть кодовым пунктом в BMP (Basic Multilingual Plane) или может быть либо одной из суррогатной пары. Это может быть также диакритический знак — например, «акцент», который должен применяться к непосредственно предшествующему кодовому центру в WideString.

Понятие «символ» в Юникоде не легко сопоставляется с одним значением любого типа.

PChars: no strings attached

The string is a stark data structure and everywhere it is passed there is much duplication of process. It is a perfect vehicle for hiding information. — Alan Perlis

In the public Delphi newsgroups on the Embarcadero server, or in the Delphi tags on StackOverflow, I often see that there is still great confusion about the PChar type on one, and the string type on the other hand. In this article I would like to discuss the similarites and the differences between both types, as well as some things you should or shouldn’t do with them.

The general principles layed out in this article apply to all Win32, Win64 and OS X versions of Delphi, including Delphi 2009 and up. There is, however, a special “chapter” at the end of this article especially for those who use Delphi 2009 and up.

PChar

Trying to outsmart a compiler defeats much of the purpose of using one. — Kernighan and Plauger, The Elements of Programming Style .

PChars were inspired by strings, as used in the C language. Most Windows API functions have a C interface, and accept C style strings. To be able to use APIs, Borland had to introduce a type that mimicked them, in the ancestor of Delphi, Turbo Pascal.

In C, there is no real string type, not like there is in Delphi. Strings are just arrays of characters, and the end of the text is marked by a character with ASCII code zero. This allows them to be very long (unlike Turbo Pascal’s string type, which was limited to 255 characters and a length byte – this is Delphi’s ShortString type now), but a bit awkward to use. The beginning of the array is simply marked by a char * , which is a pointer to a char . The exact Delphi equivalent is ^Char . This has become the type PChar in Turbo Pascal and Delphi.

To traverse a string in C, you can increment or decrement the pointer using code like p++ or —s , or use the pointer as if it were an array — this is true for all pointers in C — and use s[20] to indicate the 21st character — counting starts at 0 . But C pointer arithmetic not only allows incrementing and decrementing the pointer, it also allows calculating the sum of a pointer and a number, or the difference between two pointers. In C, *(s + 20) is equivalent to s[20] ( * is the C pointer operator, much like Delphi’s ^ ). Borland introduced almost the same syntax for the PChar type in Turbo Pascal, if the <$X+>(extended syntax) directive was set.

In Delphi version 2009 and up, pointer arithmetic (or pointer math, as the Delphi developers called it) is supported for all pointer types, if the directive <$POINTERMATH ON>is used.

Just a pointer

Despite the slighlty extended syntax and handling described further on, never forget that a PChar is just a pointer, like in C. And also like in C, you can use it as if it were an array (i.e. the pointer points to the first character in the array). But it isn’t! A PChar has no automatic storage, like the convenient Delphi type string . If you copy text to a PChar -“string”, you must always make sure that the PChar actually points to a valid array, and that the array is large enough to hold the text.

Like in C, a PChar variable merely points to a Char . Usually, as in C, this Char is part of an array of Char that ends in a Char with ordinal value 0 and such an array is often used to pass text around between functions, but there is no guarantee that the character is part of a larger array, and there is no guarantee that there is a 0 at the end. This is only a convention.

And like with any other pointers, you can make mistakes with them.

The code above did not allocate storage for the string, so it tries to store the characters starting at some undefined location in memory (the address that is formed by the bit pattern that P happens to hold before it is assigned the address of determined memory location is undefined, see my article on pointers). This can cause problems, like memory corruption and even lead to a program crash, or — worse — wrong results. It is your responsibility to ensure that the array exists. The easiest way is to use a local array:

The above code stores the characters in the array. But if you try to display the string at S , it will probably display lots of nonsense. That is because the string didn’t end in a #0 character. OK, you could simply add another line:

and you would get a display of the text «D6» . But storing characters one by one is really inconvenient. To display a text via a PChar is much simpler: you simply set the PChar to an already existing array with a text in it. Luckily, string constants like ‘Delphi’ are also such arrays, and can be used with PChars :

You should however be aware that that only changes the value of the pointer S . No text is moved or copied around. The text is simply stored somewhere in the program (and has a #0 delimiter), and S is pointed to its start address. If you do:

this does not copy the text ‘Delphi’ to the array A . Line 6 points S to the array A , but immediately after that, the next line only changes S (a pointer!) to the address of the literal string. If you want to copy text to the array, you must do that using, for instance, StrCopy or StrLCopy :

In this simple case it is obvious that ‘Delphi’ will generously fit in the array, so the use of StrLCopy seems a bit overdone, but in other occasions, where you don’t know the size of the string, you should use StrLCopy to avoid overrunning the array bounds.

A static array like A is useful as a text buffer for small strings of a known maximum size, but often you’ll have strings of a size which is unknown when the program is compiled. In that case you’ll have to use dynamic allocation of a text buffer. You can for instance use StrAlloc or StrNew to create a buffer, or GetMem , but then you’ll have to remember to free the memory again, using StrDispose or FreeMem . If you wanted to avoid low level routines, you could use a dynamic array of Char (or TArray ), but that is not quite as convenient as using a Delphi string as a text buffer. But before I describe how to do that, I want to discuss that type first.

String

A world without string is chaos — Randolf Smuntz, Mouse Hunt

Allow me to confuse you: a string or, more precise, AnsiString (in Delphi 2009 and higher: UnicodeString ) is in fact a PChar . Just as a PChar , it is a pointer to an array of characters, terminated by a #0 character. But there is one big difference. You normally don’t have to think about how they work. They can be used almost like any other variable. The compiler takes care that the appropriate code to allocate, copy and free the text is called. So instead of calling routines like StrCopy , the compiler takes care of such chores for you.

But there is more. Although the text is sure to be always terminated by a #0 , just to make AnsiStrings compatible with C-style strings, the compiler doesn’t need it. In front of the text in memory, at a negative offset, the length of the string is stored, as an Integer. So to know the length of the string, the compiler only has to read that Integer, and not count characters until it finds a #0 . That means that you can store #0 characters in the middle of the string without confusing the compiler. But some output routines, which rely on the #0 and not on the length, might be confused.

Normally, each time you’d assign one string to another variable, the compiler would have to allocate memory and copy the entire string to it. Because Delphi strings can be quite long (theoretically, up to 2GB), this could be slow. To avo >0 , the string text is not referenced anymore, and the memory can be freed.

The compiler takes care that the reference count is always correct (but you can confuse the compiler by casting – more on that later). If a string variable is declared in a var section of a function or procedure, or as a field of a class or record, it starts its life as nil , the internal representation of the empty string ( » ). As soon as string text is created and assigned to one of these variables, the reference count of the string is updated to 1 . Each additional assignment of that particular string to a new variable increments its reference count. If a string variable leaves its scope (when the function or class in which it was declared ends), or is pointed to a new string, the reference count of the text is decremented.

A simple example:

Now S1 points to the text ‘123456’ and has a reference count of 1 .

No text is copied yet, S2 is simply set to the same address as S1 , but the reference count of the text ‘123456’ is 2 now.

Now a new, larger buffer is allocated, the text ‘The number is ‘ is copied to it, and the text from ‘123456’ concatenated. But, since S2 doesn’t point to the original text ‘123456’ anymore, the reference count of that text is decremented to 1 again.

Result is set to point to the same address as S2 , and the reference count of the text ‘The number is 123456’ is incremented to 2 .

Now S1 and S2 leave their scope. The reference count for ‘123456’ is decremented to 0 , and the text buffer is freed. The reference count for ‘The number is 123456’ is decremented too, but only to 1 , since the function result still points to it. So although the function has ended, the string is still around.

What is important to notice here is that strings are more or less independent of the variables or fields that reference them. Only the number of references is important. If that is not 0 , the string is still referenced somewhere and it must remain in memory. If it becomes 0 , the memory for the string and its associated data (length, reference count, codepage) can be freed.

Complicated? Yes, it is complicated, and can get even more complicated with var , const and out parameters. But fortunately, you normally don’t have to worry about this. Only if you access strings in assembler, or using a typecast to a PChar , this can become important to know. But using strings with a typecast to PChar is something which is not uncommon.

The most importants things to remember about strings are

  • that text is only copied to a new string buffer if it is modified;
  • that the reference count and the length are not connected to a string variable, but to a specific text buffer (also known as payload), to which more than one string variable can point;
  • that the reference count is always correct unless you fool the compiler by casting to a different type;
  • that assignments to a variable decrement the reference count of the text buffer it previously pointed to;
  • that if the reference count becomes 0 , the string buffer is freed.

Using strings and PChars together

If you can’t be a good example, then you’ll just have to be a horrible warning. — Catherine Aird

PChars and character arrays are awkward to use. Most of the time, you must allocate memory, and not forget to free it. If you want to add text, you must first calculate the size of the resulting text, reallocate the text buffer if it is too small, and use StrCat or StrLCat to finally add the text. You must use StrComp or StrLComp to compare strings, etc. etc.

Strings, on the other hand, are much simpler to use. Most things are done automatically. But many Windows (or Linux) API functions require PChars , and not strings . Fortunately, since strings are also pointers to zero-terminated text, you can use them as a PChar by simply casting them:

Don’t forget that a string variable is a pointer to text, and not a text buffer itself. If the text is modified, it is often copied to a new location, and the address in the variable is adjusted accordingly. That means that you should not use a PChar to point to the string and then modify the string . It is best to avoid doing something like:

If S is changed to ‘Something else’ , P is not changed with it, and still points to ‘C:\Test.exe’ . Since P is not a string reference to that text, and there is no other string variable pointing to it, its reference count is decremented to 0 , and the text is discarded. That means that P now points to invalid memory and has become a so-called dangling pointer.

In the above, I originally had a link to the Wikipedia article on dangling pointers, but that contains so much nonsense, lack of insight and bad advice for the avoidance of dangling pointers that I removed it again. If these people do not know if a pointer under their control points to valid memory or not, there is something terribly wrong with their design.

It is wise not to confuse the compiler by mixing PChar and string variables, unless you know what you do. The compiler does not recognize a PChar as a string , so it does not change the reference count of the string memory, if you reference it with a PChar . It is often better not to use a PChar variable like this at all. Simply use the string type as much as possible, and only cast to PChar at the last possible moment. Functions accepting a PChar parameter should copy the text they receive to their own buffer as soon as possible, so it doesn’t matter what happens to the original.

Normally, string buffers are only as large as necessary to contain the text assigned to them. But using SetLength you can set the string buffer to any size you need. This makes string buffers useful as text buffers to receive text. Windows API functions that return a text in a character array can be used like this:

Alternatively, since you can assign a PChar to a string , and that will result in a new string with a copy of the text, you can set the length of the string just as well with this functionally equivalent code:

The last line of the function sets the length of the string back to the length of the C-style string that was stored in the buffer. If you need the result as a PChar anyway, to be processed by further API routines, you may perhaps be tempted to do this instead:

This will however fail. Because Buffer is a local variable, the entire buffer is in local memory (the processor stack). As soon as the function ends, the local memory is reused for other routines, so the text to which the result now points is turned into complete gibberish. Local buffers should never be used to return text.

But even if you had used a dynamic allocation with StrAlloc or a similar routine, the user would have to free the buffer. It generally is not a good idea to return PChars like that. Better follow the example of GetWindowsDirectory , and let the user of the function provide a buffer and its length. You then simply fill the buffer (using StrLCopy ) up to the given length.

There is an alternative to the function WindowsDirectory , that could use a local buffer. This relies on the fact that you can assign a PChar to a string directly. To make the text a Delphi string (with length and reference count fields), a Delphi string buffer of the required length is allocated, and the text is copied to that. So even if the local buffer is discarded, the text in the string buffer is still there:

But how would you write a function, for instance in a DLL, that must pass back data as a PChar , yourself? I think you should take the example of GetWindowsDirectory again. Here is a simple DLL function, returning a version string that is stored in our DLL:

As you can see, the string is simply copied to the provided buffer with StrLCopy . Because the user must provide the buffer, you avoid any memory management problems. If you provided it, the user would have to know how to free it. FreeMem doesn’t work across a DLL boundary. But even if it did, a user of the DLL that used C or Visual Basic would not know how to free the buffer in that language, since memory management is different in each language. Letting the user provide the buffer makes him or her independent of your implementation.

Casting

A little more on casting. Take a look at the following piece of code.

This results in 3 times the same number (the address of the payload of the string as integer) being displayed. But under the hood, different things happen, depending on the cast. If you are familiar with the CPU view of the IDE, you can see this for yourself.

For the line marked with < 1 >, the compiler inserts a call to _UniqueString , which creates a unique copy of the string, because accessing the single element A[1] already causes a copy on write, even though nothing is actually written. Had A been empty, accessing A[1] would probably cause a range check error (depending on the options set).

For the line marked with < 2 >, the compiler inserts a call to _UStrToPWChar (or a similar function, depending on the Delphi version), which simply passes on the address it is given, unless the string is empty. If the string is empty, _UStrToPWChar returns the address of a “string” that consists of a single #0 character.

For the line marked with < 3 >, the compiler does nothing special. The cast simply returns the address stored in A .

Delphi 2009 and up

In Delphi 2009 , strings were changed big time. Before, i.e. in Delphi 2 up to Delphi 2007, string mapped to AnsiString , and each character was a single-byte AnsiChar . A PChar was in fact a PAnsiChar . But in Delphi 2009 , strings were made to use 16 bit Unicode, to be precise, UTF- 16 , which meant that a new string type was required: UnicodeString . This string type is based on WideChars . This became the default string type, which meant that string now mapped to UnicodeString , Char to WideChar and PChar to PWideChar .

Delphi for Win32 already had the string type WideString , but this is a type that is allocated by the OS and has no reference count or “copy on write”, so each assignment meant that a new, unique, full copy of the text had to be made. The WideString type is not very performant, and that is why the new UnicodeString type was introduced.

Beside the length and the reference count field, each string type, i.e. AnsiString as well as UnicodeString , got extra fields stored before the text: a Word containing the encoding for the string (mainly used for single byte strings like AnsiString ) and a Word containing the character size. The encoding of an AnsiString governs how characters with byte values 128 up to 255 are interpreted and converted, the character size is mainly necessary for interfacing with C++ code.

Additionally, a few other string types were introduced as well: RawByteString and UTF8String . UTF8Strings are meant to contain text in UTF-8 format, which means that each element is an AnsiChar , but that “characters” can be encoded as multiple AnsiChars . Note that I put “characters” in quotes, since in the context of Unicode, it is more accurate to speak of code points.

As you can see in the Wikipedia article about UTF- 16 , it is also possible that some UTF- 16 code points also require the use of two WideChars , so called “surrogate pairs”. So the Length of a UnicodeString or an UTF8String do not necessarily correspond to the number of code points they contain. However, in UTF-16, surrogate pairs are pretty seldom, while in UTF-8, multibyte encodings are quite common.

Another new string type is the RawByteString . If you assign an AnsiString with one type of encoding to a string with a different encoding, an automatic conversion will take place, which could result in a loss of data, if characters from one encoding have no equivalent in the other. AnsiStrings use a default encoding, governed by system settings. RawByteString, however, is a string without any encoding, so you can be sure that if you assign your AnsiString or UTF8String to one (usually when passing one of them as a parameter), no conversion will take place.

The Delphi 2009 help says about RawByteString :

RawByteString enables the passing of string data of any code page without doing any codepage conversions. Normally, this means that parameters of routines that process strings without regard for the string’s code page should be of type RawByteString. Declaring variables of type RawByteString should rarely, if ever, be done, because this can lead to undefined behavior and potential data loss.

So what to do?

As you can see, in the text above, I hardly make any reference to the size of a Char . So anything I wrote in the article above can also be applied in Delphi 2009 and up. Most code using the techniques mentioned simply recompiles in Delphi 2009 and up, but instead of using AnsiStrings , it then uses UnicodeStrings , WideChars and PWideChars .

Илон Маск рекомендует:  Что такое код setactivepage

Win32 API functions often come in two versions, one that takes Ansi (i.e. single-byte) characters and (C-style) strings and one that takes Wide (Unicode, double-byte) characters and (C-style) strings. These two are usually distinguished by an A or a W at the end of their name, respectively. Delphi’s interface units for such API functions, like Windows.pas , generally also define a third version, without A or W at the end of the name (just like Microsoft does, in the C headers for these functions), and map that to the Ansi-based functions. One example from a Windows.pas from before Delphi 2009 :

As you can see, GetShortPathName is mapped to the function ‘GetShortPathNameA’ . You also see that the -A version is declared to take PAnsiChar strings, the -W version takes PWideChar strings, and the neutral version take PChar strings.

In Delphi 2009 and up, such neutrally named function declarations are now mapped to the W variety, so now it becomes:

This means that, in Delphi 2009 and up, even if you want to call Windows API functions, but also if you call runtime library or VCL functions, most of the time, you don’t have to worry about character size. Strings are now Unicode, the API functions are now (mapped to) Unicode too, so if you keep on using the size neutral types string , Char and PChar , you won’t have to modify a lot of your code. And if there is code that happens to have the wrong character size (some API functions, like GetProcAddress only exist in an Ansi version), you get a nice compiler warning or error, to which you can and should react.

I know that Unicode is more than just UTF- 16 . UTF- 8 and UTF- 32 are Unicode too. But in the context of strings in Delphi 2009 and above, with “Unicode” I actually mean UTF- 16 .

Conversions

Conversions between AnsiStrings and UnicodeStrings are automatic, but they produce a warning:

You can avoid these warnings by telling the compiler that you know that a conversion takes place, by doing it explicitly:

But note that such an implicit conversion does not take place when you cast one of the string types to a PWideChar or PAnsiChar . So the following does not cause any automatic conversions:

In such a case, you must perform an explicit conversion to the correct width first:

SizeOf or Length?

Of course you must be careful of code, especially code that uses low level routines like GetMem , Move or FillChar , that assumes that characters are byte sized. So to clear a static array[0..N] of Char , don’t do:

because Buffer is now made up of WideChars , which means it is now 2 * (MAX_PATH + 1) bytes in size. So if the size of such a buffer is required, you must use SizeOf :

Note that SizeOf should only be applied to static arrays. It does not work on dynamic arrays like array of Char . In that case, you use something like:

Instead of MyCharArray[0] , I more and more prefer to use something like Pointer(MyCharArray)^ , since the former form can generate a range check error if MyCharArray is of length 0 , since in that case, there is no element 0 . So then the code becomes:

For situations where the number of characters is important, you use Length :

Further Information

There is a whitepaper by Marco Cantù, which describes the various new string types and enhancements extensively and very clearly. I recommend you download it and read it at least once.

More tips and tricks about converting your strings to Delphi 2009 and up can be found in these articles by Nick Hodges, former Delphi R&D Manager: Delphi in a Unicode World, Part 1, Part 2 and Part 3.

There is a bunch of Unicode related articles and documents on the Embarcadero Developer Network.

Conclusions

The open secrets of good design practice include the importance of knowing what to keep whole, what to combine, what to separate, and what to throw away. — Kevlin Henny

Although string and PChar are both string types, they are quite different. Strings are easier to use, whereas for PChars you must do almost everything yourself. You can use them together, and cast a string as PChar , and assign a PChar to a string , but because a string changes its address when it is changed, you should not hold on very long to the address you obtain by casting a string to a PChar . Assigning a PChar to a string is less hazardous, because a copy is made to the internal buffer.

As the previous text demonstrated, allocating text in a function and then returning a PChar to the new buffer is ususally not a good idea. It is even worse if it is done across a DLL boundary, since the user can perhaps not even free the memory – the DLL and the user probably use a different memory manager, and each has a different heap. It is also not a very good idea to use a local buffer to return text.

If you must use PChar , because a function requires it, you should use string as much as possible, and only cast to PChar when you use the string as a parameter. Using strings is much easier, and less error prone, than using the C-style string functions.

Finally

A little inaccuracy sometimes saves a ton of explanation. — H. H. Munro (Saki)

I hope I have lifted a bit of the fog regarding PChars . I have not told everything there is to be known, and perhaps even twisted the exact truth a bit (for instance, not every Delphi string is reference counted – string literals always have a reference count of -1 ), but those internal details are not important for the big picture, and have no bearing on the safe use and interaction of strings and PChars .

These links are being provided as a convenience and for informational purposes only; they do not constitute an endorsement or an approval of any of the products, services or opinions of the corporation or organization or individual. I bear no responsibility for the accuracy, legality or content of the external site or for that of subsequent links. Contact the external site for answers to questions regarding its content.

The coding examples presented here are for illustration purposes only. The author takes no responsibility for end-user use. All content herein is copyrighted by Rudy Velthuis, and may not be reproduced in any form without the author’s permission. Source code written by Rudy Velthuis presented as download is subject to the license in the files.

Какие строковые типы существуют в Delphi, и чем они отличаются друг от друга?

В Delphi 1.0 существовал лишь единственный строковый тип String, полностью эквивалентный одноименному типу в Turbo Pascal и Borland Pascal. Однако, этот тип имеет существенные ограничения. Для обхода этих ограничений, в Delphi 2, разработчики из Borland устроили небольшую революцию. Теперь, начиная с Delphi 2, имеются три фундаментальных строковых типа: ShortString, AnsiString, и WideString. Кроме того, тип String теперь стал логическим. Т.е., в зависимости от настройки соответствующего режима компилятора (режим больших строк), он приравнивается либо к типу ShortString (для совместимости со старыми программами), либо к типу AnsiString (по умолчанию). Управлять режимом, можно используя директиву компиляции <$LONGSTRINGS ON/OFF>(короткая форма <$H+/->) или из окна настроек проекта – вкладка «Compiler» -> галочка «Huge strings». Если режим включен, то String приравнивается к AnsiString, иначе String приравнивается ShortString. Из этого правила есть исключение: если в определении типа String указан максимальный размер строки, например String[25], то, вне зависимости от режима компилятора, этот тип будет приравнен к ShortString соответствующего размера.

Поскольку по умолчанию, после установки Delphi, режим больших строк включен, большинство молодых программистов даже и не подозревают, что String может представлять что-то отличное от AnsiString. Поэтому, дальше в этом пункте, любое упоминание типа String без указания размера, подразумевает, что он равен типу AnsiString, если не будет явно указано иное. Т.е., считается что настройка компилятора соответствует настройке по умолчанию.

Существуют различия между типами AnsiString и WideString. Эти типы имеют практически одинаковую реализацию, и отличаются лишь тем, что WideString используется для представления строк в кодировке UNICODE использующей 16-ти битное представление каждого символа (WideChar). Эта кодировка используется в тех случаях когда необходима возможность одновременного присутствия в одной строке символов из двух и более языков (помимо английского). Например, строк содержащих одновременно символы английского, русского и европейских языков. За эту возможность приходится платить – размер памяти, занимаемый такими строками в два раза больше размера, занимаемого обычными строками. Использование WideString встречается не часто, поэтому, я буду в основном рассказывать о строках типа AnsiString. Но, поскольку они имеют одинаковую реализацию, почти все сказанное относительно AnsiString будет действительно и для WideString, естественно с учетом разницы в размере каждого символа.

Тоже самое касается и разницы между pChar и pWideChar.

Строковый тип AnsiString, обычно используется для представления строк в кодировке ANSI, или других (например OEM) в которых для кодирования одного символа используется один байт (8 бит). Такой способ кодирования называется single-byte character set, или SBCS. Но, очень многие не знают о существовании еще одного способа кодирования многоязычных строк (помимо UNICODE) используемого в системах Windows и Linux. Этот способ называется multibyte character sets, или MBCS. При этом способе, некоторые символы представляются одним байтом, а некоторые, двумя и более. В отличие от UNICODE, строки, закодированные таким способом, требуют меньше памяти для своего хранения, но требуют более сложной обработки. Так вот, строковый тип AnsiString может использоваться для хранения таких строк.

Также существуют еще типы pChar (pWideChar) и array [. ] of Char, они очень часто используются в сочетании со строковыми типами.

Итак, основные характеристики строковых типов:

Тип Максимальный размер строки Размер переменной Объем памяти, требуемый для хранения строки
String[n] где 0

Есть ещё – оператор @ (получение указателя) для переменной такого типа возвращает значение типа pChar. Это очень удобно, поскольку переменные этого типа очень часто используются как буфер при работе с функциями Windows API. Например:

var a :array[0..20] of Char;. GetModuleFileName(GetModuleFileName(HInstance,@a,SizeOf(a));

Здесь, функция GetModuleFileName возвращает результат в массив a.

PChar

Этот тип широко используется в языках C и C++. В Delphi, это не фундаментальный тип, а производный. Его определение выглядит так:

Т.е. переменные этого типа являются указателем, поэтому и имеют размер 4 байта. Формально, значение pChar может указывать как на один символ, так и на строку символов. Однако, общепринято что значения pChar указывают на строки, завершающиеся символом с кодом 0 (#0). В DOSе, такие строки назывались ASCIIZ, но чаще можно встретить название null-terminated string. Наличие такого «концевика» позволяет легко определить реальный размер строки на которую указывает значение pChar.

Не смотря на то, что формально pChar это указатель на Char (^Char), как это часто бывает в Delphi, тип pChar имеет несколько особенностей по сравнению с другими указателями. Таких особенностей несколько.

Первая, заключается в том, что константы, и глобальные переменные этого типа могут быть проинициализированы строковой константой. Вот пример:

const pc :pChar =’abc’;var pv :pChar =’abc’;

Эти строки, определяют константу pc и переменную pv типа pChar. При этом, и pc и pv указывают на разные области памяти, но содержащие одинаковые значения, состоящие из трех символов: ‘a’, ‘b’, ‘c’, и символа #0. Завершающий символ с кодом 0 компилятор добавил автоматически.

Вторая особенность в том, что к переменным типа pChar применимо обращение как к массиву символов. Например, если есть приведенные выше определения, тогда:


C := pv^; // C будет присвоен символ ‘a’. Это обычное обращениеC := pv[0]; // необычно, но С станет равным ‘a’C := pv[1]; // С станет равным ‘b’C := pv[2]; // С станет равным ‘c’C := pv[3]; // С станет равным #0C := pv[4]; // ОШИБКА!

Символ с индексом 3 отсутствует в строке, однако, там есть завершающий ее символ с кодом 0. Именно он будет результатом pv[3]. О pv[4] тоже стоит сказать особо. Дело в том, что компилятор не даст ошибки при компиляции, поскольку на этапе компиляции он, в общем случае, не известен реальный размер строки, на которую указывает переменная pv. Однако, на этапе выполнения программы, такое обращение может вызвать ошибку нарушения доступа к памяти (Access Violation). А может и не вызвать, но результатом будет неопределённое значение. Все зависит от «расклада» в памяти. Поэтому, при таком способе обращения необходимо быть внимательным, и выполнять все необходимые проверки, исключающие выход за размеры строки.

Третья и последняя особенность типа pChar в том, что к значениям этого типа применима так называемая адресная арифметика. Тем, кто программирует на C и C++ она хорошо знакома. Суть её в том, что значения pChar можно увеличивать, уменьшать, вычитать, и складывать. Для демонстрации использования этой особенности, приведем пример реализации функции подсчитывающей длину строки, указатель на которую передается в качестве параметра.

function StringLength (p :pChar) :Cardinal;begin Result := 0; if p = nil then Exit; while p^ <> #0 do begin Inc(Result); Inc(p); end;end;

Здесь важно обратить внимание на два нюанса.

Первый, это проверка переданного указателя на nil. Такая проверка необходима, поскольку очень часто, для обозначения пустой строки используется значение nil.

Второй, это оператор Inc(p) – он «продвигает» указатель на следующий символ. Можно было бы записать его и так: p := p + 1.

Что бы продемонстрировать вычитание указателей pChar, приведем еще один вариант реализации той же функции:

function StringLength (p :pChar) :Cardinal;var pp :pChar;begin Result := 0; pp := p; if pp <> nil then while pp^ <> #0 do Inc(pp); Result := (pp-p);end;

Здесь, выражение pp-p дает «расстояние» между указателями, т.е. число символов между символом, на который указывает указатель p (начало строки) и символом, на который указывает указатель pp (завершающий строку #0).

ShortString, и String[n]

ShortString является частным случаем String[n], а если быть более точным, он полностью эквивалентен String[255].

Если посмотреть на таблицу, где приведены характеристики переменных этого типа, то мы увидим что их размер на один байт больше чем размер хранимой в них строки. Но в данном случае, это не для завершающего символа, как в pChar. Здесь в дополнительном байте хранится текущий размер строки. Кроме того, этот байт располагается не в конце строки, а наоборот, в ее начале. К примеру, если имеется следующее определение:

Оно означает, что для переменной s будет статически выделена область памяти размером 5 байт.

Теперь, выполнение оператора s := ‘abc’, приведёт к тому, что содержимое этих пяти байт станет следующим: байт 1 = 3, байт 2 = ‘a’, байт 3 = ‘b’, байт 4 = ‘c’, а значение байта 5 будет неопределённо – оно будет зависеть от «расклада» в памяти. Т.е., первый символ строки будет находиться во втором байте. Это неудобно, поэтому к символам строк ShortString принято индексироваться, начиная с 1. Следовательно:

s[1] = ‘a’ – первый символ строкиs[2] = ‘b’ – второй символ строкиs[3] = ‘c’ – третий символ строкиs[4] = все что угодно :).

А как же байт длины? Да все очень просто, к нему можно обратиться как s[0]. Только вот есть маленькая проблемка. Поскольку элементами строки являются символы, то и тип значения s[0] тоже будет символ. Т.е., если Вы хотите получить длину строки в виде целого числа, как это принято у нормальных людей, то надо выполнить соответствующее преобразование типа: Ord(s[0]) = 3 – размер строки.

Почему для типа String[n] существует ограничение 0 0, поэтому то Delphi и не освобождает память, занятую строкой.

Еще, этот счётчик используется и для разрешения проблем, связанных со следующей ситуацией:

procedure ShowInteger;var s1 :AnsiString; s2 : AnsiString; n :Integer;begin n := 123; s1 := ‘abc’+IntToStr(n); s2 := s1; s2[1] := ‘X’;end;

Здесь, как мы уже знаем, после выполнения оператора s2 := s1, обе переменные указывают на один и тот же экземпляр строки ‘abc123’. Однако, что же произойдёт когда выполниться оператор s2[1] := ‘X’? Казалось бы, в единственном имеющимся в нашем распоряжении экземпляре строки первая буква будет заменена на ‘X’. И как следствие, обе строки станут равными ‘Xbc123’. s1 то за что «страдает»? Но, к счастью это не так. Здесь на помощь Delphi вновь приходит счетчик ссылок. Delphi, при выполнении этого оператора понимает, что строка на которую указывает s2 будет изменена, а это может повлиять на других. Поэтому, перед изменением строки, проверяется ее счётчик ссылок. Обнаружив, что на нее ссылается более одной строковой переменной, делается следующее: создается копия этой строки со счётчиком ссылок равным 1, и адрес этой копии, присваивается s2; У исходного экземпляра строки, счетчик ссылок уменьшается на 1 – ведь s2 на неё теперь не ссылается. И лишь после этого, происходит изменение первой буквы, теперь уже собственного экземпляра строки. Т.е., по окончанию выполнения этого оператора, в памяти будут находиться две строки: ‘abc123’ и ‘Xbc123’. Причем, s1 будет ссылаться на первую, а s2 на вторую.

При работе со строками определенными как константы, алгоритм работы несколько отличается.

Пример:

procedure ShowInteger;var s :AnsiString;begin s := ‘Вася’; ShowMessage(s);end;

Казалось бы, при завершении работы процедуры, экземпляр строки ‘Вася’ должен быть уничтожен. Но в данном случае это не так. Ведь, при следующем входе в процедуру, для выполнения присваивания нужно будет вновь где-то взять строку ‘Вася’. Для этого, ещё при компиляции, Delphi размещает экземпляр строки ‘Вася’ в области констант программы, где её даже невозможно изменить, по крайней мере, простыми методами. Но как же при завершении процедуры определить что строка ‘Вася’ – константная строка, и ее нельзя уничтожать? Все очень просто. Для константных строк, счётчик ссылок устанавливается равным -1. Это значение, «выключает» нормальный алгоритм работы со «счётчиком ссылок». Он не увеличивается при присваивании, и не уменьшается при уничтожении переменной. Однако, при попытке изменения переменной (помните s2[1]:=’X’), значение счётчика равное -1 будет всегда считаться признаком того, что на строку ссылается более одной переменной (ведь он не равен 1). Поэтому, в такой ситуации всегда будет создаваться уникальный экземпляр строки, естественно, без декремента счётчика ссылок старой. Это защитит от изменений экземпляр строки-константы.

К сожалению, этот алгоритм срабатывает не всегда.

Где же Delphi хранит «счётчик ссылок»? Причем, для каждой строки свой! Естественно, вместе с самой строкой. Вот что представляет собой эта область памяти, хранящая экземпляр строки ‘abc’:

Байты с 1 по 4 Счётчик ссылок равный -1
Байты с 5 по 8 Длина строки равная 3
Байт 9 Символ ‘a’
Байт 10 Символ ‘b’
Байт 11 Символ ‘c’
Байт 12 Символ с кодом 0 (#0)

Для удобства работы с такой структурой, когда строковой переменной присваивается ссылка на эту строку, в переменную заносится адрес не начала этой структуры, а адрес её девятого байта. Т.е. адрес начала реальной строки (прямо как pChar). Для того, что бы приблизиться к реальной жизни, перепишем приведённую структуру:

Смещение Размер Значение Назначение
-8 -1 Счётчик ссылок
-4 Длина строки
‘a’
‘b’
‘c’
#0

С полем по смещению -8, нам уже должно быть все ясно. Это значение, хранящееся в двойном слове (4 байта), тот самый счетчик, который позволяет оптимизировать хранение одинаковых строк. Значение этого счетчика имеет тип Integer, т.е. может быть отрицательным. На самом деле, используется лишь одно отрицательное значение – «-1», и положительные значения. 0 не используется.

Теперь, обратим внимание на поле, лежащее по смещению -4. Это, четырёхбайтовое значение длинны строки (почти как в ShortString). Думаю, Вы заметили, что размер памяти выделенной под эту строку не имеет избыточности. Т.е. компилятор выделяет под строку минимально необходимое число байт памяти. Это конечно хорошо, но, при попытке «нарастить» строку: s1 := s1 + ‘d’, компилятору, точнее библиотеке времени исполнения (RTL) придется перераспределить память. Ведь теперь строке требуется больше памяти, аж на целый байт. Для перераспределения памяти нужно знать текущий размер строки. Вероятно, именно для того, что бы не приходилось каждый раз сканировать строку, определяя её размер, разработчики Delphi и включили поле длины, строки в эту структуру. Длина строки, хранится как значение Integer, отсюда и ограничение на максимальный размер таких строк – 2 Гбайт. Кстати, именно потому, что память под эти строки выделяется динамически, они и получили ещё одно свое название: динамические строки.

Ещё немного о нескольких особенностях переменных AnsiString. Важнейшей особенностью значений этого типа является возможность приведения их к типу Pointer. Это впрочем, естественно, ведь в «душе» они и есть указатели, как бы они этого не скрывали. Например, если описаны переменные: s :AnsiString и p :Pointer. То выполнение оператора p := Pointer(s) приведет к тому, что переменная p станет указывать на экземпляр строки. Однако, при этом, очень важно знать: счетчик ссылок этой строки не будет увеличен.

Поскольку, переменные этого типа реально являются указателями, то для них и реально такое значение как Nil – указатель в «никуда». Это значение в переменной типа AnsiString по смыслу приравнивается пустой строке. Более того, чтобы не тратить память и время на ведение счётчика ссылок, и поля размера строки всегда равного 0, при присваивании пустой строке переменной этого типа, реально, присваивается значение Nil. Это не очевидно, поскольку обычно не заметно, но как мы увидим позже, очень важная особенность.

Преобразование строк из одного типа в другой

Преобразование между «настоящими» строковыми типами String[n], ShortString, и AnsiString выполняются легко, и прозрачно. Никаких явных действий делать не надо, Delphi все сделает за Вас. Надо лишь понимать, что в маленькое большое не влезает. Например:

var s3 :String[3]; s :AnsiString;. s := ‘abcdef’; s3 := s;

В результате выполнения этого кода, в переменной s3 окажется строка ‘abc’, а не ‘abcdef’. С преобразованием из pChar в String[n], ShortString, и AnsiString, тоже всё очень не плохо. Просто присваивайте, и все будет нормально.

Сложности начинаются тогда, когда мы начинаем преобразовывать «настоящие» строковые типы в pChar. Непосредственное присваивание переменным типа pChar значений строк не допускается компилятором. На оператор p := s где p имеет тип pChar, а s :AnsiString, компилятор выдаст сообщение: «Incompatible types: ‘String’ and ‘PChar'» — несовместимые типы ‘String’ и ‘PChar’. Чтобы избежать такой ошибки, надо применять явное приведение типа: p := pChar(s). Так рекомендуют разработчики Delphi. В общем, они правы. Но, если вспомнить, как хранятся динамические строки — с нулем в конце, как и pChar. А еще и то, что к AnsiString применимо преобразование в тип Pointer. Станет очевидным, что всего, возможно целых три способа преобразования строки в pChar:

var s :AnsiString; p1,p2,p3 :PChar;. p1 := pChar(s); p2 := Pointer(s); p3 := @(s[1]);

Все они, синтаксически правильны. И кажется, что все три указателя (p1, p2 и p3) будут в результате иметь одно и то же значение. Но это не так. Всё зависит от того, что находится в s. Если быть более точным, равно ли значение s пустой строке, или нет:

s <> »p1 = p2 <> p3s = »p1 <> p2 = p3

Чтобы была понятна причина такого явления, опишем, как Delphi выполняет каждое из этих преобразований. Переменные AnsiString представляющие пустые строки, реально имеют значение Nil. Так вот:

Для выполнения преобразования pChar(s), компилятор генерит вызов специальной внутренней функции @LstrToPChar. Эта функция проверяет – если строковая переменная имеет значение Nil, то вместо него, она возвращает указатель на реально размещенную в памяти пустую строку. Т.е. pChar(s) никогда не вернет указатель равный Nil.

Тут все просто, такое преобразование просто возвращает содержимое строковой переменной. Т.е. если она при пустой строке содержит Nil, то и результатом преобразования будет Nil. Если же строка не пуста, то результатом будет адрес экземпляра строки.

Здесь, все совсем по-другому. Перед выполнением такого преобразования, компилятор вставляет код, обеспечивающий ситуацию, когда указатель, хранящийся в s, будет единственным указателем на экземпляр строки в памяти. В нашем примере, если строка не пуста, то будет создана копия исходной строки. Вот ее-то адрес и будет возвращен как результат такого преобразования. Но, если строка пуста, то результатом будет Nil, как и во втором случае.

Теперь, интересно отметить, что если в приведенном примере, преобразование p3 := @(s[1]) выполнить первым, то при не пустой строке в s, все указатели (p1, p2, и p3), будут равны. И содержать они будут адрес «персонального» экземпляра строки.

Вот, теперь, зная, как выполняются те или иные преобразования, Вы сможете всегда выбрать подходящий способ.

Приведем пример. В нем, преобразование, рекомендуемое разработчиками, приводит к «странному» поведению программы:

procedure X1;var s :AnsiString; p :PChar;begin s := ‘abcd’; p := PChar(s); p^ := ‘X’; // 0 then PChar(S)[0] := ‘q’; //.

вызовет ошибку нарушения доступа при передаче в нее строки сrefCnt = -1.

Чтобы получить уникальную ссылку для строки, состоящей из некоторой последовательности символов, можно воспользоваться функцией UniqueString.Это позволяет ускорить вычисления со строками, так как при этом можно будет сравнивать строки, просто сравнивая указатели на них. У таких строк refCntвсегда равен 1.

Примеры

Пример 1.

В символьной строке подсчитать количество цифр, предшествующих первому символу «!»

+
+
Начало
s
K:=0; I:=1;
I ’!’
S(i)>=’0’ and s(i) ’!’) Do

If (S[I]>=’0’) And (S[i] а, тт -> т ).

1.Имеется строка, содержащая буквы латинского алфавита и цифры. Вывести на экран длину наибольшей последовательности цифр, идущих подряд.

2. Дана строка, содержащая текст, заканчивающийся точкой. Вывести на экран слова, содержащие хотя бы одну букву о.

1. Дан набор слов, разделенных точкой с запятой (;). Набор заканчивается двоеточием (:). Определить, сколько в нем слов, заканчивающихся буквой а.

2. Дана строка, заканчивающаяся точкой. Подсчитать, сколько запятых в строке.

Последнее изменение этой страницы: 2020-07-16; Нарушение авторского права страницы

PChar — Тип Delphi

Неудачное сочетание двух функций в Delphi 7 стоило мне нескольких часов жизни и много нервов. Зато мне удалось наконец поймать непредсказуемый PChar за хвост. Я и раньше сталкивался с необъяснимыми косяками, корнями идущими из бардака в pchar-переменных. Но сколько я об этом ни читал, сколько не копался — смысл не доходил. Теперь же я на 90% уверен, что точно знаю, чего ожидать от этого типа.

Теория

Я полагаю, что перед этой статьей вы уже почитали справочник по программированию и может даже пару других эпосов, посвященных PChar. Позвольте же и мне вставить «свои пять копеек». Итак, pchar — типизированный указатель. Переменная такого типа хранит адрес памяти, в котором начинается строка. Но строки-то бывают разной длины. Как прога узнает, сколько байт считать из памяти начиная с указанного адреса? А она просто читает подряд все байты, пока не найдет символ с кодом 0 (#0). Отсюда и название — «null-terminated string«. Наличие такого «концевика» позволяет легко определить реальный размер строки на которую указывает значение pChar.

Поясню рисунок: в память последовательно пришется текст с завершающим символом #0, в переменную возвращается адрес первого записанного символа. Конечно, память адресуется не так, как я нарисовал, и значения в нее пишутся не в десятичной СС , а в двоичной, но суть ясна. Кстати, числа в примере — это ACSII-код символов текста. Тоже важный момент.

Ноль-терминатор (#0) — это краеугольный камень всех проблем с PChar :). Из-за его отсутствия в переменной появляется всевозможный мусор. Хуже всего, когда там появляются вполне читаемые символы. Это конкретно заводит в тупик. Обычно функции, работающие с PChar, контролируют наличие ноль-терминатора. Однако при «правильном» подходе можно пробиться за пределы.

Еще следует кое-что знать о диспетчере памяти Delphi. Я и сам не до конца с этим разобрался, но как понял, рассказываю. При запуске программы для нее выделяется оперативная память. Она резервируется 1Мб-ыми секциями и выделяется блоками по 16 кБ по мере надобности. Память для глобальных переменных выделяется в сегменте данных программы и освобождается при ее завершении. Локальные переменные размещаются в стеке. Каждый раз при вызове функций/процедур для них выделяется память, а при выходе из функции/процедуры она освобождается, хотя оптимизация компилятора может их уничтожить еще во время выполнения подпрограммы. Размер стека программы ограничен минимальным и максимальным значениями. Эти значения задаются директивами компилятора $MINSTACKSIZE (по умолчанию — 16 384 байта=16 кБ) и $MAXSTACKSIZE (по умолчанию — 1 048 576 байт=1 Мб).

А теперь самое интересное: когда память особождается от локальной переменной, по умолчанию из памяти не удаляются данные. Всего лишь разрывается связь между переменной и адресом памяти. Но пока не закрыта программа, отведенная ей под стек память все еще доступна вместе с данными в ней. Вполне может быть, что при создании очередной переменной ей будет назначен адрес памяти, содержащей что-то отличное от нуля. Это не критично, т.к. переменные инициализируются, так же после присвоения значений в память попадут новые данные. Однако объявляя указатель на память все же можно заглянуть за кулисы.

Ловля на живца

Весь проект в zip-архиве. Исходники Delphi 7.

Что происходит? Нажимаем на кнопу, в переменную p должно быть скопировано «hell». Строки 8, 10 и 13 выдают текст и его длину на канву формы. Несколько нажатий кнопы дают более впечатляющий по глючности результат. В зависимости от версии Delphi возможно вы не сможете получить что-то не читабельное, поэтому приложил скиншот.

Сразу же раскажу, как правильно: раскомментировав строку (14) мы избавимся от меняющихся значений в конце строки. Если бы код сразу был верным (с особождением памяти переменной p), вряд ли бы наглядно удалось увидеть проблему, хотя она все равно остается. Раскоментировав строку (12) избавимся от глючности вообще. Этой строкой в конец скопированного текста добавляется ноль-терминатор.

Разбираемся: строка (9) занимает память в стеке для переменной. Функция GetMem() при этом не чистит занятую память, только связывает с переменной адрес нужного объема памяти. Поэтому следующая строка (10) выдает на форму символьный мусор. Далее копирование 4-символов и опять в конце строки мусор на форме! Почему? Потому, что функция StrMove() является редким примером функции, не следящей за наличием нуль-терминатора.

выдаем все до #0

Надеюсь, из рисунков понятно: сначала StrMove() не пишет в конце текста #0, затем текст читается из памяти по указателю, пока не будет найден #0. Т.е. еще несколько байт. Кстати, функция чтения пытается и эти байты превратить в буквы, поэтому получается «мусор». Просто их значение не соответствует буквам в кодировке ASCII.

Вскрытие

Справка Delphi: «StrMove копирует точное количество символов из Source в Dest и возвращает Dest. Source и Dest могут перекрываться.» И ни слова про отсутствие терминатора! Его наличие подразумевается типом возвращаемого значения, PChar. Однако это не так :( С тем же успехом она могла бы возвращать нетипизированный указатель Pointer, но по скольку в памяти все таки текст, как-то уточнили. На самом деле все еще интереснее. Функция StrMove() в основе своей использует другую функцию из модуля System.

Далее по коду еще реализация Move() на ассемблере, но суть та же. Нигде не добавляется #0. Из-за этого допущения я тупил несколько часов, пытаясь понять, почему в возвращаемой строке то мусор, то вообще текст из предыдущих значений переменных.

Есть верный способ избавить себя от головной боли в этом случае. Можно использовать функцию AllocMem() из модуля SysUtils. Благодаря очистке памяти в конце текста будет предостаточно нулей :) Исходный код функции следующий:

Все, что осталось не понятно мне

Диспетчер памяти Delphi. Я понял, как он работает, какие функции использует и все такое. Я не понял, откуда берутся файлы диспетчера, когда программа запущена на машине без Delphi? Где библиотеки с функциями? Компилируются с каждым приложением? Скорее всего так..

Опять возвращаясь к понятию PChar и #0. Допустим, есть такой код:

Этот код демонстрирует некоторые возможности PChar. Можно рассматривать PChar, как массив, читать и изменять его элементы. Спокойно переходить от «указателя на Char» к собственно типу Char (строка 11). Непонятно мне следующее: если раскомментировать строку 10 (все до нее можно убрать), то получается «Access Violation..» в строках 12 и 13. Почему?

Post Scriptum

Описанная здесь проблема — не единственный случай загадочного проявления PChar. Я уже боролся с багами своих программ при использовании ресурсных и функциональных dll-ок, использующих PChar. Но это другая история :)

Я допускаю, что мои суждения могут быть ошибочны. Если вы знаете лучше меня, и есть что сказать — скажите :). Вот еще пара полезных ссылок по теме:
статья: «Строковые типы в Delphi. Особенности реализации и использования»
статья: «Управление памятью в Delphi 5.0: диспетчер памяти«

Понравилась статья? Расскажите о ней друзьям:

3.3.1. Виды строк в Delphi

3.3.1. Виды строк в Delphi

Для работы с кодировкой ANSI в Delphi существует три вида строк: AnsiString , ShortString и PChar . Различие между ними заключается в способе хранения строки, а также выделения и освобождения памяти для нее. Зарезервированное слово string по умолчанию означает тип AnsiString , но если после нее следует число в квадратных скобках, то это означает тип ShortString , а число — ограничение по длине. Кроме того, существует опция компилятора Huge strings (управляется также директивами компилятора <$H+/->и <$LONGSTRINGS ON/OFF>, которая по умолчанию включена, но если ее выключить, то слово string станет эквивалентно ShortString ; или, что то же самое, string[255] . Эта опция введена для обратной совместимости с Turbo Pascal, в новых программах отключать ее нет нужды. Внутреннее устройство этих типов данных иллюстрирует рис. 3.2.

Рис. 3.2. Устройство различных строковых типов Delphi

Наиболее просто устроен тип ShortString . Это массив символов с индексами от 0 до N, где N — число символов, указанное при объявлении переменной (в случае использования идентификатора ShortString N явно не указывается и равно 255). Нулевой элемент массива хранит текущую длину строки, которая может быть меньше или равна объявленной (эту длину мы будем далее обозначать M), элементы с индексами от 1 до M — это символы, составляющие строку. Значения элементов с индексами M+1..N не определены. Все стандартные функции для работы со строками игнорируют эти символы. В памяти такая переменная всегда занимает N+1 байтов.

Ограничения типа ShortString очевидны: на хранение длины отводится только один байт, поэтому такая строка не может содержать больше 255 символов. Кроме того, такой способ записи длины не совпадает с принятым в Windows, поэтому ShortString несовместим с системными строками.

В системе приняты так называемые нуль-терминированные строки: строка передается указателем на ее первый символ, длина строки отдельно нигде не хранится, признаком конца строки считается встретившийся в цепочке символов #0 . Длина таких строк ограничена только доступной памятью и способом адресации (т. е. в Windows теоретически это 4 294 967 295 символов). Для работы с такими строками предусмотрен тип PChar . Переменная такого типа является указателем на начало строки. В литературе нередко можно встретить утверждение, что PChar = ^Сhar , однако это неверно: тип PChar встроен в компилятор и не выводится из других типов. Это позволяет выполнять с ним операции, недопустимые для других указателей. Во-первых, если P — переменная типа PChar , то допустимо обращение к отдельным символам строки с помощью конструкции P[N] , где N — целочисленное выражение, определяющее номер символа (в отличие от типа ShortString , здесь символы нумеруются с 0, а не с 1). Во-вторых, к указателям типа PChar разрешено добавлять и вычитать целые числа, смещая указатель на соответствующее число байтов вверх или вниз (здесь речь идет только об операторах «+» и «-«; адресная арифметика с помощью процедур Inc и Dec доступна для любых типизированных указателей, а не только для PChar ).

При работе с PChar программист целиком и полностью отвечает за выделение памяти для строки и за ее освобождение. Именно это и служит основным источником ошибок у новичков: они пытаются работать с такими строками так же, как и с AnsiString , надеясь, что операции с памятью будут выполнены автоматически. Это очень грубая ошибка, способная привести к самым непредсказуемым последствиям.

Хотя программист имеет полную свободу выбора в том, как именно выделять и освобождать память для нуль-терминированных строк, в большинстве случаев самыми удобными оказываются специально предназначенные для этого функции StrNew , StrDispose и т. п. Их преимущество заключается в том, что менеджер памяти выделяет чуть больше места, чем требуется для хранения строки, и в эту дополнительную память записывается, сколько байтов было выделено. Благодаря этому функция StrDispose удаляет ровно столько памяти, сколько было выделено, даже если в середину выделенного блока был записан символ #0 , уменьшающий длину строки.

Компилятор также позволяет рассматривать статические массивы типа Char , начинающиеся с нулевого индекса, как нуль-терминированные строки. Такие массивы совместимы с типом PChar , что позволяет обойтись без использования динамической памяти при работе со строками.

Тип AnsiString объединяет достоинства типов ShortString и PChar : строки имеют фактически неограниченную длину, заботиться о выделении памяти для них не нужно, в их конец автоматически добавляется символ #0 , что делает их совместимыми с системными строками (впрочем, эта совместимость не абсолютная; как и когда можно использовать AnsiString в функциях API, мы рассматривали в разд. 1.1.13.).

Переменная типа AnsiString — это указатель на первый символ строки, как и в случае PChar . Разница в том, что перед этой строкой в память записывается дополнительная информация: длина строки и счетчик ссылок. Это позволяет компилятору генерировать код, автоматически выделяющий, перераспределявший и освобождающий память, выделяемую для строки. Работа с памятью происходит совершенно прозрачно для программиста, в большинстве случаев со строками AnsiString можно работать, вообще не задумываясь об их внутреннем устройстве. Символы в таких строках нумеруются с единицы, чтобы облегчить перенос старых программ, использовавших строки типа ShortString .

Счетчик ссылок позволяет реализовать то, что называется copy-on-demand, копирование по необходимости. Если у нас есть две переменные S1 , S2 типа AnsiString , присваивание вида S1:= S2 не приводит к копированию всей строки. Вместо этого в указатель S1 копируется значение указателя S2 , а счетчик ссылок строки увеличивается на единицу. В дальнейшем, если одну из этих строк потребуется модифицировать, она сначала будет скопирована (а счетчик ссылок оригинала, естественно, уменьшен) и только потом изменена, чтобы это не затрагивало остальные переменные.

Далее мы рассмотрим, какие проблемы могут возникнуть при использовании строк разного вида.

Разница между PAnsiChar и PChar

Есть ли разница и какой тип между PAnsiChar и PChar? (в Delphi в предыдущем 2007 году)

В D2009 и более поздних версиях: да, есть. PChar является указателем на Char , который является символом Unicode (a WideChar ). И PAnsiChar — это указатель на AnsiChar , который — как следует из названия — символ ANSI.

EDIT. Для версий Delphi PChar и PAnsiChar до 2009 года точно такие же. Оба они указывают на символ (Ansi).

Я обратил внимание на «In Delphi previous 2007» в вашем вопросе, который я подразумеваю «В Delphi 2007 и ранее», поэтому.

В Delphi 2007 и предыдущие PChar и PANSIChar являются синонимами. Они означают одно и то же — указатель на значение ANSIChar. Char является синонимом ANSIChar в этих версиях.

Однако в Delphi 2009 и более поздних версиях Char становится синонимом WideChar, поэтому PChar становится синонимом PWideChar.

Обратите внимание, что WideChar не является символом Unicode. Unicode просто не так прост. Любое значение WideChar может быть кодовым пунктом в BMP (Basic Multilingual Plane) или может быть либо одной из суррогатной пары. Это может быть также диакритический знак — например, «акцент», который должен применяться к непосредственно предшествующему кодовому центру в WideString.

Понятие «символ» в Юникоде не легко сопоставляется с одним значением любого типа.

Как использовать Delphi Dll (с типом PChar) в С#

Вот DLL-код Delphi:

Функция Dll MsgEncode будет передаватьmem параметру pOut, а BlockFree используется для освобождения памяти, которая была выделена с помощью MsgEncode.

Мой вопрос: Как использовать эту DLL в С#? Я новичок в С#.

Я собираюсь взять ваш вопрос по номиналу, с несколькими оговорками:

  • Если вы используете Unicode Delphi или нет, важно знать код взаимодействия, используя PChar , потому что PChar плавает между AnsiChar и WideChar в зависимости от версии Delphi. Я предположил, что вы используете Unicode Delphi. Если нет, вам нужно будет изменить сортировку строк на стороне P/Invoke.
  • Я изменил ваш DLL-код. Я удалил параметры длины и работаю в предположении, что вы только собираетесь позволить доверенный код вызвать эту DLL. Неверный код может вызвать переполнение буфера, но вы не собираетесь запускать ненадежный код на вашем компьютере, не так ли?
  • Я также изменил BlockFree , чтобы он мог получить нетипизированный указатель. Там нет необходимости в том, чтобы он был типом PChar , он просто вызывал Free .

Здесь измененный код Delphi:

И здесь код С# с другой стороны:

с использованием System; используя System.Runtime.InteropServices;

Это должно помочь вам начать. Поскольку вы новичок в С#, вам нужно будет немного почитать P/Invoke. Наслаждайтесь!

Обратите внимание, что строковые данные С# являются Unicode, поэтому, если вы продолжите с этим кодом Delphi с помощью PChar, будет скрытое преобразование из PChar в PWideChar, выполняемое в вызове PInvoke. (Преобразование означает выделение другого буфера памяти и копирование всех данных в новый буфер). Если вы собираетесь использовать этот код Delphi для использования с С#, и вы заботитесь о производительности, вы должны изменить свой код Delphi для работы с данными PWideChar.

Есть еще одна причина использовать PWideChar вместо PChar: Delphi выделяет тип OleString с помощью распределяющего устройства Win32 SysAllocString, как и требования COM. Это означает, что получатель строки способен освободить ее, используя API Win32.

Если вы фактически не обрабатываете текст в своей функции, а используете PChar в качестве суррогата для массива произвольных байтовых значений, вы можете избежать этого на неуправляемой стороне вызова, но не на управляемой стороне. Если это байтовые данные, он должен быть объявлен как массив байтов, чтобы избежать преобразования кодировки или char.

Delphi Programming Diary — Jitendra Kumar

Delphi! is my art of programming. This blog contains technical solutions and tips for Delphi programming. I am just trying to share knowledges that I gained from others and which will help others. (Delphi Tokyo/XE7/ XE6 / XE5 / XE4 / XE3 / XE2 / XE, FireMonkey, FireDAC, DataSnap, QuickReport, DevExpress, Woll2Woll, TMS Components, Indy, REMObjects SDK. )

Subscribe to this blog

Follow by Email

STRING / PCHAR / CHAR in Delphi


  • Get link
  • Facebook
  • Twitter
  • Pinterest
  • Email
  • Other Apps

STRING / PCHAR / CHAR in Delphi
———————————————
Char
——-
Char is a data type that holds only one character as value. Ansi Characters are used to store single byte ansi characters. Unicode
characters are used to store 2byte characters that is to cupport foreign languages like chinese, japanese etc.
var
chr1 : Char; // Holds a single character, alphabet
chr2 : WideChar; // Holds a single character, International alphabet // for Unicode support
chr3 : AnsiChar; // Holds a single character, alphabet

NON-PRINTING OR CONTROL CHARACTERS, NOT USED IN THIS CLASS, INCLUDED FOR INTEREST ONLY
0 Null (nothing)
7 Bell
8 Back space
9 Tab
10 Line feed (new line at present column)
13 Carriage return (return to beginning of line)
26 End of file
27 [Esc] (Escape key)
ASCII CHARACTER CODES
32 [space] 64 @ 96 `
33 ! 65 A 97 a
34 » 66 B 98 b
35 # 67 C 99 c
36 $ 68 D 100 d
37 % 69 E 101 e
38 & 70 F 102 f
39 ‘ 71 G 103 g
40 ( 72 H 104 h
41 ) 73 I 105 i
42 * 74 J 106 j
43 + 75 K 107 k
44 , 76 L 108 l
45 — 77 M 109 m
46 . 78 N 110 n
47 / 79 O 111 o
48 0 80 P 112 p
49 1 81 Q 113 q
50 2 82 R 114 r
51 3 83 S 115 s
52 4 84 T 116 t
53 5 85 U 117 u
54 6 86 V 118 v
55 7 87 W 119 w
56 8 88 X 120 x
57 9 89 Y 121 y
58 : 90 Z 122 z
59 ; 91 [ 123 <
60 94 ^ 126

String
———
String is a data type this kind of variables holds sequence of characters as value. In old version String = Ansistring.
But in new versions from Delphi 2010 it is Unicodestring / WideString. String starts with index 1 and 0 index stores
length of the string. SO we can find end of string by its length. Maximum length is depend on available memory. In Delphi string
is reference counted. So delphi will automatically frees a string datatype. Delphi doesn’t support automatic type conversions
betwwen Ansi and Wide string types.
var
Str4 : ShortString; // Holds a string of up to 255 Char’s
Str5 : String; // Holds strings of Char’s of any size desired
Str6 : AnsiString; // Holds strings of AnsiChar’s any size desired
Str7 : WideString; // Holds strings of WideChar’s of any size desired // for Unicode support/ so same as Unicodestring
str9 : Unicodestring// same as widestring
str8 : string[20]; // maximum 20 characters
String system functions
AnsiLeftStr Returns leftmost characters of a string
AnsiMidStr Returns middle characters of a string
AnsiRightStr Returns rightmost characters of a string
AnsiStartsStr Does a string start with a substring?
AnsiContainsStr Does a string contain another?
AnsiEndsStr Does a string end with a substring?
AnsiIndexStr Check substring list against a string
AnsiMatchStr Check substring list against a string
AnsiReverseString Reverses characters in a string
AnsiReplacStr Replaces all substring occurences
DupeString Repeats a substring n times
StrScan Scans a string for a specific character
StuffString Replaces part of a string text
Trim Removes leading and trailing white space
TrimLeft Removes leading white space
TrimRight Removes trailing white space
Converting from numbers to strings
CurrToStrF Convert a currency value to a string with formatting
DateTimeToStr Converts TDateTime date and time values to a string
DateTimeToString Rich formatting of a TDateTime variable into a string
DateToStr Converts a TDateTime date value to a string
FloatToStr Convert a floating point value to a string
FloatToStrF Convert a floating point value to a string with formatting
Format Rich formatting of numbers and text into a string
FormatCurr Rich formatting of a currency value into a string
FormatDateTime Rich formatting of a TDateTime variable into a string
FormatFloat Rich formatting of a floating point number into a string
IntToHex Convert an Integer into a hexadecimal string
IntToStr Convert an integer into a string
Str Converts an integer or floating point number to a string
Converting from strings to numbers
StringToWideChar Converts a string into a WideChar 0 terminated buffer
StrToCurr Convert a number string into a currency value
StrToDate Converts a date string into a TDateTime value
StrToDateTime Converts a date+time string into a TDateTime value
StrToFloat Convert a number string into a floating point value
StrToInt Convert an integer string into an Integer value
StrToInt64 Convert an integer string into an Int64 value
StrToInt64Def Convert a string into an Int64 value with default
StrToIntDef Convert a string into an Integer value with default
StrToTime Converts a time string into a TDateTime value
Val Converts number strings to integer and floating point values

PCHAR
———-
Pchar is a datatype (pointer of characters) that holds array of characters or single character. It is null terminated string like C.
It starts from 0 index. In Delphi it is mostly used for Windows API function calls. Since the array has no length indicator, Delphi uses
the ASCII 0 (NULL; #0) character to mark the boundary of the string. It allowed strings of any length, limited only by how much memory
the native pointer type can access.
var
p1: Pchar;
chr1: char;
s1: string;
chr2: char;
.
.
p1:=’abcdefgh’;
chr1:=p1[0];
s1:=p1;
chr2:=s1[1];
————————————————————————-
procedure TForm1.Button1Click (Sender: TObject);
var
S1: String;
begin
SetLength (S1, 100);
GetWindowText (Handle, PChar (S1), Length (S1));
Button1.Caption := S1;
end;
S1 := String (PChar (S1));

PChar — Тип Delphi

Ты наверное думаешь, что про строки уже и писать нечего. Тогда почитай, чего я для себя наконспектировал и наверняка что-нибудь новое для себя узнаешь. Думаю статейка будет полезна не только начинающим программистам.

Прежде всего давай четко определимся, что такое тип String в Delphi. В зависимости от директив компилятора тип String может интерпретироваться как ShortString или AnsiString.

— <$H+>или <$LongStrings On>String = AnsiString. По умолчанию.
— <$H->или <$LongStrings Off>String = ShortString.
— Можно управлять из окна настроек проекта – “Compiler” -> “Huge strings”
— Если при определении типа String указана длина строки: String[32], то вне зависимости от установок компилятора это будет означать объявление ShortString соответствующего размера.

ShortString — короткая строка
— sstr: ShortString; — это обычная паскалевская строка, то есть массив (цепочка) символов с зарезервированным в начале байтом для длины строки.
— Соответственно максимальная длина короткой строки = 0..255.
— Может объявляться так: sstr: String[макс.длина строки];.
— Если сослаться на нулевой элемент: sstr [0], то получишь длину строки в виде Char, но лучше использовать для этого Length(sstr)(она делает тоже самое). Ord(sstr[0]) = Length(sstr)
— При определении адреса короткой строки (с помощью @sstr или Addr(sstr)) возвращается указатель на байт длины.
— Индексы символов (sstr [ i]) начинается с 1.
— Значение в байте длины может быть меньше, чем размер строковой переменной : Byte(sstr[0]) ‹= SizeOf(sstr). То есть, хотя длина строки может и меняться, память, занимаемая ShortString, всегда равна 256 байтам.
— Короткие строки удобны для записи текстовой информации в файл (т.к. они фиксированной длины).

Чтобы преобразовать ShortString в PChar надо ручками добавить в конец строки терминальный нуль #0 и вернуть адрес первого символа :

function ShortStringToPChar (sstr: ShortString): PChar;
begin
sstr := sstr + #0;
Result := @sstr[1];
end;

AnsiString — длинная строка
— astr: AnsiString; — это длинная строка состоящая из символов AnsiChar (тоже, что и Char, пока). Этот тип принят по умолчанию, то есть если сделать определение: var astr: String; — то astr определится как AnsiString.

AnsiString можно представить в виде записи:
type // Это описательное определение,
TAnsiString = record // не предназначенное для компиляции .
RefCount: LongWord; //Счетчик ссылок
Length: LongWord; // Длина строки
Data: array[1..Length+1] of AnsiChar; //Массив символов, нумерация с единицы
end;

Так что AnsiString — это указатель на запись, только ссылается он не на начало записи, а на начало поля Data. Ты можешь это проверить сам (Приложение 1). Объяви заголовок строки вот так:

type
PStrRec = ^StrRec;
StrRec = packed record // Заголовок строки 8 байт
refCnt: Longint; // 4 байта – счетчик ссылок
length: Longint; // 4 байта – длина строки
end;

В коде объяви строку и указатель на структуру заголовка:
var
S: String;
P: PStrRec;

Получи указатель на заголовок строки S:

Здесь ты получил указатель на строку S и отступил от начала строки на 8 байт (длину заголовка StrRec). Теперь ты можешь смотреть значение счетчика, получать длину строки и даже менять их (но это не рекомендуется)

Memo1.Lines.Add(‘refCnt = ‘ + IntToStr(P.refCnt)); // Счетчик ссылок
Memo1.Lines.Add(‘length = ‘ + IntToStr(P.length)); // Длина строки

— Получить символ по его номеру (индексу) можно, обращаясь со строкой, как с динамическим массивом: astr[ i].

Delphi проверяет: попадает ли индекс в границы диапазона, как и с динамическими массивами (если включена проверка диапазона <$R+>). Но пустая длинная строка представлена нулевым указателем. Поэтому проверка границ пустой строки (при обращении к символу строки по индексу) приводит к ошибке доступа вместо ошибки выхода за границы диапазона.

По умолчанию проверка диапазона выключена ( <$R->или <$RangeChecks Off>), но лучше всегда ее включать, т.к. она помогает отловить многие ошибки, а в релизе сделает прогу менее чувствительной к BufferOverflow-атаке (т.н. строковое и массивное переполнение). По этой же причине всегда включай <$O+>или <$OverflowCheks On>. Выключай их только при серьезной проблеме с производительностью и только в критичных участках кода.

— Длина строки (Length) может изменяться с помощью функции SetLength. На настоящий момент максимальная длина длинной строки = 2 Гб (т.к. размер длины строки — 4 байта). Минимальная длина – 4 байта (пустая строка) Функция SizeOf(astr) возвратит 4 байта при любой длине строки, т.е. возвращается размер указателя.
— В конец строки автоматически записывается терминальный нуль #0 (но он не включается в общую длину строки). Поэтому строку легко преобразовать в тип PChar: PChar(astr). С короткой строкой такое преобразование не получится, потому что у нее в конце терминального нуля нет !
— AnsiString поддерживает многобайтовые строки. В отличие от PChar в AnsiString могут быть любые символы, даже несколько терминальных нулей! Но некоторые строковые фукции думают, что терминальный нуль в строке только один и что он в конце (например SysUtils.AnsiPos). Учитывай это!
— Счетчик ссылок RefCount используется в операциях присваивания и управляет жизненным циклом строки. Придуман для экономии памяти. Если мы копируем строку в другую переменную ( somestr := astr, то настоящего копирования памяти не происходит, а просто копируется указатель (AnsiString ведь указатель) и увеличивается на 1 счетчик ссылок. А вот если исходная строка изменяется (somestr := astr + ‘A’, то тогда создается новая уникальная запись со своим счетчиком ссылок.
— Если тебе очень нужно создать именно уникальную строку, а не увеличить счетчик ссылок, то используй функцию: procedure UniqueString (var str: string). Она гарантирует, что строка str до этого больше нигде не использовалась и, изменяя эту строку, ты больше нигде не напортачишь. Например может потребоваться указатель на строку PChar при работе с API-функциями. Тогда создай уникальную строку, преобразуй ее в PChar и спокойно передавай в функцию не опасаясь побочных эффектов.

Пример. Вывод русского текста в консольное окно. Это почему-то у многих вызывает трудности.

procedure WriteRussianText(Msg :String);
// вывод строки в консольное окно в OEM кодировке
begin
UniqueString(Msg); // получим уникальный экземпляр строки
Windows.CharToOem(PChar(Msg),PChar(Msg)); // преобразуем его
Write(Msg); // выведем текст на консоль
end;

— Компилятор сам управляет длинными строками, не доверяя это программисту, и вставляет вместо операций со строками свои процедуры. Память для длинной строки выделяется динамически. Компилятор почти всегда (про почти: см. в третьей статье — Переменная Result) инициализирует длинные строки: (примерно так) Pointer(astr) := nil; При выходе из области видимости (из процедуры, при разрушении объекта) компилятор вставляет процедуру финализации и освобождения динамической памяти примерно так: System._LStrClr(S);

PChar — нультерминальная строка
— pstr: PChar; — это нультерминальная строка (zero-terminated). Так называется, потому что представляет собой указатель на цепочку символов, заканчивающуюся терминальным нулем #0. Ее еще называют сишной строкой (из языка С, там она определяется как char*).
— type PChar = ^Char;
— Используется для вызова ANSI-версий API-функций (типа CreateFileA). VCL использует только ANSI-версии API-функций для совместимости со всеми версиями Windows, поэтому вызов CreateFile идентичен CreateFileA. В модуле SysUtils сделаны функции-оболочки для многих API-функций, в которые надо передавать String вместо PChar (все-таки PChar не родной паскалевкий тип).
— Delphi хранит терминальный нуль в конце длинных и широких строк для удобства преобразования в PChar, PAnsiChar и PWideChar.
— Можно рассматривать PChar, как указатель на array of Char. В этом случае индексы начинаются с нуля. Проверка на выход за границу массива не выполняется! Подпрограмма, сканирующая строку, ищет только #0.
— При приведении AnsiString к PChar надо помнить, что Delphi автоматически уничтожает строку, когда она больше не нужна (т.е. когда счетчик ссылок равен 0, например при выходе из процедуры) , и тогда в переменной PChar может оказаться некорректный указатель. Поэтому надо быть осторожным при сохранении указателя PChar для дальнейшего использования (pstr := PChar(astr) , а лучше делать это приведение только при передаче параметров в API-функцию. То же относится и к приведению WideString к PWideChar. Прочитай еще про UniqueString выше.
— Операции с PChar проходят медленнее, чем операции с AnsiString, потому-что сначала Delphi сканирует всю строку PChar, что определить ее длину, а уже потом производит с ней действия.
— PChar автоматически преобразуется в AnsiString: astr := patr; , но эта операция проходит медленно.

Чтобы избежать накладных расходов можно использовать:
procedure SetString(var Str: string; Buffer: PChar; Length: Integer); устанавливает длину Str равной Length и копирует Length символов из Buffer в Str (если Str — короткая строка, то Length должна быть ‹256). Эта процедура используется в исходном коде многих строковых функций Delphi.

PWideChar
— PWideChar — скажем так, это «широкая» нультерминальная строка.
— Для хранения символа используется 2 байта. Поддерживает стандарт Unicode.
— На конце — терминальный нуль #0.
— Может рассматриваться как указатель на array of WideChar. Нумерация начинается с нуля. Так же как и PChar — не контролирует выход за границы массива!
— Используется для передачи параметров в Unicode-версии API-функций (типа CreateFileW), подпрограммы OLE и COM.
— Создать строку PWideChar из String можно с помощью функции StringToOleStr. Только помни, что строка создается динамически и потом надо освободить память с помощью API-функции SysFreeString.

procedure SomeProc(S: String);
var
OleStr: PWideChar;
begin
OleStr := StringToOleStr(S); // создаешь широкую строку
try
CallSomeOLEProc(OleStr); // че-то там вызываешь
finally
SysFreeString (OleStr); // Освобождаешь память
end;
end;

WideString — широкая строка
— wstr: WideString; — широкая строка. Хранит строку в формате Unicode, то есть использует для хранения символа 2 байта (16-битовые символы WideChar).
— Первые 256 символов в Unicode (WideChar) и AnsiChar (Char) совпадают.
— Также, как и AnsiString, WideString отслеживает свою длину, дописывает в конец #0 (может быть преобразована в PWideChar), но не содержит счетчика ссылок, поэтому любое присваивание приводит к копированию строки в памяти.
— Delphi автоматически по мере надобности расширяет «узкие» строки и сужает «широкие».
— При приведении WideString к AnsiString используется кодовая страница ANSI. Преобразование не-ANSI-символов (с индексом больше 255) происходит, как принято в Windows по умолчанию (то есть зависит от национальных настроек). При этом приведении в строке могут оказаться многобайтовые символы. Чтобы управлять процессом преобразования надо напрямую вызывать API-функцию WideCharToMultiByte.

Многобайтовые строки — для сведения
— Многобайтовая строка — это строка, в которой символ может занимать более 1 байта (в Windows используются 2 байта). Не надо путать многобайтовые строки с Unicode — это разные вещи, хотя они приводятся друг к другу. В Unicode символ всегда занимает 2 байта, а многобайтовой строке он может занимать 1 или 2 байта (во как!).
— Нужны такие строки для некоторых национальных языков (японского, китайского), где используется больше 256 символов.
— Байт в многобайтовой строке может быть: одинарным символом, ведущим байтом (первым байтом символа) и завершающим байтом (т.е. вторым байтом). При обработке таких строк надо учитывать этот момент, т.к. символ, выглядящий как ‘A’ может оказаться вторым байтом многобайтового символа.
— Delphi не всегда корректно работает с многобайтовыми строками, и в этом случае нас опять спасает модуль SysUtils, где есть специальные функции. Для определения типа байта (по его индексу) в многобайтовой строке применяются функции SysUtils: ByteType и StrByteType:

type TMbcsByteType = (
mbSingleByte,// Одиночный однобайтовый символ
mbLeadByte, // Первый байт многобайтового символа
mbTrailByte);// Второй байт многобайтового символа

function ByteType (const S: String; Index: Integer): TMbcsByteType;

— В реальной работе многобайтовая строка может получиться при приведении W >- Если планируется программу (или компонент) продавать (да еще за бугром), то при встрече с многобайтовыми строками надо хотя бы корректно завершить работу (конечно лучше, чтобы прога их поддерживала). Встает вопрос: Как определить нужна ли поддержка многобайтовых строк? Ответ: В переменной var SysLocale: TSysLocale; хранится информация о региональных установках Windows по умолчанию и, если поддержка многобайтовых срок нужна, то SysLocale.FarEast = True.
— Правильно обрабатывать такие строки особенно важно для имен файлов. Ты ведь собираешься со своей программой на мировой рынок выходить .

На сладкое: resourcestring

— Что ты делаешь, когда тебе надо в программу включить строковые ресурсы? Наверное создаешь файл с расширением mystringresource.rc, пишешь в него по специальным правилам свои строки, компилишь файл в res c помощью brcc32.exe, включаешь в свой экзешник директивой компилятора <$R mystringresource.res>и потом загружаешь из него строки с помошью API-функций. Скажи мне, а нужна тебе такая морока? Разработчики Delphi, как я постоянно убеждаюсь, далеко не дураки и всё уже для тебя придумали.
— Всё что требуется от тебя — это объявить с своем модуле строковую константу вот так:

resourcestring
MyResString = ‘Hello World’;

— ВСЁ! Теперь твоя строка будет сохранена в строковом табличном ресурсе, под уникальным номером. Можешь обращаться с ней, как с обычной строковой константой. После компиляции проги можешь открыть ее ResHacker’ом или Restorator’ом и среди других строк увидишь свою. Учти, что номер(идентификатор) ресурса присваивается автоматически и может меняться от компиляции к компиляции. Так что особо на него не полагайся.
— Компилятор заменяет строковую константу на вызов LoadResSring для загрузки ресурса во время выполнения программы.
— Эти ресурсные строки очень полезны, если потом надо будет локализовать программу для другого языка. Поэтому как resourcestring надо объявлять все строковые констаты в программе: сообщения об ошибках и не только, хинты-подсказки и т.п. Тоже самое и даже в большей степени относится к разработке компонентов.
— Delphi сам назначает номера строковым ресурсам, так что можешь не беспокоиться насчет конфликтов идентификаторов resourcestring из разных модулей.
— Если ресурсная строка используется в качестве строки формата (например для функции SysUtils.Format), то обязательно включай в нее спецификаторы позиций (потом удобнее переводить будет, т.к. в другом языке и порядок слов другой):

resourcestring
ResErrorMsg = ‘Ошибка: невозможно сделать %0:s для %1:s , потому-что %2:s’;

— Адрес resourcestring — указатель типа PResStringRec, который ты можешь использовать для получения идентификатора ресурса во время работы программы:

type
PResStringRec = ^TResStringRec;
TResStringRec = packed
record
Module: ^Cardinal; // Модуль из которого загружен ресурс (чаще всего твой экзешник)
Identifier: Integer; // Идентификатор строкового ресурса
end;

— Получить номер строкового ресурса можно так:

var
ResID: Integer;
Res >
Успехов! Орехов Роман aka tripsin

unit Unit1;
// Модуль для изучения AnsiString и его счетчика ссылок
interface

uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls;

type
TForm1 = class(TForm)
Button1: TButton;
Memo1: TMemo;
procedure Button1Click(Sender: TObject);
// 1. Сначала попробуй процедуру без передачи параметра, потом
// раскомментируй параметр везде и проверь, как он передается,
// 2. при этом не забудь закомментировать строки обозначенные //***
// 3. Напоследок попробуй передать параметр как (var S: String)
procedure GetStringInfo<(S: String)>;
end;

var
Form1: TForm1;
S: String;
implementation

<$R *.dfm>
procedure TForm1.GetStringInfo<(S: String)>;
type
PStrRec = ^StrRec;
StrRec = packed record // Заголовок строки 8 байт
refCnt: Longint; // 4 байта
length: Longint; // 4 байта
end;
var
S: String; //***
P: PStrRec;
pp: ^Byte;
begin
S := ‘AAAAAAAAAA’; // Так S будет ссылаться на литерал //***
// S := S + ‘a’; // Раскомментируешь — S будет в динамической памяти
P := Pointer(Integer(s) — ; // Смещаемся к началу заголовка

// Проверяем размер переменной S
Memo1.Lines.Add(‘SizeOf(S) = ‘ + IntToStr(SizeOf(S)));
// Проверяем размер заголовка
Memo1.Lines.Add(‘Sizeof(StrRec) = ‘ + IntToStr(Sizeof(StrRec)));
Memo1.Lines.Add(‘refCnt = ‘ + IntToStr(P.refCnt)); // Счетчик ссылок
Memo1.Lines.Add(‘length = ‘ + IntToStr(P.length)); // Длина строки
pp := Pointer(S); // Получаем указатель на строку
inc(pp, P.length); // Передвигаемся в конец строки .
// . и смотрим, что там
Memo1.Lines.Add(‘Байт в конце строки = ‘ + IntToStr(Integer(pp^)));
Memo1.Lines.Add(‘S = ‘ + s); // Выводим строку
end;

procedure TForm1.Button1Click(Sender: TObject);
begin
S := ‘AAAAAAAAAAAAAAAA’;
GetStringInfo<( S)>;
end;

DelphiComponent.ru — бесплатно видеоуроки по Delphi, статьи, исходники

Строки в Delphi, или Жестокие игры со словами

Вначале было слово, А потом слова, слова, слова!

Людям привычнее общаться с помощью слов, а не чисел, поэтому для вы­вода сообщений и другой текстовой информации почти в каждой про­грамме используются строки. О важности этого типа данных говорит и тот факт, что в паскале был только 1 тип строк, а в Delphi их огромное количе­ство!

Очень часто мы будем рассматривать строки как одномерный мас­сив символов типа Char, но при этом следует помнить, что строки это не разновидность массива, а отдельный тип данных. К ним можно применять операции, отсутствующие у массивов, например, конкатенацию («сложение» строк).

Короткие строки

Тот самый, «паскалевский», тип строк в Delphi называется ShortString — короткая строка. Он используется всё реже и оставлен только для совме­стимости с более ранними версиями Delphi. Объявляют короткие строки с помощью зарезервированного слова string, после которого, подобно мас­сивам, в квадратных скобках указывают максимальную длину строки в диапазоне от 1 до 255:

Сразу после объявления длина строки sPascal равна нулю, хотя в памяти компьютера она всегда будет занимать 11 байтов. Почему 11, а не 10, как указано в объявлении?

Дело в том, что, действительно, для хранения каждого символа в памяти компьютера требуется 1 байт, что для 10 символов и составляет 10 бай­тов. А ещё 1 байт нужен для хранения текущей длины строки. Он имеет нулевой индекс в массиве, а символы строки — от 1 до заданной макси­мальной длины строки. Так как под хранение длины строки отводится 1 байт, то и длина строки не может превышать 255 символов.

Чтобы яснее представить, как хранятся строки в памяти компьютера, об­ратимся к Рис., верхний.

Итак, при запуске программы процессор компьютера выполняет машин­ный код, соответствующий строке исходного кода var sPascal: string[10]; При этом создаётся переменная sPascal, под неё отводится 11 байтов памяти, начиная, например, с адреса 1000 (конечно, адрес вы­мышленный, но точный адрес нам и не нужен) и по адресу 1000 заносится число 0 (на самом деле не число, а символ с кодом 0!), соответствующее текущей длине строки, то есть наша строка пустая: sPascal =

Присвоим строке какое-нибудь значение, к примеру, Pascal:

Содержимое памяти изменится (Рис. У10.1, средний). Теперь по адресу 1000 находится число 6 (длина строки), а далее — символы строки, которые имеют тип AnsiChar.

Если присваивать переменной sPascal другие значения, то, соответственно, будет изменяться и содержимое ячеек памяти. Как именно, вы теперь лег­ко можете догадаться. Нас могут заинтересовать только 2 предельных случая. Чтобы уничтожить строку (не саму переменную, а только её зна­чение!), достаточно её сделать пустой:

В этом случае строка вернётся в исходное состояние, показанное на рисун­ке Рис., верхний.

Если переменной присвоить строку, длина которой больше указанной в объявлении максимальной, например,

то все «лишние» символы будут просто проигнорированы, и в данном слу­чае длина строки будет равна 10, а её значение — Pascal1234. Эту ситуацию отражает Рис. У10.1, нижний.

Таким образом, Delphi нещадно пресекает все ваши — даже неумышленные — попытки испортить программу. В самом деле, под переменную отводится 10 байтов памяти, а вы желаете втиснуть туда строку, длина которой на 2 символа больше. Значит, остальные символы должны занять следующие 2 байта памяти, в которых уже есть что-то жизненно необходимое для про­граммы, и тем безнадёжно испортить её. А сможете ли вы потом найти ошибку в исходном коде?

Впрочем, если вы ярый последователь экстремального программирова­ния, переходите на язык С и вытворяйте с памятью компьютера самые не­потребные вещи, о которых здесь даже стыдно упоминать.

Поскольку короткая строка это почти массив символов, то мы легко можем добраться до каждого из них по его индексу. Снова обратим свой пылаю­щий взор на Рис. У10.1. На нём синим цветом выделены индексы массива, то есть номера символов в строке. Из него ясно видно, что

sPascal[1] =’P’, sPascal[2] = ‘a’, и так далее до

Но вы никогда не узнаете, что же находится дальше — sPascal[11] :

Delphi сочтёт ваше любопытство необоснованным и выдаст ошибку при первой же попытке запустить программу.

Наверное, вы думаете, что значение sPascal[0] имеет тип byte и вы легко сможете узнать длину строки? — А вот и нет! Оно тоже имеет тип AnsiChar — как и символы строки (это значит, что для Delphi там находится не число, а символ, код которого равен длине строки).

Но, «если нельзя, но очень хочется, то можно», поэтому просто объявите новую переменную типа byte (или integer)

и получите длину строки

Хотя можно было не стараться, а просто расширить кругозор, найти функцию Length и воспользоваться ею:

Результат будет тем же самым, но программа лишится части своей загадочности, которая так же гипнотически действует на не­окрепшие умы, как удав на кролика.

Если мы имеем право считать значение элемента sPascal[0], то мы можем и записать туда всякую ерунду.

Например, sPascal[0]: =#255;

(почему не просто 255?) — и длина строки увеличится до 255. Однако попробуйте что-нибудь записать в новую строку:

— всё равно ничего не получится.

Для таких крамольных действий имеется и встроенная процедура SetLength (sPascal, 255);, которая действует аналогично и столь же бесполезна для коротких строк. Разве что вы захотите от­сечь хвост у текущей строки. После выполнения процедуры SetLength (sPascal, 2);, останутся только 2 первых символа «Ра». Но это па вы можете станцевать и более красиво!

Если строка неполная, то есть короче максимальной длины, то значения символов за последним символом строки не определены! Обратимся к среднему рисунку. Первые 6 байтов памяти занимает строка Pascal, а дальше находятся ещё 4 «пустые» ячейки памяти. Не обольщайтесь, они не пустые, в них что-нибудь да есть!

Если вы присваивали строке значение Pascal123456, а потом Pascal, то в них останутся символы 1234, хотя длина строки теперь только 6 символов.

В других случаях эти ячейки могут занимать любые символы, поэтому ни­когда не используйте в своих программах значения символов за предела­ми присвоенного строке значения!

Если вы создаёте несколько строковых переменных равной длины, то удобнее объявить новые типы строк:

Затем вы можете использовать эти типы для объявления строковых переменных ограниченной длины:

AnsiString, или длинные строки, — основной тип строк в Delphi до послед­него времени. Они могут содержать до 231 символов и занимать в памяти компьютера до 2 гигабайтов.

Для объявления длинных строк также используется зарезервированное слово AnsiString, но максимальная длина строки в скобках не указывается, поскольку длина этих строк изменяется динамически:

Но главное отличие длинных строк от коротких вовсе не в длине строки. Символы длинной строки, как и короткой, образуют в памяти последова­тельность символов типа AnsiChar, подобную массиву, после которых Del­phi автоматически добавляет нулевой символ #0, который и является признаком конца строки. Кроме того, в четырёх байтах перед первым символом строки хранится текущая длина строки (и другая информация).

При изменении строки Delphi автоматически уменьшает или увеличивает размер памяти под строку так, чтобы она целиком могла там разместить­ся. Это значит, что вам не нужно заранее заботиться и высчитывать мак­симальную длину строки, в то время как при объявлении коротких строк это делать необходимо. Более того, даже пустая короткая строка всегда занимает в памяти заказанное число байтов. Интересный вопрос: сколько памяти занимает пустая длинная строка? Интересный ответ: нисколько, пустые строки в памяти не хранятся. Так что при объявлении длинной строки система выделяет только 4 байта для хранения адреса памяти, начиная с которого будет размещаться сама строка, и для пустой строки там будет константа nil.

Строки с нулевым символом в конце используются во многих язы­ках программирования, например в C и C++, а также в Windows API, чем и вызвано появление таких строк в Delphi.

Таким образом, переменная типа AnsiString — это указатель на об­ласть памяти, в которой хранятся символы строки.

Как мы уже выяснили, при объявлении длинной строки место в памяти для неё не выделяется. Если же присвоить строке значение (не пустую строку!), например,

то в памяти выделяется столько байтов, чтобы поместились все символы заданной строки, плюс ещё 1 байт для нулевого символа в конце строки (не нуля, а нулевого символа типа AnsiChar!), плюс 4 байта для хранения длины строки (они занимают в памяти место перед первым символом строки), плюс ещё 4 байта для подсчёта ссылок на строку (нам это значе­ние не потребуется) + 4 байта для хранения кодовой страницы.

В картинках это выглядит так

Очень похоже на короткую строку, к которой добавлен нулевой символ. Поэтому доступ к отдельным символам строки осуществляется точно так же, как и в случае коротких строк — по их индексу в массиве символов. За­помните, что индекс первого символа равен 1!

Таким образом, если

то после присвоения символьной переменной ch значения первого симво­ла строки sDelphi

её значение будет равно D, второго символа — е, шестого — i.

Если задать индекс, превышающий длину слова (в нашем случае 7 и боль­ше), то значение переменной ch будет равно нулевому символу.

Длинные строки Юникода

Длинные строки Юникода типа UnicodeString — основной тип строк в по­следних версиях языка Delphi. Они очень похожи на длинные ANSI-строки, но для каждого символа строки в них отводится 2 байта, поэтому они мо­гут содержать до 230 символов (вдвое меньше, чем строки ANSI) и занимать в памяти компьютера до 2 гигабайтов.

Для объявления длинных строк используются зарезервированные слова UnicodeString и string:

Таким образом, ключевое слово string — это всего лишь псевдоним более длинного слова UnicodeString. Оба эти слова обозначают один и тот же тип данных.

Длинные строки Юникода имеют ту структуру и те же свойства, что и ANSI-строки, поэтому мы не будем повторяться.

Объявление глобальных строковых переменных и типизированных констант можно совмещать с их инициализацией, то есть присваи­вать им начальное значение:

Обратите внимание: нулевой символ после строки указывать не нужно, он будет добавлен автоматически!

Другие типы строк

Строки WideString — основной тип строк платформы .NET — очень похожи на длинные строки Юникода, но символы имеют тип не UnicodeChar, а WideChar (эти типы совместимы с типами string и char). Они занимают 2 байта памяти каждый. Этот тип строк используется исключительно для работы с объектной моделью компонентов для ОС Microsoft (Component Object Model, COM).

Строки типа PAnsiChar, PChar (PUnicodeChar) и PWideChar- также очень похожи на длинные строки Delphi (точнее, наоборот: длинные строки по­хожи на эти строки), они также заканчиваются нулевым символом, но в строках этих типов отсчёт индексов ведётся от нуля, а не от единицы (вечная головная боль от массивов в языке С).

Строки типа PChar используются не только в языках C и C++ (нам до этого и дела нет), но и в функциях Windows API, без которых обойтись не удастся.

Нам придётся использовать эти невнятные строки только для передачи строковых параметров в чуждые нам функции, поэтому подробно рассматривать их не будем.

Мешанина из строк разных типов

Таким образом, строки типа string сочетают в себе все достоинства корот­ких строк паскаля и строк PChar языка С, избегая при этом их недостатков. Им можно присваивать значения коротких строк и строк PChar:

Строки PChar вреднее строк Delphi: они не позволяют присвоить им всуе значения ни коротких, ни длинных строк. Для этого необходимо явно пре­образовать их к типу PChar:

Очень часто в приложениях для вывода сообщений используется функция Windows API Application.MessageBox:

Первые два передаваемых ей параметра — текст сообщения и заголовок окна — имеют тип PChar. Если вы передаёте в функцию строковые констан­ты (выражения), то просто укажите их при вызове функции:

Если же ваши строки имеют тип string, то следует привести их к типу PChar:

Впрочем, для таких случаев лучше сразу использовать тип PChar:

Простые операции со строками

Строки типа string можно объединять в одну строку с помощью оператора + (оператор сцепления строк; сама операция называется конкатенацией строк). В качестве операнда можно использовать не только длинную, но и короткую строку, а также символьные и строковые переменные и кон¬станты.

Значением строки s3 будет новая строка s3 = stringl + string2. Длина строки будет равна длине всех строковых операндов, входящих в выражение. В нашем случае — 22.

Эти и многие другие операции со строками можно выполнить с помощью процедур и функций, рассматриваемых на одном из следующих уроков.

Для сравнения строк используются операторы отношения ( = <> =). Результат сравнения имеет логический тип, то есть значение true или false.

Сравнение строк производится слева направо, начиная с первого символа. Пустая строка всегда меньше строки, имеющей символы:

Если очередные символы двух сравниваемых строк не совпали, то меньше та, код символа которой меньше:

Если строки имеют разную длину и все символы более короткой строки совпали с соответствующими символами более длинной строки, то мень­ше первая строка:

Если строки имеют одинаковую длину и все символы совпали, то строки равны:

Илон Маск рекомендует:  Fstat взять статус файла
Понравилась статья? Поделиться с друзьями:
Кодинг, CSS и SQL