DLNA android关于Platinum库的dmr底层c++代码实现

来源:互联网 发布:数据传输线 编辑:程序博客网 时间:2024/05/21 21:38

最近公司正在做关于DLNA的项目,初期在网上大概了解了一些DLNA相关知识,可以用来开发的框架有Platinum,Cling和Cybergarage。同学们可以选择适合自己的框架进行学习开发,至于为什么选择Platinum库是因为他是一个跨平台的C++库,效率较高,稳定性也很强。

下载地址:http://sourceforge.net/projects/platinum/

网上的示例帖子很少,有一大部分都是蓝斯老师的,在这里要感谢他。下面开始讲解利用Platinum库开发步骤:


1.NDK下的降Platinum库编译成so文件

大家可以参考这篇博文http://blog.csdn.net/lancees/article/details/8789678,讲解的很详细了。

下载的Platinum为1-0-5-13_0ab854版本,博文中提到的将LOCAL_LDLIBS += -laxTls改为LOCAL_LDLIBS += -laxTLS新版本已经修复了。


2.Platinum库c++的实现

大家可以在目录Platinum\Source\Platform\Android\module\platinum\jni\下找到platinum-jni.cpp,这里边有示例代码:

/*****************************************************************||      Android JNI Interface||      (c) 2002-2012 Plutinosoft LLC|      Author: Sylvain Rebaud (sylvain@plutinosoft.com)| ****************************************************************//*----------------------------------------------------------------------|       includes+---------------------------------------------------------------------*/#include <assert.h>#include <jni.h>#include <string.h>#include <sys/types.h>#include "platinum-jni.h"#include "Platinum.h"#include <android/log.h>/*----------------------------------------------------------------------|   logging+---------------------------------------------------------------------*/NPT_SET_LOCAL_LOGGER("platinum.android.jni")/*----------------------------------------------------------------------|   functions+---------------------------------------------------------------------*/__attribute__((constructor)) static void onDlOpen(void){}/*----------------------------------------------------------------------|    JNI_OnLoad+---------------------------------------------------------------------*/JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void* reserved){    NPT_LogManager::GetDefault().Configure("plist:.level=FINE;.handlers=ConsoleHandler;.ConsoleHandler.outputs=2;.ConsoleHandler.colors=false;.ConsoleHandler.filter=59");    return JNI_VERSION_1_4;}/* * Class:     com_plutinosoft_platinum_UPnP * Method:    _init * Signature: ()J */JNIEXPORT jlong JNICALL Java_com_plutinosoft_platinum_UPnP__1init(JNIEnv *env, jclass){    NPT_LOG_INFO("init");    PLT_UPnP* self = new PLT_UPnP();    return (jlong)self;}/* * Class:     com_plutinosoft_platinum_UPnP * Method:    _start * Signature: (J)I */JNIEXPORT jint JNICALL Java_com_plutinosoft_platinum_UPnP__1start(JNIEnv *, jclass, jlong _self){    NPT_LOG_INFO("start");    PLT_UPnP* self = (PLT_UPnP*)_self;        return self->Start();}/* * Class:     com_plutinosoft_platinum_UPnP * Method:    _stop * Signature: (J)I */JNIEXPORT jint JNICALL Java_com_plutinosoft_platinum_UPnP__1stop(JNIEnv *, jclass, jlong _self){    NPT_LOG_INFO("stop");    PLT_UPnP* self = (PLT_UPnP*)_self;        return self->Stop();}

编译后你会发现没有办法开启一个设备,因为PLT_UPnP中并没有加入PLT_DeviceHostReference对象,所以不能开启设备。但是在\Platinum\Source\Tests\MediaRenderer目录下大家可以找到MediaRendererTest.cpp文件:

/*****************************************************************||   Platinum - Test UPnP A/V MediaRenderer|| Copyright (c) 2004-2010, Plutinosoft, LLC.| All rights reserved.| http://www.plutinosoft.com|| This program is free software; you can redistribute it and/or| modify it under the terms of the GNU General Public License| as published by the Free Software Foundation; either version 2| of the License, or (at your option) any later version.|| OEMs, ISVs, VARs and other distributors that combine and | distribute commercially licensed software with Platinum software| and do not wish to distribute the source code for the commercially| licensed software under version 2, or (at your option) any later| version, of the GNU General Public License (the "GPL") must enter| into a commercial license agreement with Plutinosoft, LLC.| licensing@plutinosoft.com| | This program is distributed in the hope that it will be useful,| but WITHOUT ANY WARRANTY; without even the implied warranty of| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the| GNU General Public License for more details.|| You should have received a copy of the GNU General Public License| along with this program; see the file LICENSE.txt. If not, write to| the Free Software Foundation, Inc., | 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.| http://www.gnu.org/licenses/gpl-2.0.html| ****************************************************************//*----------------------------------------------------------------------|   includes+---------------------------------------------------------------------*/#include "PltUPnP.h"#include "PltMediaRenderer.h"#include <stdlib.h>/*----------------------------------------------------------------------|   globals+---------------------------------------------------------------------*/struct Options {    const char* friendly_name;} Options;/*----------------------------------------------------------------------|   PrintUsageAndExit+---------------------------------------------------------------------*/static voidPrintUsageAndExit(char** args){    fprintf(stderr, "usage: %s [-f <friendly_name>]\n", args[0]);    fprintf(stderr, "-f : optional upnp server friendly name\n");    fprintf(stderr, "<path> : local path to serve\n");    exit(1);}/*----------------------------------------------------------------------|   ParseCommandLine+---------------------------------------------------------------------*/static voidParseCommandLine(char** args){    const char* arg;    char**      tmp = args+1;    /* default values */    Options.friendly_name = NULL;    while ((arg = *tmp++)) {        if (!strcmp(arg, "-f")) {            Options.friendly_name = *tmp++;        } else {            fprintf(stderr, "ERROR: too many arguments\n");            PrintUsageAndExit(args);        }    }}/*----------------------------------------------------------------------|   main+---------------------------------------------------------------------*/intmain(int /* argc */, char** argv){       PLT_UPnP upnp;    /* parse command line */    ParseCommandLine(argv);    PLT_DeviceHostReference device(        new PLT_MediaRenderer(Options.friendly_name?Options.friendly_name:"Platinum Media Renderer",                              false,                              "e6572b54-f3c7-2d91-2fb5-b757f2537e21"));    upnp.AddDevice(device);    bool added = true;    upnp.Start();    char buf[256];    while (gets(buf)) {        if (*buf == 'q')            break;        if (*buf == 's') {            if (added) {                upnp.RemoveDevice(device);            } else {                upnp.AddDevice(device);            }            added = !added;        }    }    upnp.Stop();    return 0;}

可以看到代码中构建了一个PLT_DeviceHostReference对象,然后用upnp.AddDevice(device)添加进去,就可以正常开启一个dmr设备了。(我在实现自己的platinum-jni时,编译会报undefined reference to `PLT_MediaRenderer::PLT_MediaRenderer(char const*, bool, char const*, unsigned int, bool)的错误,可以尝试在mk文件中添加LOCAL_C_INCLUDES += $(PLT_SRC_ROOT)/Devices/MediaRenderer,问题解决)。

接下来我们要做的事情就是将c++中接受到的action事件反射到java层进行处理:

此时我们就要去\Platinum\Source\Devices\MediaRenderer目录下找PltMediaRenderer.cpp文件:

/*----------------------------------------------------------------------|   PLT_MediaRenderer::OnAction+---------------------------------------------------------------------*/NPT_ResultPLT_MediaRenderer::OnAction(PLT_ActionReference&          action,                             const PLT_HttpRequestContext& context){    NPT_COMPILER_UNUSED(context);    /* parse the action name */    NPT_String name = action->GetActionDesc().GetName();    // since all actions take an instance ID and we only support 1 instance    // verify that the Instance ID is 0 and return an error here now if not    NPT_String serviceType = action->GetActionDesc().GetService()->GetServiceType();    if (serviceType.Compare("urn:schemas-upnp-org:service:AVTransport:1", true) == 0) {        if (NPT_FAILED(action->VerifyArgumentValue("InstanceID", "0"))) {            action->SetError(718, "Not valid InstanceID");            return NPT_FAILURE;        }    }serviceType = action->GetActionDesc().GetService()->GetServiceType();if (serviceType.Compare("urn:schemas-upnp-org:service:RenderingControl:1", true) == 0) {if (NPT_FAILED(action->VerifyArgumentValue("InstanceID", "0"))) {action->SetError(702, "Not valid InstanceID");return NPT_FAILURE;}}/* Is it a ConnectionManager Service Action ? */if (name.Compare("GetCurrentConnectionInfo", true) == 0) {return OnGetCurrentConnectionInfo(action);}  /* Is it a AVTransport Service Action ? */    if (name.Compare("Next", true) == 0) {        return OnNext(action);    }    if (name.Compare("Pause", true) == 0) {        return OnPause(action);    }    if (name.Compare("Play", true) == 0) {        return OnPlay(action);    }    if (name.Compare("Previous", true) == 0) {        return OnPrevious(action);    }    if (name.Compare("Seek", true) == 0) {        return OnSeek(action);    }    if (name.Compare("Stop", true) == 0) {        return OnStop(action);    }    if (name.Compare("SetAVTransportURI", true) == 0) {        return OnSetAVTransportURI(action);    }    if (name.Compare("SetPlayMode", true) == 0) {        return OnSetPlayMode(action);    }    /* Is it a RendererControl Service Action ? */    if (name.Compare("SetVolume", true) == 0) {          return OnSetVolume(action);    }if (name.Compare("SetVolumeDB", true) == 0) {return OnSetVolumeDB(action);    }if (name.Compare("GetVolumeDBRange", true) == 0) {return OnGetVolumeDBRange(action);}    if (name.Compare("SetMute", true) == 0) {          return OnSetMute(action);    }    // other actions rely on state variables    NPT_CHECK_LABEL_WARNING(action->SetArgumentsOutFromStateVariable(), failure);    return NPT_SUCCESS;failure:    action->SetError(401,"No Such Action.");    return NPT_FAILURE;}

可以看到OnAction方法,此方法会在mediaRender设备接收到事件时调用

/*----------------------------------------------------------------------|   PLT_MediaRenderer::OnNext+---------------------------------------------------------------------*/NPT_ResultPLT_MediaRenderer::OnNext(PLT_ActionReference& action){    if (m_Delegate) {        return m_Delegate->OnNext(action);    }    return NPT_ERROR_NOT_IMPLEMENTED;}

接着会调PltMediaRenderer的各种OnXXX(PLT_ActionReference& action)方法,在这里我们可以看到把action事件交给了m_Delegate来处理,所以我们要手动设置一个m_Delegate。在\Platinum\Source\Devices\MediaRenderer目录下有PltMediaRenderer.h头文件,定义了一个方法:
// methods    virtual void SetDelegate(PLT_MediaRendererDelegate* delegate) { m_Delegate = delegate; }
PLT_MediaRenderDelegate* delegate是从这个方法里边设置进来的,所以我们需要继承PLT_MediaRenderDelagate来定义一个自己的子类,代码如下:

class IMediaActionReceiver : public PLT_MediaRendererDelegate{ public:      static const int MEDIA_RENDER_CTL_MSG_BASE = 0x100;      static const int MEDIA_RENDER_CTL_MSG_SET_AV_URL = (MEDIA_RENDER_CTL_MSG_BASE+0);      static const int MEDIA_RENDER_CTL_MSG_STOP = (MEDIA_RENDER_CTL_MSG_BASE+1);      static const int MEDIA_RENDER_CTL_MSG_PLAY = (MEDIA_RENDER_CTL_MSG_BASE+2);      static const int MEDIA_RENDER_CTL_MSG_PAUSE = (MEDIA_RENDER_CTL_MSG_BASE+3);      static const int MEDIA_RENDER_CTL_MSG_SEEK = (MEDIA_RENDER_CTL_MSG_BASE+4);      static const int MEDIA_RENDER_CTL_MSG_SETVOLUME = (MEDIA_RENDER_CTL_MSG_BASE+5);      static const int MEDIA_RENDER_CTL_MSG_SETMUTE = (MEDIA_RENDER_CTL_MSG_BASE+6);      static const int MEDIA_RENDER_CTL_MSG_SETPLAYMODE = (MEDIA_RENDER_CTL_MSG_BASE+7);      static const int MEDIA_RENDER_CTL_MSG_PRE = (MEDIA_RENDER_CTL_MSG_BASE+8);      static const int MEDIA_RENDER_CTL_MSG_NEXT = (MEDIA_RENDER_CTL_MSG_BASE+9);      public:    virtual void ActionInflect(int cmd, const char* value, const char* data);    virtual NPT_Result OnGetCurrentConnectionInfo(PLT_ActionReference& action);    // AVTransport    virtual NPT_Result OnNext(PLT_ActionReference& action);    virtual NPT_Result OnPause(PLT_ActionReference& action);    virtual NPT_Result OnPlay(PLT_ActionReference& action);    virtual NPT_Result OnPrevious(PLT_ActionReference& action);    virtual NPT_Result OnSeek(PLT_ActionReference& action);    virtual NPT_Result OnStop(PLT_ActionReference& action);    virtual NPT_Result OnSetAVTransportURI(PLT_ActionReference& action);    virtual NPT_Result OnSetPlayMode(PLT_ActionReference& action);       // RenderingControl    virtual NPT_Result OnSetVolume(PLT_ActionReference& action);    virtual NPT_Result OnSetVolumeDB(PLT_ActionReference& action);    virtual NPT_Result OnGetVolumeDBRange(PLT_ActionReference& action);    virtual NPT_Result OnSetMute(PLT_ActionReference& action);};

在相关代码中实现此类并且调用自己的ActionInflect方法来进行处理:

NPT_Result IMediaActionReceiver::OnNext(PLT_ActionReference& action){    NPT_String curURI;    action->GetArgumentValue("CurrentURI", curURI);    NPT_String metaData ;    action->GetArgumentValue("CurrentURIMetaData", metaData);    ActionInflect(MEDIA_RENDER_CTL_MSG_NEXT, curURI.GetChars(),  metaData.GetChars());    return NPT_SUCCESS;}
ActionInflect方法代码如下:

voidIMediaActionReceiver::ActionInflect(int cmd, const char* value, const char* data){NPT_LOG_INFO("------------------->@@@@@@@############.\n");int status;JNIEnv *env = NULL;bool isAttach = false;status = jvm->GetEnv((void**) &env, JNI_VERSION_1_4);if(status != JNI_OK) {status = jvm->AttachCurrentThread(&env, NULL);if(status < 0) {return;}isAttach = true;}jstring valueString = NULL;jstring dataString = NULL;jclass inflectClass = g_inflectClass;jmethodID inflectMethod = g_methodID;if (inflectClass == NULL || inflectMethod == NULL){ goto end;}valueString = env->NewStringUTF(value);dataString = env->NewStringUTF(data);env->CallStaticVoidMethod(inflectClass, inflectMethod,valueString, dataString,cmd);env->DeleteLocalRef(valueString);env->DeleteLocalRef(dataString);end:if (env->ExceptionOccurred()){env->ExceptionDescribe();env->ExceptionClear();}if (isAttach){jvm->DetachCurrentThread();}}
其中jvm可以在JNI_OnLoad方法中得到,g_inflectClass跟g_methodID是你java层反射函数的Class跟方法ID(根据自身需求来定)。


下一步我们需要将事件状态值更新至所在服务列表

可以参考代码:

NPT_ResultPLT_MediaRenderer::SetupServices(){    PLT_Service* service;    {        /* AVTransport */        service = new PLT_Service(            this,            "urn:schemas-upnp-org:service:AVTransport:1",             "urn:upnp-org:serviceId:AVTransport",            "AVTransport",            "urn:schemas-upnp-org:metadata-1-0/AVT/");        NPT_CHECK_FATAL(service->SetSCPDXML((const char*) RDR_AVTransportSCPD));        NPT_CHECK_FATAL(AddService(service));        service->SetStateVariableRate("LastChange", NPT_TimeInterval(0.2f));        service->SetStateVariable("A_ARG_TYPE_InstanceID", "0");         // GetCurrentTransportActions        service->SetStateVariable("CurrentTransportActions", "Play,Pause,Stop,Seek,Next,Previous");        // GetDeviceCapabilities        service->SetStateVariable("PossiblePlaybackStorageMedia", "NONE,NETWORK,HDD,CD-DA,UNKNOWN");        service->SetStateVariable("PossibleRecordStorageMedia", "NOT_IMPLEMENTED");        service->SetStateVariable("PossibleRecordQualityModes", "NOT_IMPLEMENTED");        // GetMediaInfo        service->SetStateVariable("NumberOfTracks", "0");        service->SetStateVariable("CurrentMediaDuration", "00:00:00");        service->SetStateVariable("AVTransportURI", "");        service->SetStateVariable("AVTransportURIMetadata", "");;        service->SetStateVariable("NextAVTransportURI", "NOT_IMPLEMENTED");        service->SetStateVariable("NextAVTransportURIMetadata", "NOT_IMPLEMENTED");        service->SetStateVariable("PlaybackStorageMedium", "NONE");        service->SetStateVariable("RecordStorageMedium", "NOT_IMPLEMENTED");service->SetStateVariable("RecordMediumWriteStatus", "NOT_IMPLEMENTED");        // GetPositionInfo        service->SetStateVariable("CurrentTrack", "0");        NPT_Result durResult = service->SetStateVariable("CurrentTrackDuration", "00:00:00");        service->SetStateVariable("CurrentTrackMetadata", "");        service->SetStateVariable("CurrentTrackURI", "");        NPT_Result relTimeResult = service->SetStateVariable("RelativeTimePosition", "00:00:00");         service->SetStateVariable("AbsoluteTimePosition", "00:50:00");        service->SetStateVariable("RelativeCounterPosition", "2147483647"); // means NOT_IMPLEMENTED        service->SetStateVariable("AbsoluteCounterPosition", "2147483647"); // means NOT_IMPLEMENTED        // disable indirect eventing for certain state variables        PLT_StateVariable* var;        var = service->FindStateVariable("RelativeTimePosition");        if (var) var->DisableIndirectEventing();        var = service->FindStateVariable("AbsoluteTimePosition");        if (var) var->DisableIndirectEventing();        var = service->FindStateVariable("RelativeCounterPosition");        if (var) var->DisableIndirectEventing();        var = service->FindStateVariable("AbsoluteCounterPosition");        if (var) var->DisableIndirectEventing();        // GetTransportInfo        service->SetStateVariable("TransportState", "NO_MEDIA_PRESENT");        service->SetStateVariable("TransportStatus", "OK");        service->SetStateVariable("TransportPlaySpeed", "1");        // GetTransportSettings        service->SetStateVariable("CurrentPlayMode", "NORMAL");        service->SetStateVariable("CurrentRecordQualityMode", "NOT_IMPLEMENTED");    }    {        /* ConnectionManager */        service = new PLT_Service(            this,            "urn:schemas-upnp-org:service:ConnectionManager:1",             "urn:upnp-org:serviceId:ConnectionManager",            "ConnectionManager");        NPT_CHECK_FATAL(service->SetSCPDXML((const char*) RDR_ConnectionManagerSCPD));        NPT_CHECK_FATAL(AddService(service));        service->SetStateVariable("CurrentConnectionIDs", "0");        // put all supported mime types here instead        service->SetStateVariable("SinkProtocolInfo", "http-get:*:video/x-ms-wmv:DLNA.ORG_PN=WMVMED_PRO,http-get:*:video/x-ms-asf:DLNA.ORG_PN=MPEG4_P2_ASF_SP_G726,http-get:*:video/x-ms-wmv:DLNA.ORG_PN=WMVMED_FULL,http-get:*:image/jpeg:DLNA.ORG_PN=JPEG_MED,http-get:*:video/x-ms-wmv:DLNA.ORG_PN=WMVMED_BASE,http-get:*:audio/L16;rate=44100;channels=1:DLNA.ORG_PN=LPCM,http-get:*:video/mpeg:DLNA.ORG_PN=MPEG_PS_PAL,http-get:*:video/mpeg:DLNA.ORG_PN=MPEG_PS_NTSC,http-get:*:video/x-ms-wmv:DLNA.ORG_PN=WMVHIGH_PRO,http-get:*:audio/L16;rate=44100;channels=2:DLNA.ORG_PN=LPCM,http-get:*:image/jpeg:DLNA.ORG_PN=JPEG_SM,http-get:*:video/x-ms-asf:DLNA.ORG_PN=VC1_ASF_AP_L1_WMA,http-get:*:audio/x-ms-wma:DLNA.ORG_PN=WMDRM_WMABASE,http-get:*:video/x-ms-wmv:DLNA.ORG_PN=WMVHIGH_FULL,http-get:*:audio/x-ms-wma:DLNA.ORG_PN=WMAFULL,http-get:*:audio/x-ms-wma:DLNA.ORG_PN=WMABASE,http-get:*:video/x-ms-wmv:DLNA.ORG_PN=WMVSPLL_BASE,http-get:*:video/mpeg:DLNA.ORG_PN=MPEG_PS_NTSC_XAC3,http-get:*:video/x-ms-wmv:DLNA.ORG_PN=WMDRM_WMVSPLL_BASE,http-get:*:video/x-ms-wmv:DLNA.ORG_PN=WMVSPML_BASE,http-get:*:video/x-ms-asf:DLNA.ORG_PN=MPEG4_P2_ASF_ASP_L5_SO_G726,http-get:*:image/jpeg:DLNA.ORG_PN=JPEG_LRG,http-get:*:audio/mpeg:DLNA.ORG_PN=MP3,http-get:*:video/mpeg:DLNA.ORG_PN=MPEG_PS_PAL_XAC3,http-get:*:audio/x-ms-wma:DLNA.ORG_PN=WMAPRO,http-get:*:video/mpeg:DLNA.ORG_PN=MPEG1,http-get:*:image/jpeg:DLNA.ORG_PN=JPEG_TN,http-get:*:video/x-ms-asf:DLNA.ORG_PN=MPEG4_P2_ASF_ASP_L4_SO_G726,http-get:*:audio/L16;rate=48000;channels=2:DLNA.ORG_PN=LPCM,http-get:*:audio/mpeg:DLNA.ORG_PN=MP3X,http-get:*:video/x-ms-wmv:DLNA.ORG_PN=WMVSPML_MP3,http-get:*:video/x-ms-wmv:*,http-get:*:video/mp4:*");        service->SetStateVariable("SourceProtocolInfo", "");    }    {        /* RenderingControl */        service = new PLT_Service(            this,            "urn:schemas-upnp-org:service:RenderingControl:1",             "urn:upnp-org:serviceId:RenderingControl",            "RenderingControl",            "urn:schemas-upnp-org:metadata-1-0/RCS/");        NPT_CHECK_FATAL(service->SetSCPDXML((const char*) RDR_RenderingControlSCPD));        NPT_CHECK_FATAL(AddService(service));        service->SetStateVariableRate("LastChange", NPT_TimeInterval(0.2f));        service->SetStateVariable("Mute", "0");        service->SetStateVariableExtraAttribute("Mute", "Channel", "Master");        service->SetStateVariable("Volume", "100");        service->SetStateVariableExtraAttribute("Volume", "Channel", "Master");        service->SetStateVariable("VolumeDB", "0");        service->SetStateVariableExtraAttribute("VolumeDB", "Channel", "Master");        service->SetStateVariable("PresetNameList", "FactoryDefaults");    }    return NPT_SUCCESS;}

按照这些步骤,就能构建自己的dmr。说明一下自己在开发中遇到的一个问题,在构建自己的PLT_MediaRender时:

JNIEXPORT jint JNICALL Java_com_geniusgithub_mediarender_jni_PlatinumJniProxy__1startMediaRender(JNIEnv *env, jclass, jlong _self, jbyteArray _jbyteArrName,jbyteArray _jbyteArrayUUID){     PLT_UPnP* upnp = (PLT_UPnP*)_self;
    char* dmr_name = (char*)env->GetByteArrayElements(_jbyteArrName, 0);    char* dmr_uuid = (char*)env->GetByteArrayElements(_jbyteArrayUUID, 0);    PLT_MediaRenderer* mediaRender = new PLT_MediaRenderer(dmr_name,false,dmr_uuid,36439,false);    PLT_DeviceHostReference device(mediaRender);    myDevice = device;    upnp->AddDevice(myDevice);     upnp->Start();     return 0; }
<span style="font-size:18px;">GetByteArrayElements有可能发生字符乱码情况,把方法中传入的jbyteArray改为jstring(相应的java层也应改变),代码改为:</span>
<pre name="code" class="cpp">char* dmr_name = (char *) env->GetStringUTFChars(name, 0);char* dmr_uuid = (char *) env->GetStringUTFChars(uuid, 0);
<span style="font-size:18px;">乱码问题得到解决。所有要讲解的就到这了,过程中遇到了很多问题,所以写了这篇博文供大家参考,再次谢谢蓝斯博主!</span>




0 0
原创粉丝点击