Delphi and using the Teampeak SDK

I am trying to use the TeamSpeak3 SDK with my delphi, I have come across a couple of problems, the code compiles and seems to work, most of the code is sample code from sample projects except trying to read the returned data.
 1. Will I free the memory correctly? 2. Did I read the returned data from the SDK correctly, or could this be done better?

I asked a question about this SDK on another thread, but I was obviously too quick to mark the thread as an answer.: /

SDK documentation:

To get a list of all currently visible clients on the specified virtual server:
unsigned int ts3client_getClientList (serverConnectionHandlerID, result);
uint64 serverConnectionHandlerID;
anyID ** result;
Image parameters β€’ serverConnectionHandlerID The ID of the server connection handler for which the list of clients is requested.
β€’ result The
address of the variable that receives a NULL-terminated array of client IDs.
If no error occurs, the array must be freed using ts3client_freeMemory.
Returns ERROR_ok for success; otherwise, the error code defined in public_errors.h. If an error occurs, the result array is not initialized and should not be released.

A list of all channels on the specified virtual server can be requested using: unsigned int ts3client_getChannelList (serverConnectionHandlerID, result);
uint64 serverConnectionHandlerID;
uint64 ** result;
Image parameters β€’ serverConnectionHandlerID
ID of the server connection handler for which the list of channels is requested.
β€’ result The
address of the variable that receives a NULL-terminated array of channel identifiers. If no error occurs, the array must be freed using ts3client_freeMemory.
Returns ERROR_ok for success; otherwise, the error code defined in public_errors.h. If an error occurs, the result array is not initialized and should not be released.

unsigned int ts3client_getCaptureDeviceList (modeID, result); const char * modeID; char **** result;

Parameters
β€’ modeID
Specifies the playback / recording mode to use. There may be different device lists for different modes. Valid modes are returned by ts3client_getDefaultPlayBackMode / ts3client_getDefaultCaptureMode and ts3client_getPlaybackModeList / ts3client_getCaptureModeList.
β€’ result The
address of the variable that receives a null-terminated array {{char * deviceName, char * deviceID}, {char * deviceName, char * deviceID}, ..., NULL}.
If the function does not return an error, the elements of the array and the array itself must be freed using ts3client_freeMemory.

Returns ERROR_ok for success; otherwise, the error code defined in public_errors.h. On error, the result array is not initialized and should not be released.

There are playback and capture devices available for this mode, as well as the current operating systems by default. Device return values ​​can be used to initialize devices.
To query the default playback and recording device, call

To get a list of all available playback and capture devices for a specified mode, call

unsigned int ts3client_getPlaybackDeviceList (modeID, result); const char * modeID;
char **** result;
unsigned int ts3client_getCaptureDeviceList (modeID, result); const char * modeID;
char **** result;

Parameters
β€’ modeID
Specifies the playback / recording mode to use. There may be different device lists for different modes. Valid modes are returned by ts3client_getDefaultPlayBackMode / s3client_getDefaultCaptureMode and ts3client_getPlaybackModeList / ts3client_getCaptureModeList.

β€’ result The
address of the variable that receives a null-terminated array {{char * deviceName, char * deviceID}, {char * deviceName, char * deviceID}, ..., NULL}.
If the function does not return an error, the elements of the array and the array itself must be freed using ts3client_freeMemory.
Returns ERROR_ok for success, otherwise the error code defined in public_errors.h. On error, the result array is not initialized and should not be freed.

unsigned int ts3client_startConnection (serverConnectionHandlerID, id, ip, port, alias, defaultChannelArray, defaultChannelPassword, serverPassword);

uint64 serverConnectionHandlerID; const char * identity; Const char * ip; unsigned int port; const char * nickname; const char ** defaultChannelArray; // This is what I don't get const char * defaultChannelPassword; const char * serverPassword;

Parameters β€’ serverConnectionHandlerID The
unique identifier for this connection to the server. Created with ts3client_spawnNewServerConnectionHandler
β€’ identity The identity of the clients. This string must be created by calling ts3client_createIdentity.
Note that the application only needs to create the ID once, store the string locally, and reuse it for future connections.
β€’ ip
Hostname or IP of the TeamSpeak 3 server. If you pass in the hostname instead of the IP address, the Lib client will try to resolve it to the IP address, but the feature may block for an unusually long period of time when resolving. If you are relying on a function to return quickly, we recommend that you resolve the hostname yourself (for example, asynchronously) and then call ts3client_startConnection with the IP instead of the hostname.
β€’ The
default UDP server port of the TeamSpeak 3 server is 9987. TeamSpeak 3 uses UDP. TCP support may be added in the future.
β€’ alias At logon, the client tries to take this alias on the connected server. Note that this is not necessarily the actually assigned alias, as the server can modify the alias ("gandalf_1" instead of the requested "gandalf") or discard blocked names.
β€’ defaultChannelArray
String array defining the path to the channel on the TeamSpeak 3 server. If the channel exists and the user has sufficient rights and, if necessary, sends the correct password, the channel will be connected at login.
To define the path to an arbitrary level subchannel, create an array of channel names detailing the default channel position (for example, "grandparent", "parent", "mydefault", "").The array ends with an empty string.
Pass NULL to join the server's default channel.
β€’ defaultChannelPassword The
password for the default channel. Pass an empty string if no password is required or no default channel is specified.
β€’ serverPassword The
password for the server. Pass an empty string if the server does not require a password.
All strings must be encoded in UTF-8 format

Attention!
Lib Lib functions returning C strings or arrays dynamically allocate memory to be freed by the caller using ts3client_freeMemory. It is only important to access and free memory if the function returned ERROR_ok.
If the function returns an error, the result variable is not initialized, so freeing or accessing it could crash the application.
See the "Calling Lib Client Functions" section for additional notes and examples.
The print error string for a specific error code can be requested using unsigned int ts3client_getErrorMessage (errorCode, error);
unsigned int errorCode;
char ** error;
Image parameters β€’ errorCode
The error code is returned from all Client Lib functions.
β€’ error The
address of the variable that receives the error message string encoded in UTF-8 format. If the return value of the function is not ERROR_ok, the line must be emitted using ts3client_freeMemory. Example:

unsigned int error;
anyID myID;
error = ts3client_getClientID(scHandlerID, &myID); /* Calling some Client Lib function */
if(error != ERROR_ok) {
    char* errorMsg;
    if(ts3client_getErrorMessage(error, &errorMsg) == ERROR_ok) 
    { /* Query printable error */
        printf("Error querying client ID: %s\n", errorMsg);
        ts3client_freeMemory(errorMsg); /* Release memory */
    }
}


type
  PPanyID = ^PAnyID;
  PanyID = ^anyID;
  anyID  = word;

var
   error: longword;
   errormsg: PAnsiChar;


procedure TfrmMain.RequestOnlineClients;
var
  ids : PanyID;
  pids : PanyID;
  aid : anyID;
begin
  error := ts3client_getClientList(FTSServerHandlerID, @ids);
  if (error <> ERROR_ok) then
  begin
    if (ts3client_getErrorMessage(error, @errormsg) = ERROR_ok) then
    begin
      LogMsg(Format('Error requesting online clients: %s', [errormsg]));
      ts3client_freeMemory(errormsg);
    end;
  end else
      begin
         pids := ids;
         while (pids^ <> 0) do
         begin
           aid := pids^;
           LogMsg(format('userid %u',[aid, getUserNickNameById(aid)]));
           inc(pids);
         end;
         ts3client_freeMemory(@pids^);  // here potiential problem
      end;
end;

procedure TfrmMain.RequestChannels;
var
  ids : PUint64;
  pids : PUint64;
  aid : uint64;
  channelname : PAnsiChar;

begin
  error := ts3client_getChannelList(FTSServerHandlerID, @ids);
  if (error <> ERROR_ok) then
  begin
    if (ts3client_getErrorMessage(error, @errormsg) = ERROR_ok) then
    begin
      LogMsg(Format('Error requesting channels: %s', [errormsg]));
      ts3client_freeMemory(errormsg);
    end;
  end else
    begin
       pids := ids;
       while (pids^ <> 0) do
       begin
         aid := pids^;
         LogMsg(format('channelid %u %s',[aid, getChannelNameById(aid)]));
         inc(pids);
       end;
       ts3client_freeMemory(@pids^);
    end;
end;

**// Added details 25-11-2014**
char* defaultMode;  

if(ts3client_getDefaultPlayBackMode(&defaultMode) == ERROR_ok) {
char*** array;  
    if(ts3client_getPlaybackDeviceList(defaultMode, &array) == ERROR_ok) {
       for(int i=0; array[i] != NULL; ++i) {
           printf("Playback device name: %s\n", array[i][0]); /* First element: Device name */  
           printf("Playback device ID: %s\n", array[i][1]); /* Second element: Device ID */  
           /* Free element */  
          ts3client_freeMemory(array[i][0]);  
          ts3client_freeMemory(array[i][1]);  
          ts3client_freeMemory(array[i]);  
       }  
   ts3client_freeMemory(array); /* Free complete array */  
   } else {  
     printf("Error getting playback device list\n");  
   }  
} else {  
printf("Error getting default playback mode\n");  
}  

Example to query all available playback devices:
char* defaultMode;  

if(ts3client_getDefaultPlayBackMode(&defaultMode) == ERROR_ok) {
char*** array;
if(ts3client_getPlaybackDeviceList(defaultMode, &array) == ERROR_ok) {
  for(int i=0; array[i] != NULL; ++i) {  
    printf("Playback device name: %s\n", array[i][0]); /* First element: Device name */  
    printf("Playback device ID: %s\n", array[i][1]); /* Second element: Device ID */  
    /* Free element */  
    ts3client_freeMemory(array[i][0]);  
    ts3client_freeMemory(array[i][1]);  
    ts3client_freeMemory(array[i]);  
}  
ts3client_freeMemory(array); /* Free complete array */  
} else {  
  printf("Error getting playback device list\n");  
}  
} else {  
printf("Error getting default playback mode\n");  
}  

procedure TfrmMain.ConnectServer2;
var
  version : PAnsiChar;
  DefaultChannelsArr : PPAnsiChar;
begin
  if Connected then Exit;

  if not ClientInitialized then
    InitializeClient;

  // Dbl Check if we can connect
  if ClientInitialized then
  try

    // Connect to server on localhost:9987 with nickname "client", no default channel, no default channel password and server password "secret"
    // error := ts3client_startConnection(FTSServerHandlerID, identity, '127.0.0.1', 9987, 'Delphi Client', nil, '', 'secret'); // example connection setup
    ts3check(ts3client_startConnection(FTSServerHandlerID, PAnsiChar(FSetup.ClientIdentity), PAnsiChar(FSetup.ServerAddress), FSetup.FServerPort, PAnsiChar(FSetup.NickName), nil, '', PAnsiChar(FSetup.ServerPassword)));
    { TODO -oMe -cImportant : Need to check how to convert ansistrings to UTF8 } // UnicodeToUtf8() // AnsiToUtf8()...


     // Query and print client lib version
    ts3check(ts3client_getClientLibVersion(@version));
    LogMsg(Format('Client lib version: %s', [version]));
    ts3client_freeMemory(version);  // Release dynamically allocated memory

     // Do not set connected here, wait for the callback connected state
  except
    on e: exception do
    begin
      UnInitializeClient; // clear the hole thing and start over
      LogMsg(Format('Error connecting: %s',[e.Message]));
    end;
  end;
end;

      

+3


source to share


1 answer


I would translate ts3client_getClientList

as follows:

function ts3client_getClientList(serverConnectionHandlerID: UInt64; 
  out result: PAnyID): Cardinal; cdecl; external '...';

      

I think the parameter is out

better than double pointer. This makes the goal clearer.

Then, to call the function, I would write it like this:

var
  ids: PAnyID;
  idarr: TArray<anyID>;
....
ts3check(ts3client_getClientList(serverConnectionHandlerID, ids));
try
  idarr := GetIDs(ids);
finally
  ts3check(ts3client_freeMemory(ids));
end;

      

Here ts3check

is the function that throws an exception if passed a return value other than ERROR_ok

.



function ts3client_getErrorMessage(error: Cardinal; 
  out errormsg: PAnsiChar): Cardinal; cdecl; external '...';

....

procedure ts3check(error: Cardinal);
var
  errormsg: PAnsiChar;
  errorstr: string;
begin
  if error = ERROR_ok then
    exit;

  if ts3client_getErrorMessage(error, @errormsg) <> ERROR_ok then
    raise Ets3Error.CreateFmt('Error code %d', [error]);

  errorstr := UTF8ToUnicodeString(errormsg);
  ts3client_freeMemory(errormsg);
  raise Ets3Error.CreateFmt('Error code %d (%s)', [error, errorstr]);
end;

      

And you can implement GetIDs

like this:

function GetIDs(const ids: PAnyID): TArray<anyID>;
var
  Count: Integer;
  p: PAnyID;
begin
  Count := 0;
  p := ids;
  while p^ <> 0 do 
  begin
    inc(Count);
    inc(p);
  end;

  SetLength(Result, Count);
  Count := 0;
  p := ids;
  while p^ <> 0 do 
  begin
    Result[Count] := p^;
    inc(Count);
    inc(p);
  end;
end;

      

Now I don't think you really need an array of ids. You are probably happy to handle inline ids. I don't want to get into this, because it leads me to your code, which I can't see. You won't be writing code exactly like I did above, but you can hopefully use the above as a source of ideas.

The key to this is to try and encapsulate as much of the messy boiler plate code as possible. The call wrapper ts3client_getErrorMessage

makes the higher-level code much easier to read. Use things like OleCheck

and Win32Check

as inspiration.

One point I would do is that it doesn't feel like this code lives inside a form. It is generally cleaner to remove this kind of code from the UI. Create a wrapper for this library that can be used by your UI code. Keep this wrapper in a dedicated block and hide the gross details like that.

+1


source







All Articles