C#:将字符串编码为 utf8 char*

标签 c# .net pinvoke libspotify

背景

我正在尝试基于修改后的 libspotify.net ( http://libspotifydotnet.codeplex.com/ ) 编写一个高级 libspotify 包装器。 由于 libspotify.net 只是一个薄弱的(且完全有缺陷的……)pinvoke 层,因此它不处理 libspotify 使用的 utf8 编码。

我的想法是将字符串转换为 byte[] 并适本地更改签名。

我有这个 native 结构:

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;

工作版本:

    [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;
    }

此版本将工作交给使用该库的开发人员。

我的版本:

    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);
    ...
    }

运行时,libspotify 内部出现以下错误:读取位置 0x000001c0 时出现访问冲突 我用谷歌搜索并读到有时只有第一个数组元素被复制。 我尝试用装饰所有数组

        [MarshalAs(UnmanagedType.LPArray, ArraySubType = UnmanagedType.U1)]

这给出了异常“无法编码(marshal)类型为“sp_session_config_internal”的字段“cache_location”:托管/非托管类型组合无效(数组字段必须与 ByValArray 或 SafeArray 配对)。”

tl;博士

使用 IntPtr 和手动编码的复杂解决方案有效,使用 utf8 字符串的字节数组的更简单解决方案在库读取时会出现访问冲突。 有没有比使用 intptr 手动编码更简单的方法?

最佳答案

我已经解决了完全相同的问题 - 为 .NET 包装 libspotify。我采用了 IntPtr 路线,并编写了一个辅助类来进行编码。我发现数组编码非常令人抓狂,不值得为之撕心裂肺。

这是辅助类:

https://github.com/openhome/ohLibSpotify/blob/master/src/ohLibSpotify/Utf8String.cs#L99

简化版本:

internal class Utf8String : IDisposable
{
    IntPtr iPtr;
    public IntPtr IntPtr { get { return iPtr; } }
    public int BufferLength { get { return iBufferSize; } }
    int iBufferSize;
    public Utf8String(string aValue)
    {
        if (aValue == null)
        {
            iPtr = IntPtr.Zero;
        }
        else
        {
            byte[] bytes = Encoding.UTF8.GetBytes(aValue);
            iPtr = Marshal.AllocHGlobal(bytes.Length + 1);
            Marshal.Copy(bytes, 0, iPtr, bytes.Length);
            Marshal.WriteByte(iPtr, bytes.Length, 0);
            iBufferSize = bytes.Length + 1;
        }
    }
    public void Dispose()
    {
        if (iPtr != IntPtr.Zero)
        {
            Marshal.FreeHGlobal(iPtr);
            iPtr = IntPtr.Zero;
        }
    }
}

用法可以在这里看到:

https://github.com/openhome/ohLibSpotify/blob/master/src/ohLibSpotify/SpotifySession.cs#L104

public static SpotifySession Create(SpotifySessionConfig config)
{
    IntPtr sessionPtr = IntPtr.Zero;
    IntPtr listenerToken;
    using (var cacheLocation = SpotifyMarshalling.StringToUtf8(config.CacheLocation))
    using (var settingsLocation = SpotifyMarshalling.StringToUtf8(config.SettingsLocation))
    using (var userAgent = SpotifyMarshalling.StringToUtf8(config.UserAgent))
    using (var deviceId = SpotifyMarshalling.StringToUtf8(config.DeviceId))
    using (var proxy = SpotifyMarshalling.StringToUtf8(config.Proxy))
    using (var proxyUsername = SpotifyMarshalling.StringToUtf8(config.ProxyUsername))
    using (var proxyPassword = SpotifyMarshalling.StringToUtf8(config.ProxyPassword))
    using (var traceFile = SpotifyMarshalling.StringToUtf8(config.TraceFile))
    {
        IntPtr appKeyPtr = IntPtr.Zero;
        listenerToken = ListenerTable.PutUniqueObject(config.Listener, config.UserData);
        try
        {
            NativeCallbackAllocation.AddRef();
            byte[] appkey = config.ApplicationKey;
            appKeyPtr = Marshal.AllocHGlobal(appkey.Length);
            Marshal.Copy(config.ApplicationKey, 0, appKeyPtr, appkey.Length);
            sp_session_config nativeConfig = new sp_session_config {
                api_version = config.ApiVersion,
                cache_location = cacheLocation.IntPtr,
                settings_location = settingsLocation.IntPtr,
                application_key = appKeyPtr,
                application_key_size = (UIntPtr)appkey.Length,
                user_agent = userAgent.IntPtr,
                callbacks = SessionDelegates.CallbacksPtr,
                userdata = listenerToken,
                compress_playlists = config.CompressPlaylists,
                dont_save_metadata_for_playlists = config.DontSaveMetadataForPlaylists,
                initially_unload_playlists = config.InitiallyUnloadPlaylists,
                device_id = deviceId.IntPtr,
                proxy = proxy.IntPtr,
                proxy_username = proxyUsername.IntPtr,
                proxy_password = proxyPassword.IntPtr,
                tracefile = traceFile.IntPtr,
            };
            // Note: sp_session_create will invoke a callback, so it's important that
            // we have already done ListenerTable.PutUniqueObject before this point.
            var error = NativeMethods.sp_session_create(ref nativeConfig, ref sessionPtr);
            SpotifyMarshalling.CheckError(error);
        }
        catch
        {
            ListenerTable.ReleaseObject(listenerToken);
            NativeCallbackAllocation.ReleaseRef();
            throw;
        }
        finally
        {
            if (appKeyPtr != IntPtr.Zero)
            {
                Marshal.FreeHGlobal(appKeyPtr);
            }
        }
    }

    SpotifySession session = SessionTable.GetUniqueObject(sessionPtr);
    session.Listener = config.Listener;
    session.UserData = config.UserData;
    session.ListenerToken = listenerToken;
    return session;
}

因为手工编写这些东西很快就会变得乏味,所以绝大多数包装器和 DllImport 声明都是从 api.h 头文件自动生成的。 (因此,您不会在 github 存储库中找到它们,您需要下载项目并构建它才能查看所有生成的代码。)

它都是自由许可的(2-clause BSD),所以您可以随意使用它或借用它的一部分。

关于C#:将字符串编码为 utf8 char*,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/17438339/

相关文章:

c# - 计算后台工作人员 OnProgressChanged 事件的总剩余时间和总时间

c# - 如何在 Visual Studio 解决方案中查找最常用的类

c# - 删除接口(interface)中的冗余类型

c# - 这种方式在 c# 中传递参数真的很好吗

c# - 使用 C# 从另一个应用程序的 SysListView32 中的数据网格读取单元格项目

javascript - 如何使用 jquery ajax 从 url 将变量传递到 Controller

c# - C# 是否支持函数组合?

.net - REDIS 耐久性 ?如何自动过期数据?

c# - 返回时导致 SEGFAULT 的非托管函数指针

c# - 将 C# 结构的指针传递给 C++ API