How to copy binary data 1s. Expanding the functionality of working with binary data

The 1C:Enterprise 8 technology platform allows you to save arbitrary files in the information base, retrieve them from there and use them in various ways. Let's look at these operations using examples.

Before uploading a file to the 1C information base, you need to obtain the full address of the file on disk. Working with file selection dialogs is described in .

To store files, use an attribute (or register resource) with the type StorageValues.

Uploading an arbitrary file to the 1C information base

Any file can be represented as binary data and loaded into Value Storage.

When converting binary data to an object StorageValues design used new StorageValues(Data, Compression) with two parameters:

  1. Data— binary data that needs to be stored in storage
  2. Compression— compression ratio of the Deflation algorithm. Integer in the range -1...9. -1 is the default compression level. 0 - no compression, 9 - maximum compression. Default value: -1. The parameter is optional; if not specified, then compression is not used.

//Convert the file to binary data
File = New BinaryData(Path) ;

//Create a new Value Storage object

DataStorage = NewValueStorage(File, NewDataCompression(9) ) ;

Saving an arbitrary file from the 1C infobase to disk

To save a file from the 1C database to disk, you need to determine the path and file name. To do this, there is a file saving dialog, working with which is described in.

//Get binary data from storage
//Data Storage - attribute of an object with the Value Storage type

//Write the received data to disk
//The Path variable contains the full address of the file on disk
Data. Write(Path) ;

Viewing a file located in the 1C information base

To view a file saved in the database, you must have an application installed on your computer that opens the file.

//Get the name of the temporary file with the required extension
//In the Extension variable you need to put the file extension, for example "pdf"
Path = GetTemporaryFileName(Extension) ;

//Receive data from storage
//Data Storage - attribute of an object with the Value Storage type
Data = Datastore. Get() ;

//Write data to a temporary file
Data. Write(Path) ;

//Trying to open the file in the intended application
//If the application is not found, the system dialog "Open with..." will appear.
LaunchApplication(Path) ;

Print (Ctrl+P)

16.3. Working with binary data

16.3.1. general information

When implementing application solutions, there may be situations where it is necessary to analyze various binary data. For example, you need to determine the file type using a signature or perform some manipulations with a picture. To work with binary data, 1C:Enterprise provides special software interfaces. Next, we will look at the possibilities for working with binary data.
All work with binary data is based on the concept of a stream. Flow is a logical generalization of an arbitrary (in general case) data source (Stream object). The system does not provide the ability to create an independent Stream object that is not associated with any source. But there are derived objects that can be created - a stream associated with a file on disk (FileStream object) or a stream created in memory (MemoryStream object). A stream allows you to both read data and write it. To determine the possibility of performing certain operations, a stream (and derived objects) has special methods that allow you to determine which
operations are available with this thread (methods AvailableRecord(), AvailableRead(), AvailableChangePosition()).
If you need to work with a stream at a higher level, in particular, read/write data such as a number (of different bit depth) or a string, then the DataRead/DataWrite objects are intended for this. Using these objects, you can take a more structured approach to the binary data located in the stream. So, for example, knowing the format of a file, you can quite comfortably read such a file, obtaining the necessary data from the headers (which, as a rule, are represented by the types number and string), skipping unnecessary data blocks and loading the necessary ones for processing.
The general scheme for working with binary data can be represented as follows:

  1. Stream receiving in progress
  2. A Data Reader or Data Writer object is created.
  3. Using the object created in step 2, the required actions are performed.
  4. The object created in step 2 is closed.
  5. If no more operations are required, the stream obtained in step 1 is closed.
  6. If you need to continue working with the stream, you can set a new position in the stream (if this operation is supported) and continue working starting from step 2.

It is worth noting that it is possible to combine paragraphs 1 and 2. In other words, the system provides the ability to create objects Read Data/Write Data directly from, for example, a BinaryData object.
To perform various operations with binary data, the system provides the ability to obtain some part of the stream as a separate fragment with random (byte-by-byte) access (object BufferBinaryData). The buffer size is set when created and cannot be changed later. When working with a binary data buffer, it is possible to work with numbers of different bit depths as
as one whole. In this case, it is possible to specify the byte order in words: “little endian” or “big endian” (big endian). It is also possible to split one buffer into several and combine several binary data buffers into one resulting buffer.
It is important to note that working with a binary data buffer can significantly simplify the implementation if working with binary data is implemented on the client application side in asynchronous mode. In this case, reading data into the buffer will be performed as an asynchronous operation, and working with the buffer data will be synchronous.
Working with binary data is available on the client side (including the web client) of the application and on the server side, as well as in synchronous and asynchronous work schemes. Further examples will use a synchronous work scheme.

16.3.2. Reading binary data

As an example of reading binary data, we will consider the task of determining the correct file format that was selected in the system for further use. A .wav file with audio data will be used as the file being checked. To store .wav files, Resource Interchange File Format (RIFF) is used, a description of which is given at the link:

https://msdn.microsoft.com/enus/library/windows/desktop/ee415713.aspx (in English). For the reading example the following format information will be used:
1. The first 4 bytes of the file contain the format identifier: RIFF.
2. the next 4 bytes contain the size of the actual audio data in little-endian byte order.
3. The next 4 bytes contain the text type of data used: WAVE.
To perform these actions you will need the following code in the built-in language:

Read = New ReadData(FileName, ByteEndian.LittleEndian);
FileFormat = Read.ReadCharacters(4);
DataSize = Read.ReadInteger32();
FileType = Read.ReadCharacters(4);
If File Format<>“RIFF” Then
Report("This is not a RIFF file");
Return ;
EndIf ;
If FileType = “WAVE” Then
Report (“This is a WAV file with data, size ” + DataSize + ” bytes”);
Otherwise
Report (“This is not a WAV file”);
Return;
endIf;

Let's look at the example in more detail.
First, the file whose name is contained in the FileName variable is opened, the file is opened for reading ( FileOpenMode.Open), will only read from the file ( FileAccess.Read) and a 16-byte buffer will be used for reading.
Then a stream is generated for reading data, which will have the least significant byte order for data of the Number type. Then 4 characters, a 32-bit integer, and 4 more characters are read from the resulting stream. The resulting data is analyzed and based on the results of the analysis, a decision is made as to whether the selected file is a .wav file or not.

16.3.3. Writing binary data

Writing binary data to a file, in the simplest case, is done as follows:

Entry = New WriteData(FileName);
For Index = 0 To 255 Cycle
Write.WriteByte(Index);
EndCycle;
Record.Close() ;

This example writes to a file a sequence of bytes from 0 to 255 (0xFF in hexadecimal). This is the simplest recording option.
You can also use a method similar to the reading method discussed in the previous example, where a file stream is obtained and data is written to this file stream.

16.3.4. Working with a binary data buffer

As mentioned above, the binary data buffer provides a convenient way to manipulate fragments of binary data.
Not only reading data is supported, but also writing.
As an example, we will consider parsing the RIFF file header from the data reading example (see here). To build the example, exactly the same information about the file format will be used. Thus, it is necessary to read from the source file a buffer the size of the file header. The header consists of three 4-byte fields. Thus, 12 bytes need to be read.

Buffer = New BufferBinaryData(12);
File = FileStreams.Open(Temporary Files Directory() + “Windows Logon.wav”, FileOpenMode.Open, FileAccess.Read);
File.Read(Buffer, 0, 12);
Size = Buffer.ReadInteger32(4);
StreamString = newStreamInMemory(Buffer);
StreamRows.Go(0, PositionInStream.Start);

FileFormat = ReadLines.ReadCharacters(4, “windows-1251”);
ReadLines.Close();
StreamRows.Go(8, PositionInStream.Start);
RowReader = new DataReader(RowStream);
FileType = ReadLines.ReadCharacters( 4, “windows-1251”);
ReadLines.Close();

The process of getting data into a binary data buffer is nothing special. Further operations require some comments. Reading numbers of any supported bit depth is possible from any position in the buffer. In this example Buffer.ReadInteger32(4); means reading a 32-bit integer starting from byte 4 of the buffer. Thus, if you need to read multiple numbers located in different places in the buffer, this can be done without direct positioning in that buffer.
Reading a string, however, is not supported by the binary data buffer. Therefore, you should use an object that allows you to do this: Read Data. A DataReader object cannot be created from a binary data buffer. But based on a binary data buffer, you can create a stream that is a universal intermediary between the physical storage location of information (file, binary data buffer) and a high-level object that allows you to work with this data.
When a DataReader object is created based on a stream, it begins reading data from the position that is currently installed in the stream. Therefore, in the example, the position in the stream is first set, and then a DataReader object is created and the required number of characters is read. For a detailed description of the difference between the number of bytes and characters when reading strings, see the next section 16.3.5

16.3.5. Features of use

When using binary data, you should take into account the features of working with data of the String type. The peculiarity is that the length of the string that the global context function StrLength() returns is measured in characters. In symbols, you should indicate the size of the data to be read/written in the methods for writing/reading strings in objects for working with binary data ( ReadCharacters(),
ReadString(), WriteCharacters(), WriteString()). However, there is no unambiguous option for converting the length of a string in characters to a similar parameter in bytes. Depending on the contents of the string and the encoding, this ratio will be different. Therefore, when working with any data structures that include strings of variable length, you should clearly understand in what units the string lengths are expressed.
If in the available data the string length is indicated in bytes, and the string is specified in a multi-byte variable-length encoding (for example, UTF-8), then using binary data objects, reading such a structure from a file into data of the String type is generally impossible.
But in this case, you can easily change the read/write position in the file stream. If the length of a string is specified in characters, then it becomes possible to read such a string into data of the String type, but it becomes impossible to change the read/write position in such a stream.
To get the length of a string in bytes, you can use the following function to convert the string to a BinaryData object:

Function Get Binary Data From String(Value StrParameter, Value Encoding = “UTF-8”)
MemoryThread = NewMemoryThread;
Writer = New WriteData(StreamMemory);
Writer.Write String(StrParameter, Encoding);
Writer.Close();
Return StreamMemory.CloseAndGetBinaryData();
EndFunction

The actual size in bytes can be obtained by calling the Size() function on the BinaryData object, which is obtained as a result of the function.
Simultaneous use of objects is not recommended Read Data/Write Data and stream objects. If between two successive reading operations from ReadData or two successive write operations to WriteData there is a change in position in the stream with which the Ch objects work ShadowData/WriteData– an exception is generated. Thus, the following example demonstrates the correct change of position in a stream when writing data to a stream:

Stream = newStreamInMemory();

WriteData.WriteString("Hello World!");
WriteData.Close();
Stream.Go (0, PositionInStream.Start);
DataWrite = newDataWrite(Stream);
WriteData.WriteString("Bye!");
WriteData.Close();
The following example hi to an exception being thrown:

Stream = NewStreamInMemory();

WriteData.WriteString(“Hello, world!”);
Stream.Go(0, PositionInStream.Start);
// The next line will throw an exception
WriteData.WriteString(“Bye!”);
At the same time, situations are possible when the system behavior will be incorrect, but no errors will be generated:

Stream = GetStream();
ReadData = new ReadData(Stream);
TestString = ReadData.Read();
InitialPosition = Stream.CurrentPosition();
DataWrite = newDataWrite(Stream);
WriteData.WriteString(“Unexpected string”);
WriteData.Close();
Stream.Go(InitialPosition, PositionInStream.Start);
// In general, it is impossible to determine what value will be placed in the TestString2 variable
TestLine2 = ReadData.ReadLine();

The behavior described in this section is caused by o Data Reader/Data Writer objects use their own buffers when working with a stream. As a result, the actual position of the thread differs from the logical position, which is formed as a result of the completed operations.
Also, the simultaneous use of Data Reader and Data Writer objects, which use one thread for their work, is not supported.

Implemented in version 8.3.10.2168.

We are gradually increasing the functionality for working with binary data. There are several reasons for this. Firstly, we did not implement everything that we had planned. And secondly, in the process of discussing new opportunities, we received a number of wishes from you, which we also decided to implement.

New functions for converting binary data to different formats

In a global context, we have added a large number of new functions for converting binary data. So, for example, you can perform forward and reverse conversion of binary data into a regular string, format string Base64 and format string BinHex. In addition, you can convert the binary data itself into formats Base64, BinHex and back.

Similar conversions are supported for the type BufferBinaryData. In addition, you can convert a binary data buffer to binary data and vice versa.

In addition, two new functions allow you to split binary data into several parts, and vice versa, to combine several objects of the type BinaryData into one. In this case, the new object will contain the data of all parts in the order that you specify.

These functions are similar in concept to splitting and merging files, but in many cases they are more efficient. Because there is no need to first save the binary data to a file, and because there is no unnecessary copying of data when splitting.

Adding the ability to work with streams to objects that work with files

Since the use of binary data is largely related to file operations, we considered it completely logical and natural to add work with streams to those objects that currently read and write files in one way or another.

As a result, you can now open streams for reading and writing when using objects such as:

  • ReadingText And WriteText;
  • ReadingFastInfoSet And EntryFastInfoSet;
  • ReadingHtml And PostHtml;
  • Reading JSON And JSON entry;
  • Reading XML And XML entry;
  • Reading Zip File And Record Zip File.

You can receive the body as a stream when working with HTTP:

  • HTTPRequest And HTTPResponse;
  • HTTPServiceRequest And HTTPServiceResponse.
  • Text Document;
  • TabularDocument;
  • FormattedDocument;
  • GeographicalScheme;
  • GraphicScheme;
  • FTPConnection.

Writing to a stream is now available when working with types Picture And ConvertToCanonicalXML. And besides this, working with streams is now supported in various methods that types have XSL Conversion, Cryptography Manager, CertificateCryptography And HashingData.

Efficient copying by reading and writing data

The binary tools we implemented made it possible to copy streams. But on large volumes of data this operation was not performed very efficiently.

Therefore, the type ReadData we have implemented a new method CopyB(). It not only eliminates this problem, but also simplifies the text, making it more understandable.

For example, previously it was possible to receive binary data from one stream and write it to another stream.

Now there is no need to receive binary data; copying is performed at the data reading stage.

The nice thing is that you can copy not only to a stream, but also to an object WriteData. This option is convenient when, in addition to data from the source stream, you need to write some of your own data to the output stream.

Bitwise logical operations on a binary data buffer

You can now use bitwise logical operations when working with binary data buffers. As a result of these operations, the result of a bitwise combination of the original bytes and bytes in the given buffer will be written to the source buffer according to the rules of the selected logical operation. We implemented the following operations:

  • WriteBitAnd();
  • WriteBitOr();
  • WriteBitExclusiveOr();
  • WriteBitIne();
  • Invert().

A good example of the use of bitwise logical operations is the task of decoding the exchange format with retail equipment. For example, the exchange format with retail equipment is described by a 1-byte field. This field contains a set of characteristics describing the product range:

  • Bits 0-2: tax rate;
  • Bit 3: 0 - piece goods, 1 - weight goods;
  • Bit 4: 0 - allow sale, 1 - prohibit sale;
  • Bit 5: 0 - enable quantity counting, 1 - disable quantity counting;
  • Bit 6: 0 - single sale is prohibited, 1 - single sale is allowed;
  • Bit 7: Reserved.

Then the code that extracts this information and presents it in a form convenient for further processing may look like this.

Getting a number from hexadecimal and binary literals

  • NumberFromHexString();
  • NumberFromBinaryString().

Binary literals are useful for defining masks when used in conjunction with bitwise operations. For example, in the previous example with analysis of the exchange format with commercial equipment, masks are specified using decimal numbers. This is not very convenient, since when writing and reading code you need to constantly mentally translate the decimal number into the binary system.

It is much more convenient to use binary literals instead. At the same time, the code becomes more clear and the likelihood of errors is significantly reduced.

Hexadecimal literals are convenient to use when parsing technical formats: image, sound, video formats.

Changes in the technology of external NativeAPI components

Previously, there were a number of restrictions when transferring binary data between 1C:Enterprise and an external component. For example, it was impossible to transfer binary data to an external component, and when working in a web client, exchanging binary data was generally impossible.

Now we are removing all these restrictions. You can exchange binary data in both directions and even in the web client.

This will not affect the operation of existing external components in any way. They will work as before. But in newly created components you can now pass objects as parameters BinaryData.

Binary data in 1C is intended for storing files of arbitrary format. With their help you can:

  • Organize interaction using a binary protocol with various devices;
  • Store files of any formats as metadata object attributes;
  • Convert text data to binary (most often used for sending reports);
  • Work with binary data in memory.

What the system can do

When working with binary data, Platform 8.3 can perform the following actions:

  1. Read and write binary data;
  2. Move data from client to server and back using temporary storage;
  3. Initialize an object of the “Picture” type using binary files;
  4. Read them from the World Wide Web using the objects “Mail Attachment”, “HTTP Connection”, etc.
  5. Use cryptographic tools to encrypt and sign important attachments;
  6. Using the “Data Hashing” object, calculate the hash function.

Saving data to details

For example, let's create a directory in a test configuration.

In fact, using the same directory to store information about nomenclature and binary image data is a little incorrect. With sufficiently large volumes of data and heavy, large files, unwanted downtime and “brakes” in the operation of the system may occur. From the point of view of the system, it would be much more correct to organize a separate “Pictures” directory, a link to which we could set as a props type.


It is important to note that due to the fact that attributes of the “ValueStorage” type containing binary data are not available in managed application mode, they can only be accessed using the FormAttributesValue method.


The message field represents a value store binary data record.

Reading data from props

Let's create a processing that will output the file stored in binary form in our configuration into a spreadsheet document (this is necessary, for example, to print a company logo).


Basically, this is all the code we need. Using the Get() operator, we read the binary data stored in the corresponding directory attribute and transfer it to the “Picture” object, which will be shown in the upper left cell of the form’s spreadsheet document (Fig. 9).

Fig.9

Data Conversion

It is not common, but it happens that when working with non-standard exchanges with external systems, it is necessary to convert data from binary format to Base64 format or vice versa.

In most cases, the platform automatically converts the data; if this does not happen, you need to use global translation functions:

  1. Base64String – converts the specified value into a string of the corresponding encoding;
  2. Base64Value – does the reverse conversion.

Optimization of the above code

The code presented in Fig. 4 certainly works, but with one significant caveat: if the “Modality use mode” checkbox is selected in the configuration properties (Fig. 10). Otherwise, using it will cause an error.
Fig.10

To prevent this from happening, while in the directory element form module, go to the menu Text->Refactoring->Deprecated synchronous calls->Convert module calls.

After some time, synchronous calls will automatically be converted to asynchronous, and the code will take the form (Fig. 11)

Fig.11

mob_info