C#:将字符串编组为utf8 char * [英] C#: marshall strings to utf8 char*

查看:97
本文介绍了C#:将字符串编组为utf8 char *的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在尝试基于修改后的libspotify.net( http://libspotifydotnet.codeplex .com/). 由于libspotify.net只是一个薄薄的(而且是完全错误的...)pinvoke层,因此它无法处理libspotify使用的utf8编码.

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.

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

My idea was to convert the string to a byte[] and change the signatures appropriately.

我有这个本地结构:

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

此版本使用库将工作交给开发人员.

This version puts the work on the developer using the library.

    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 我在谷歌上搜索并阅读了某个地方,有时只有第一个数组元素会被复制. 我尝试用

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)]

给出了异常无法封送'sp_session_config_internal'类型的字段'cache_location':无效的托管/非托管类型组合(数组字段必须与ByValArray或SafeArray配对)."

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)."

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

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?

推荐答案

我已经解决了完全相同的问题-为.NET包装libspotify.我走了IntPtr路线,并编写了一个帮助程序类来进行编组.我发现阵列编组绝对令人发疯,不值得把头发扯下来.

I have tackled the exact same problem - wrapping libspotify for .NET. I went the IntPtr route, and wrote a helper class to do the marshalling. I found array marshalling to be utterly maddening, and not worth tearing your hair out over.

这是帮助程序类:

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仓库中找到它们,您需要下载该项目并进行构建以查看所有生成的代码.)

Because it quickly gets tedious to write this stuff by hand, the vast majority of the wrappers and the DllImport declarations are automatically generated from the api.h header file. (Thus, you won't find them in the github repo, you'd need to download the project and build it to see all the generated code.)

它都是自由许可的(2句BSD),因此可以随意使用或借用它.

It's all liberally licensed (2-clause BSD), so feel free either to use it or to borrow bits of it.

这篇关于C#:将字符串编组为utf8 char *的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆