delphipointersdelphi-xeunicode-stringansistring

Converting UnicodeString to PAnsiChar in Delphi XE


In Delphi XE I am using the BASS audio library, which contains this function:

function BASS_StreamCreateURL(url: PAnsiChar; offset: DWORD; flags: DWORD; 
    proc: DOWNLOADPROC; user: Pointer):HSTREAM; stdcall; external bassdll;

The 'url' parameter is of type PAnsiChar, so in my code I do a cast:

FStreamHandle := BASS_StreamCreateURL(PAnsiChar( url ) [...]

The compiler emits a warning on this line: "suspicious typecast of string to PAnsiChar". In trying to eliminate the warning, I found that the recommended way is to use a double cast:

FStreamHandle := BASS_StreamCreateURL(PAnsiChar( AnsiString( url )) [...]

This does eliminate the warning, but the BASS function now returns error code 2 ("cannot open file"), which tells me the URL string it receives is somehow broken. I cannot see what the bass DLL actually receives, but using a breakpoint in the debugger the string looks good:

var
  s : PAnsiChar;
begin
  s := PAnsiChar( AnsiString( url ));

At this point string s appears fine, but the BASS function fails when I pass it. My initial code: PAnsiChar( url ) works well with BASS, but emits a warning.

So what's the correct way of getting from UnicodeString to PAnsiChar without a warning?


Solution

  • I am amazed that

    BASS_StreamCreateURL(PAnsiChar( url ) [...]
    

    works. If url is a unicode string, each character will occupy two bytes. If, for instance, the string is test, it will read

    7400 6500 7300 7400 0000                t e s t #0                     (Unicode)
    

    in memory. Notice that the string ends at the null character (0000) When you do

    PAnsiChar(url)
    

    you will tell the compiler that the memory at this address should be thought of as an AnsiString. But if you consider the above sequence of bytes, you will find only "t" in this case. Indeed, as an AnsiString, the sequence should be interpreted as

    74 00 65 00 73 00 74 00 00 00           t #0 e #0 s #0 t #0 #0 #0      (Ansi)
    

    which is the string "t", ending with the null character (00).

    PAnsiChar(AnsiString(url))
    

    on the other hand will first convert the unicode string to an ansi string, that is, you will obtain

    74 65 73 74 00                           t e s t #0                    (Ansi)
    

    which is the string "test" ending with the null character (00).

    Update

    I am not psychic (yet), but maybe the Bass library actually does require a pointer to a UnicodeString as the first argument to this function? That would explain why the seemingly odd

    PAnsiChar(url)
    

    works. The library might say "Hey, just give me the pointer to the unicode string, and I'll do some manual processing with it" and the code above does just that (a pointer is just a pointer (that is, an unsigned 32 bit integer)...). But the compiler will complain, of course, because the library explicity told the compiler that it will require a PAnsiChar, and normally PAnsiChar(SomeUnicodeString) is bad. If this is the case, of course, then,

    PAnsiChar(AnsiChar(url))
    

    doesn't work. The library expects /the address of/ a unicode string, but gets /the address of/ an ansi string.

    Update 2

    If my hypothesis in Update 1 is correct, the declaration

    function BASS_StreamCreateURL(url: PWideChar; offset: DWORD; flags: DWORD; 
        proc: DOWNLOADPROC; user: Pointer):HSTREAM; stdcall; external bassdll;
    

    would make the skies blue again.

    Update 3

    From the documentation:

    NOTE: Delphi 2009 users should use the BASS_UNICODE flag where possible

    I bet that the problem will go away if you specify this flag when you call BASS_StreamCreateURL!

    From the sample unit Main.pas:

    Channel := BASS_StreamCreateFile(FALSE, PChar(OpenDialog.FileName), 0, 0,
        0 {$IFDEF UNICODE} or BASS_UNICODE {$ENDIF});