Thanks, Chebmaster! This adds some background knowledge and fits well what I found out in the meantime. That's reassuring.

Ok, all the following was a matter of tedious research and a bit trial-and-error. I knew almost nothing of it when I started this thread, and I wasn't confident:

1. Don't call SteamAPI_ISteamClient_CreateSteamPipe (see my last post). This is already called on the API's initialization. You get that handle from SteamAPI_GetHSteamPipe, which is declared in steam_api.h.

2. You have to connect to the Steam client's interface with SteamInternal_CreateInterface, which is declared in steam_api_internal.h. From this function you get a pointer to that interface, and you have to pass this interface pointer to every function whose name begins with "SteamAPI_ISteamClient_".
By the way, that's what the parameter instancePtr is intended for (see Chebmaster's post above).

3. From the Steam client you get pointers to all the other interfaces. In order to get the pointer to ISteamUtils for example you have to call SteamAPI_ISteamClient_GetISteamUtils.

4. Then you can use the member functions of each interface. And again you have to pass the appropriate interface pointer through that parameter instancePtr.

5. Additional requiremement: If you want to get a pointer to any of the above interfaces (see 2. and 3.), you have to specify the interface's version by passing a string.
The current version string is defined in the header file for each interface.
In isteamclient.h we find this one:
Code:
#define STEAMCLIENT_INTERFACE_VERSION "SteamClient017"
And in isteamutils.h:
Code:
#define STEAMUTILS_INTERFACE_VERSION "SteamUtils009"
Putting it all together:

Code:
unit steam;

{$H+}{$mode objfpc}

interface

uses
  dynlibs, ctypes, sysutils;

const
  steamlib = 'steam_api.dll';

function steam_init: longint;

implementation

var
  steamlib_handle: tlibhandle = nilhandle;
  steampipe_handle: longint;
  pisteamclient: pointer = nil;
  pisteamutils: pointer = nil;

var
  steamapi_init: function(): boolean; cdecl = nil;
  steamapi_shutdown: procedure(); cdecl = nil;
  steamapi_gethsteampipe: function(): longint; cdecl = nil;
  steaminternal_createinterface: function(chversion: pchar): pointer; cdecl = nil;
  steamapi_isteamclient_getisteamutils: function(instanceptr: pointer; hsteampipe: longint; chversion: pchar): pointer; cdecl = nil;
  steamapi_isteamutils_getappid: function(instanceptr: pointer): longword; cdecl = nil;

procedure steam_exit;
begin
  steamapi_shutdown();
  unloadlibrary(steamlib_handle);
end;

function steam_init: longint;
var
  chversion: string;
  appid: longword;
begin
  steamlib_handle := loadlibrary(steamlib);
  
  if steamlib_handle=nilhandle then exit(0); // no steam DLL, run without steam
  
  addexitproc(@steam_exit);

  pointer(steamapi_init) := getprocedureaddress(steamlib_handle, pchar('SteamAPI_Init'));
  pointer(steamapi_shutdown) := getprocedureaddress(steamlib_handle, pchar('SteamAPI_Shutdown'));
  pointer(steamapi_gethsteampipe) := getprocedureaddress(steamlib_handle, pchar('SteamAPI_GetHSteamPipe'));
  pointer(steaminternal_createinterface) := getprocedureaddress(steamlib_handle, pchar('SteamInternal_CreateInterface'));
  pointer(steamapi_isteamclient_getisteamutils) := getprocedureaddress(steamlib_handle, pchar('SteamAPI_ISteamClient_GetISteamUtils'));
  pointer(steamapi_isteamutils_getappid) := getprocedureaddress(steamlib_handle, pchar('SteamAPI_ISteamUtils_GetAppID'));

  if not steamapi_init() then exit(-1); // steam client not running, program should halt

  steampipe_handle := steamapi_gethsteampipe();

  chversion :='SteamClient017';
  pisteamclient := steaminternal_createinterface(pchar(chversion));

  chversion :='SteamUtils009';
  pisteamutils := steamapi_isteamclient_getisteamutils(pisteamclient, steampipe_handle, pchar(chversion));

  appid := steamapi_isteamutils_getappid(pisteamutils);

  // output to logfile: appid

  result := 1; // ok
end;

end.
Ok, this really works!

But it's not enough to make use of common Steam features. Most functions have to get data from the Steam server, or they send data to it. This is done asynchronously, and therefor you have to work with callback functions or callback results. There is a C++ template thingy that I have to puzzle out, and of course there are more internal functions involved. Would be to easy otherwise.

To be coninued.