Iterating string characters and passing them to a BIOS API
The two samples don't quite do the same thing. The first sample starts by setting the location of the stack and data segments to some 'magic' location in memory:

mov ax, 07C0h
add ax, 288
mov ss, ax
mov sp, 4096
mov ax, 07C0h
mov ds, ax


The second sample doesn't (so it will use the SS and DS locations which are left in those registers by the O/S which loaded the program). I don't understand what 07C0h is and why there is that difference between the two programs.

Also the styles of assembly are different, for example one uses 0Eh and the other uses 0x0E. Are they both supported by NASM?



The first sample ends with jmp $ which I don't understand: does it mean "jump to here" i.e. "infinite loop"? The .done: ret label seems to be unreachable.

The second sample ends with exit_function: ret which I guess returns to the O/S.




  The most significant place in both code for me is the loop for printing a char from the whole string, so I want you to tell me which variant is more correct (or maybe both variants are bad and you can provide me a much better sample).


Your using BIOS int 10h which Wikipedia says is:

Teletype output AH=0Eh  AL = Character, BH = Page Number, BL = Color (only in graphic mode)


The first sample doesn't initialize the BX register (so it may may contain a semi-random value), the second does. BX may or may not have an effect (in fact this whole "BIOS Teletype output" API may or may not work) depending on current the video mode.

If you're interested in speed then the following algorithm can be much faster than using the BIOS interrupt:


Set the right video mode
Set registers (for example, es:di) to point to the main memory location which is shared with the video card (perhaps 0xB0000 if I recall correctly)
Write the character into video memory


Or instead of writing characters the the BIOS one at a time, there's a BIOS API which lets you write an entire string. I copied the following from 8086 BIOS and DOS interrupts (IBM PC):

INT 10h / AH = 13h - write string.

input:
AL = write mode:
    bit 0: update cursor after writing;
    bit 1: string contains attributes.
BH = page number.
BL = attribute if string contains only characters (bit 1 of AL is zero).
CX = number of characters in string (attributes are not counted).
DL,DH = column, row at which to start writing.
ES:BP points to string to be printed.
example:
mov al, 1
mov bh, 0
mov bl, 0011_1011b
mov cx, msg1end - offset msg1 ; calculate message size. 
mov dl, 10
mov dh, 7
push cs
pop es
mov bp, offset msg1
mov ah, 13h
int 10h
jmp msg1end
msg1 db " hello, world! "
msg1end:


As for your print loops:

print_string:
    mov ah, 0Eh

.repeat:
    lodsb
    cmp al, 0
    je .done
    int 10h 
    jmp .repeat


The first one (above) looks tight: i.e. it's good and short, with the mov ah, 0Eh performed once outside the loop. lodsb doesn't affect flags, so the separate cmp is necessary. There may be a faster way to compare al with 0, perhaps something like or al,al.

I don't have a BIOS reference manual, but one thing it's lacking is a check of the 'return code' to see whether the output might have failed. This suggests that if the call fails it might leave a return code in ah. You want your ah preserved, so you might want to check that at least the first one didn't fail, and then have a loop which assumes that subsequent won't fail.

If your string weren't null-terminated, if instead you knew the length of it ...

HelloString:
dw 0bh
db 'Hello World'


... then you could load the length into cx and use the loop instruction, something like:

mov si, HelloString
lodsw
mov cx,ax
mov ah,0eh
.repeat:
lodsb
int 10h 
loop .repeat
; .done:
ret


Beware that the above may not be the fastest way to loop, anymore. I once read that the more complicated opcodes (like loop) perform more slowly on modern CPUs than using several primitive instructions (e.g. dec cx and jnz). The reason for that is that a developer (or compiler) will choose and place those more primitive opcodes in such as way as to use both pipelines and not cause a stall. Writing assembly which keeps both pipelines busy is an advanced art (which, they try to build-in to compilers). If I look at the output from a compiler, I hardly recognize it any more! Because, it often uses several instructions instead of one or two. Nevertheless I think that the loop above is compact and idiomatic (if you're writing code for another programmer to read, not writing code in order use every last CPU cycle).

The second one doesn't seem to be optimized for size or speed:

PrintCharacter:
    mov ah, 0x0E
    mov bh, 0x00
    mov bl, 0x07
    int 0x10
    ret

PrintString:
    next_character:
        mov al, [si]
        inc si
        or al, al
        jz exit_function
        call PrintCharacter
        jmp next_character
    exit_function:
        ret



Why not use lodsb instead of mov al, [si] and inc si?
Why set bl and bh instead of setting bx?
Why not set bx and ah before the loop begins (I think that a call to int 10h should preserve the contents of the bx register?
Why make PrintCharacter a subroutine to be called, instead of including those instructions inline?



  Also, about code style. I don't know which style is more correct in ASM, for e.g. in the 1st variant I'm using the name of the functions like: print_string, but as for the 2nd variant, I'm using the style, which is closer to C# name style PrintCharacter, so which variant of naming is more suitable in ASM language?


The NASM manual seems to use naming conventions more like print_string. Chapter 9 talks about writing assembly called from high-level languages. Names of HLL functions are subject to name mangling so if you want to write the C printf function in assembly you might need to call it _printf.