This question already has an answer here:
- PInvoke struct with array of unspecified length 2 answers
Background
I am trying to write a high level libspotify wrapper based on a modified libspotify.net (http://libspotifydotnet.codeplex.com/). As libspotify.net is just a thin (and completely bugged ... ) pinvoke layer, it does not handle the utf8 encoding libspotify uses.
My idea was to convert the string to a byte[] and change the signatures appropriately.
I have this native struct:
typedef struct sp_session_config {
int api_version;
const char *cache_location;
const char *settings_location;
const void *application_key;
size_t application_key_size;
const char *user_agent;
const sp_session_callbacks *callbacks;
void *userdata;
bool compress_playlists;
bool dont_save_metadata_for_playlists;
bool initially_unload_playlists;
const char *device_id;
const char *proxy;
const char *proxy_username;
const char *proxy_password;
const char *ca_certs_filename;
const char *tracefile;
} sp_session_config;
Working version:
[DllImport("libspotify")]
public static extern sp_error sp_session_create(ref sp_session_config config, out sp_session sessionPtr);
public struct sp_session_config
{
public int api_version;
public IntPtr cache_location;
public IntPtr settings_location;
public IntPtr application_key;
public uint application_key_size;
public IntPtr user_agent;
public IntPtr callbacks;
public IntPtr userdata;
public bool compress_playlists;
public bool dont_save_metadata_for_playlists;
public bool initially_unload_playlists;
public IntPtr device_id;
public IntPtr proxy;
public IntPtr proxy_username;
public IntPtr proxy_password;
public IntPtr ca_certs_filename;
public IntPtr tracefile;
}
This version puts the work on the developer using the library.
My version:
public struct sp_session_config_internal
{
public int api_version;
public byte[] cache_location;
public byte[] settings_location;
public byte[] application_key;
public uint application_key_size;
public byte[] user_agent;
public IntPtr callbacks;
public IntPtr userdata;
public bool compress_playlists;
public bool dont_save_metadata_for_playlists;
public bool initially_unload_playlists;
public byte[] device_id;
public byte[] proxy;
public byte[] proxy_username;
public byte[] proxy_password;
public byte[] ca_certs_filename;
public byte[] tracefile;
}
[DllImport("libspotify", EntryPoint="sp_session_create")]
private static extern sp_error sp_session_create_internal(ref sp_session_config_internal config, out sp_session sessionPtr);
public static sp_error sp_session_create(ref sp_session_config config, out sp_session sessionPtr)
{
sp_session_config_internal config_internal = new sp_session_config_internal();
config_internal.api_version = config.api_version;
config_internal.application_key = config.application_key;
config_internal.application_key_size = (uint)config.application_key.Length;
config_internal.ca_certs_filename = SH.StringToUtf8Bytes( config.ca_certs_filename);
...
var err = sp_session_create_internal(ref config_internal, out session);
...
}
When running, this gives the following error inside libspotify: Access violation reading location 0x000001c0 I googled and read somewhere that sometimes only the first array element gets copied. I tried decorating all arrays with
[MarshalAs(UnmanagedType.LPArray, ArraySubType = UnmanagedType.U1)]
That gave the exception "Cannot marshal field 'cache_location' of type 'sp_session_config_internal': Invalid managed/unmanaged type combination (Array fields must be paired with ByValArray or SafeArray)."
tl;dr
The complicated solution with IntPtr and manual marshalling works, the easier solution with byte arrays for utf8 strings gives a access violation when the library reads. Is there an easier way than manual marshalling with intptr?