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;
source to share
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.
source to share