【VS开发】这就是COM组件

[实例]这就是COM组件

[实例]这就是COM组件

Author: ume Date:2012-02-21

    自从微软推出.NET以来,COM技术就渐渐淡出人们的视野,然而这并不意味COM不再发挥作用,相反,COM非常重要。可以说.NET的实现离不开COM的支撑。COM是好东西,但是它太难了,不利于产品推广,于是微软只能在之上增加一层封装从而提高产品的易用性。对COM有所了解是很有必要的,希望这篇文章给你带来一点启发。

1. COM的思想

    开篇就讲COM的思想肯定让人泄气,因为它极有可能抽象空洞、晦涩难懂。换个角度来说,我觉得COM的思想仍然是需要自己去体会的,这里给出的不过是一个思考的线索而已,你大可不求甚解。

    软件的开发是永无止境的,只要软件还在存活期,它就应当不断被完善和更新。一款软件可能十分庞大,但真正可变的部分却是很有限的。我们当然希望在更新软件的时候,只更新变化的部分,而不要笨手笨脚把整个软件都换掉。只更新变化的部分?这就要求模块支持动态链接。所谓动态链接就是模块只在被使用的时候才被加载,程序调用模块中的某个函数是通过指针来完成的。动态链接允许模块分离出去,而不像静态链接那样须经过编译才能整合到程序中来。dll是实现动态链接的一种方法,它使更新软件的工作浓缩成了更新dll,用户无需重新安装软件,只需替换相应的dll文件就能实现软件的升级。

    动态链接是针对普通用户而言的,现在换一个对象:模块的用户。模块的用户是应用程序开发人员,对于模块的提供商来说也算得上同行了,只不过术业有专攻,各自工作的重点不同而已。显然采用dll的形式,模块的提供商可以很方便的发布自己的产品。其中不可忽视的另一点即信息的封装(或称隐藏),即将模块的实现细节隐蔽起来,用户无法知道模块的提供商采用何种语言、何种算法,简而言之就是用户看不到模块的源代码。dll是二进制级别上的代码复用,它实现了信息的封装。

    综上所述,软件开发要求模块支持“动态链接”和“信息封装”,更直白地说就是要求模块和客户代码之间更低的耦合度。把模块制作成组件是必然的选择,而COM本质上是一种创建组件的方法和规范。

    注:dll并不等同于组件,它只是组件的一种形式。由于dll的易用性,它的应用很广泛。

2. 实例说明

    我们创建一个COM组件,它将实现接口ICouplet,用户可通过该接口调用what()方法输出一副对联。what()方法不值一提,不过你可以将它当作程序可变的部分。我们创建的COM组件也要实现接口IClassFactory,它是创建组件的简单组件。之所以这么设计是为了让组件与客户代码彻底脱耦,尽可能少的联系。

    除了实现接口ICouplet和IClassFactory外, COM组件还要能实现自注册,因此它必须导出函数DllRegister/DllUnregister。另外两个导出函数DllCanUnloadNow和DllGetClassObject也非常重要,前者用来询问当前dll能否被卸载,它被CoFreeUnusedLibraries调用;后者用来创建类厂组件,它被CoCreateInstance调用。名称形如Coxxx的函数是COM库函数,它是实现COM组件的公共操作,由微软提供,类似于Win32 API。我们常见的客户代码中CoInitialize/CoUninitialize函数就起到初始化和卸载COM库的作用。要导出上述4个函数就必须编写一个.def文件,具体写法见代码清单。

    最后要说明的是COM组件的自注册。我们知道注册表是Windows的公共系统数据库,其中记录了软件、硬件、用户配置等信息。而COM组件是用一个128比特的GUID标识的,为了使得COM组件的安装目录更灵活,我们可以在注册表中对它进行注册,注册的主要信息即COM组件的GUID标识与其存储路径的对应关系,在使用该组件时就到注册表中去查找。注册一个COM组件一般使用regsvr32.exe程序来完成,当然你也可以自己写一个类似于regsvr32.exe的小程序来完成COM组件的注册,regsvr32.exe本质上调用了组件的导出函数DllRegister/DllUnregister。

    生成Couplet.dll文件后,首先在控制台注册它。具体方法:切换到Couplet.dll所在目录,输入指令regsvr32 Couplet.dll。然后运行客户程序Reader.exe,其结果如下所示:

Create Couplet object
Succeeded in getting the pointer to ICouplet1st Scroll: Study Hard, Work Hard, Make Money More and More
2nd Scroll: Eat Well, Sleep Well, Have Fun Day by Day
Top Scroll: GelievableCouplet object deleted
请按任意键继续. . .

    然后修改Couplet::what()方法,让它输出中文,重新生成Couplet.dll。这一步不用重新注册Couplet.dll,因为Couplet.dll的路径没变,CLSID_Couplet也没变。运行客户程序Reader.exe,其结果如下所示:

CreateCouplet objectSucceededin getting the pointer to ICouplet上联:我爱的人名花有主下联:爱我的人惨不忍睹横批:命苦Coupletobject deleted请按任意键继续. . .

    这个例子证明了COM组件的更新不会对客户端造成影响,使用COM组件可以实现模块与客户代码彻底脱耦。实验结束后,在控制台输入指令regsvr32 /u Couplet.dll,从注册表中将dll模块信息清除。

3. 代码清单

/* File List: (COM) IFace.h Register.h Register.cpp Couplet.cpp Couple.def*           (Client) IFace.h Reader.cpp* date: 2012-02-21* author: ume*/
/////////////////////////////////////////////////////////////////
// IFace.h 接口的声明,组件ID、接口ID的定义
//
#include <ObjBase.h>
// interface
interface ICouplet : IUnknown
{virtual void what() = 0;
};
// GUIDs
// {03844548-B0B9-4B12-869D-061AAE2E4B7F}
static const GUID IID_ICouplet = 
{ 0x3844548, 0xb0b9, 0x4b12, { 0x86, 0x9d, 0x6, 0x1a, 0xae, 0x2e, 0x4b, 0x7f } };
// {26615B48-1D2E-4A40-9C07-AD5B1B48368C}
static const GUID CLSID_Couplet = 
{ 0x26615b48, 0x1d2e, 0x4a40, { 0x9c, 0x7, 0xad, 0x5b, 0x1b, 0x48, 0x36, 0x8c } };
/////////////////////////////////////////////////////////////////
// Register.h 注册函数的声明
//
HRESULT RegisterServer(HMODULE hModule,               const CLSID& clsid,               const char* szFriendlyName,           const char* szVerIndProgID,           const char* szProgID);
HRESULT UnRegisterServer(const CLSID& clsid,        const char* szVerIndProgID,         const char* szProgID);
/////////////////////////////////////////////////////////////////
// Register.cpp 注册函数的定义
// 这些函数可重复使用,非本文重点
//
#include <objbase.h>
#include "Register.h"
//set the given key and its value;
BOOL setKeyAndValue(const char* pszPath,const char* szSubkey,const char* szValue);
//Convert a CLSID into a char string
void CLSIDtochar(const CLSID& clsid,char* szCLSID,int length);
//Delete szKeyChild and all of its descendents
LONG recursiveDeleteKey(HKEY hKeyParent,const char* szKeyChild);
//size of a CLSID as a string
const int CLSID_STRING_SIZE = 39;
//Register the component in the registry
HRESULT RegisterServer(HMODULE hModule,const CLSID& clsid,const char* szFriendlyName,const char* szVerIndProgID,const char* szProgID)
{//Get the Server locationchar szModule[512];DWORD dwResult = ::GetModuleFileName(hModule,szModule,sizeof(szModule)/sizeof(char));assert(dwResult!=0);//Convert the CLSID into a charchar szCLSID[CLSID_STRING_SIZE];CLSIDtochar(clsid,szCLSID,sizeof(szCLSID));//Build the key CLSID\\{}char szKey[64];strcpy(szKey,"CLSID\\");strcat(szKey,szCLSID);//Add the CLSID to the registrysetKeyAndValue(szKey,NULL,szFriendlyName);//Add the Server filename subkey under the CLSID keysetKeyAndValue(szKey,"InprocServer32",szModule);setKeyAndValue(szKey,"ProgID",szProgID);setKeyAndValue(szKey,"VersionIndependentProgID",szVerIndProgID);//Add the version-independent ProgID subkey under HKEY_CLASSES_ROOTsetKeyAndValue(szVerIndProgID,NULL,szFriendlyName);setKeyAndValue(szVerIndProgID,"CLSID",szCLSID);setKeyAndValue(szVerIndProgID,"CurVer",szProgID);//Add the versioned ProgID subkey under HKEY_CLASSES_ROOTsetKeyAndValue(szProgID,NULL,szFriendlyName);setKeyAndValue(szProgID,"CLSID",szCLSID);return S_OK;
}//
//Remove the component from the register
//
HRESULT UnRegisterServer(const CLSID& clsid,           // Class IDconst char* szVerIndProgID,   // Programmaticconst char* szProgID)           // IDs
{//Convert the CLSID into a char.char szCLSID[CLSID_STRING_SIZE];CLSIDtochar(clsid,szCLSID,sizeof(szCLSID));//Build the key CLSID\\{}char szKey[64];strcpy(szKey,"CLSID\\");strcat(szKey,szCLSID);//Delete the CLSID key - CLSID\{}LONG lResult = recursiveDeleteKey(HKEY_CLASSES_ROOT,szKey);assert((lResult == ERROR_SUCCESS) || (lResult == ERROR_FILE_NOT_FOUND));//Delete the version-independent ProgID KeylResult = recursiveDeleteKey(HKEY_CLASSES_ROOT,szVerIndProgID);assert((lResult == ERROR_SUCCESS) || (lResult == ERROR_FILE_NOT_FOUND));//Delete the ProgID key.lResult = recursiveDeleteKey(HKEY_CLASSES_ROOT,szProgID);assert((lResult == ERROR_SUCCESS) || (lResult == ERROR_FILE_NOT_FOUND));return S_OK;
}
//Convert a CLSID to a char string
void CLSIDtochar(const CLSID& clsid,char* szCLSID,int length)
{assert(length>=CLSID_STRING_SIZE);//Get CLSIDLPOLESTR wszCLSID = NULL;HRESULT hr = StringFromCLSID(clsid,&wszCLSID);assert(SUCCEEDED(hr));//Convert from wide characters to non_widewcstombs(szCLSID,wszCLSID,length);//Free memoryCoTaskMemFree(wszCLSID);
}
// Delete a Key and all of its descendents
LONG recursiveDeleteKey(HKEY hKeyParent,const char* lpszKeyChild)
{//Open the child.HKEY hKeyChild;LONG lRes = RegOpenKeyEx(hKeyParent,lpszKeyChild,0,KEY_ALL_ACCESS,&hKeyChild);if(lRes != ERROR_SUCCESS)return lRes;//Enumerate all of the decendents of this childFILETIME time;char szBuffer[256];DWORD dwSize = 256 ;while(RegEnumKeyEx(hKeyChild,0,szBuffer,&dwSize,NULL,NULL,NULL,&time) == S_OK){//Delete the decendents of this child.lRes = recursiveDeleteKey(hKeyChild,szBuffer);if(lRes != ERROR_SUCCESS){RegCloseKey(hKeyChild);return lRes;}dwSize = 256;}RegCloseKey(hKeyChild);return RegDeleteKey(hKeyParent,lpszKeyChild);
}BOOL setKeyAndValue(const char* szKey,const char* szSubkey,const char* szValue)
{HKEY hKey;char szKeyBuf[1024];//Copy keyname into buffer.strcpy(szKeyBuf,szKey);//Add subkey name to buffer.if(szSubkey!=NULL){strcat(szKeyBuf,"\\");strcat(szKeyBuf,szSubkey);}// Create and open key and subkey.long lResult = RegCreateKeyEx(HKEY_CLASSES_ROOT ,szKeyBuf, 0, NULL, REG_OPTION_NON_VOLATILE,KEY_ALL_ACCESS, NULL, &hKey, NULL) ;if (lResult != ERROR_SUCCESS){return FALSE ;}// Set the Value.if (szValue != NULL){RegSetValueEx(hKey, NULL, 0, REG_SZ, (BYTE *)szValue, strlen(szValue)+1) ;}RegCloseKey(hKey) ;return TRUE ;
}
/////////////////////////////////////////////////////////////////
// Couplet.cpp 接口的实现
// 本文的重点,尤其是Couplet和CFactory的实现
//
#include "IFace.h"
#include "Register.h"
#include <iostream>
using namespace std;
// trace
void trace(const char* msg) { cout<<msg<<endl; }
// global variables
HMODULE g_hModule;
static long g_cComponents = 0;
static long g_cLocks = 0;
// Friendly name of component
const char g_szFriendlyName[] = "A Couplet";
// Version independent ProgID
const char g_szVerIndProgID[] = "Couplet.Test";
// ProgID
const char g_szProgID[] = "Couplet.Test.1";
// implementation
class Couplet : public ICouplet
{
public:virtual LRESULT __stdcall QueryInterface(const IID& iid, void** ppv);virtual ULONG __stdcall AddRef(){return ::InterlockedIncrement(&m_cRef);}virtual ULONG __stdcall Release(){if(::InterlockedDecrement(&m_cRef) == 0){delete this;return 0;}return m_cRef;}virtual void what() { //cout<<"\n上联:我爱的人名花有主\n下联:爱我的人惨不忍睹\n横批:命苦\n\n"; cout<<"\n1st Scroll: Study Hard, Work Hard, Make Money More and More\n\
2nd Scroll: Eat Well, Sleep Well, Have Fun Day by Day\nTop Scroll: Gelievable\n\n"; }// constructorCouplet() : m_cRef(1) {::InterlockedIncrement(&g_cComponents);trace("Create Couplet object"); }// destructor~Couplet(){::InterlockedDecrement(&g_cComponents);trace("Couplet object deleted");}
private:long m_cRef;
};
// definition of QueryInterface
LRESULT __stdcall Couplet::QueryInterface(const IID& iid, void** ppv)
{if((iid == IID_IUnknown) || (iid == IID_ICouplet)){*ppv = static_cast<ICouplet*>(this);}else{*ppv = NULL;return E_NOINTERFACE;}static_cast<IUnknown*>(*ppv)->AddRef();return S_OK;
}
// class CFactory
class CFactory : public IClassFactory
{
public:virtual LRESULT __stdcall QueryInterface(const IID& iid, void** ppv);virtual ULONG __stdcall AddRef(){return ::InterlockedIncrement(&m_cRef);}virtual ULONG __stdcall Release(){if(::InterlockedDecrement(&m_cRef) == 0){delete this;return 0;}return m_cRef;}virtual LRESULT __stdcall CreateInstance(IUnknown* pCmpntOuter,const IID& iid,void** ppv);virtual LRESULT __stdcall LockServer(BOOL bLock);
private:long m_cRef;
};
// definition of QueryInterface
LRESULT __stdcall CFactory::QueryInterface(const IID& iid, void** ppv)
{if((iid == IID_IUnknown) || (iid == IID_IClassFactory)){*ppv = static_cast<IClassFactory*>(this);}else{*ppv = NULL;return E_NOINTERFACE;}static_cast<IUnknown*>(*ppv)->AddRef();return S_OK;
}
// definition of CreateInstance
LRESULT __stdcall CFactory::CreateInstance(IUnknown* pCmpntOuter,const IID& iid,void** ppv)
{if(pCmpntOuter != NULL){cout<<"No Aggregate in this Class Factory"<<endl;return CLASS_E_NOAGGREGATION;}Couplet* pCouplet = new Couplet;if(pCouplet == NULL)return E_OUTOFMEMORY;HRESULT hr = pCouplet->QueryInterface(iid, ppv);pCouplet->Release();return hr;
}
// definition of LockServer
LRESULT __stdcall CFactory::LockServer(BOOL bLock)
{if(bLock){::InterlockedIncrement(&g_cLocks);}else{::InterlockedDecrement(&g_cLocks);}return S_OK;
}
STDAPI DllCanUnloadNow()
{if((g_cComponents == 0) && (g_cLocks == 0)){return S_OK;}else{return S_FALSE;}
}
// Get class factory
STDAPI DllGetClassObject(const CLSID& clsid,const IID& iid,void** ppv)
{// Can we create this component?if(clsid != CLSID_Couplet){return CLASS_E_CLASSNOTAVAILABLE;}// Create class factoryCFactory* pFactory = new CFactory;if(pFactory == NULL){return E_OUTOFMEMORY;}// Get requested interfaceHRESULT hr = pFactory->QueryInterface(iid, ppv);pFactory->Release();return hr;
}
// register and unregister component
STDAPI DllRegisterServer()
{return RegisterServer(g_hModule,CLSID_Couplet,g_szFriendlyName,g_szVerIndProgID,g_szProgID);
}
STDAPI DllUnregisterServer()
{return UnRegisterServer(CLSID_Couplet,g_szVerIndProgID,g_szProgID);
}
// dll main
BOOL APIENTRY DllMain(HANDLE hModule,DWORD dwReason,void* lpReserved)
{if(dwReason == DLL_PROCESS_ATTACH){g_hModule = (HMODULE)hModule;}return TRUE;
}
/////////////////////////////////////////////////////////////////
// Couplet.def 模块定义文件
//
LIBRARY Couplet.dll
EXPORTSDllCanUnloadNow @1 PRIVATEDllGetClassObject @2 PRIVATEDllRegisterServer @3 PRIVATEDllUnregisterServer @4 PRIVATE
/////////////////////////////////////////////////////////////////
// Reader.cpp 通过ICouplet接口调用what()方法读取对联内容
// 注意: 客户端的IFace.h与COM组件中的IFace.h完全一样
//
#include <iostream>
#include <ObjBase.h>
#include "IFace.h"
using namespace std;
// global function
void trace(const char* pMsg){ cout<<pMsg<<endl; }
// main function
int main()
{::CoInitialize(NULL);ICouplet* pICouplet = NULL;HRESULT hr = ::CoCreateInstance(CLSID_Couplet, NULL, CLSCTX_INPROC_SERVER, IID_ICouplet, (void**)&pICouplet);if(SUCCEEDED(hr)){trace("Succeeded in getting the pointer to ICouplet");pICouplet->what();pICouplet->Release();}else {trace("Failed to get the pointer to ICouplet");}::CoUninitialize();system("pause");return 0;
}

转载于:https://www.cnblogs.com/huty/p/8518744.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/271960.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

前端:QuickJS到底能干什么

QuickJS 是一个轻量且可嵌入的 JavaScript 引擎&#xff0c;它支持 ES2019 规范&#xff0c;包括 ES module、异步生成器以及 proxies。除此之外&#xff0c;还支持可选的数学扩展&#xff0c;例如大整数(BigInt)、大浮点数(BigFloat)和运算符重载。主要特点&#xff1a;轻量而…

随机存取是什么意思_手机小白必看!12GB+256GB,同样是GB,它们到底有什么不同?...

导语本文适合对电子产品有深度兴趣的小白&#xff0c;详细介绍了信息世界的数据计量单位&#xff0c;以及RAM与ROM的&#xff0c;文末给出了购机建议&#xff0c;建议不了解手机或者说半懂的同学观看。看完如果您觉得还可以的话&#xff0c;点赞关注&#xff0c;给小编一个鼓励…

ES6 解构赋值的用法笔记

1、概念&#xff1a;解构赋值可以理解为对赋值运算符的一种扩展。它主要针对数组或者对象进行模式匹配&#xff0c;然后对模式中的变量进行赋值。2、特性&#xff1a;采用ES6解构赋值的方式可以代码的可读性更高、代码书写更加简洁、清晰。3、解构模型&#xff1a;分为解构源、…

一.高阶函数

一.函数式编程中函数的特点 可以创建匿名函数 def声明带名函数&#xff0c;val声明匿名函数scala scala> def triple(x:Int):Int {3*x} scala> triple(2) res1: Int 6scala scala> val triple (x:Int) > 3*x scala> triple(2) res0: Int 6函数和数字一样&am…

线程和进程的区别?

进程是操作系统分配资源的最小单元&#xff0c;线程是操作系统调度的最小单元。 一个程序至少有一个进程,一个进程至少有一个线程。

收集12个经典的程序员段子

1bug 跟蚊子的相似之处&#xff1a;1、不知道藏在哪里。2、不知道有多少。3、总是在你即将睡觉休息的时候出现。2A&#xff1a;最近在看《一拳超人》&#xff0c;觉得咱们程序猿跟埼玉老师有点像啊&#xff01;B&#xff1a;哪里像了&#xff1f;A&#xff1a;越秃越强&#xf…

2020mysql安装教程_2020MySQL安装图文教程

MySQL安装图文教程(Windows10)1、MySQL下载可以去MySQL官网下载&#xff0c;或者在我提供的百度云链接下载。官网下载网速较慢&#xff0c;我从官网下载了将近四个小时&#xff0c;然后把下载好的放在了百度网盘&#xff0c;需要的而已自取。MySQL官网地址&#xff1a;MySQL官网…

说一下 runnable 和 callable 有什么区别?

主要区别 Runnable 接口 run 方法无返回值&#xff1b;Callable 接口 call 方法有返回值&#xff0c;支持泛型Runnable 接口 run 方法只能抛出运行时异常&#xff0c;且无法捕获处理&#xff1b;Callable 接口 call 方法允许抛出异常&#xff0c;可以获取异常信息 Runnable Ca…

几种常见的光纤接头(ST,SC,LC,FC)以及PC、APC和UPC的区别

一、几种常见的光纤接头(ST,SC,LC,FC)FC型光纤连接器&#xff1a;外部加强方式是采用金属套&#xff0c;紧固方式为螺丝扣。 一般在ODF侧采用(配线架上用的最多)SC型光纤连接器&#xff1a;连接GBIC光模块或普通光纤收发器的连接器&#xff0c;它的外壳呈矩形&#xff0c;紧固方…

nginx配置ssl

1.使用pfx证书配置ssl &#xff08;http://www.heartlifes.com/archives/12/&#xff09; 。上传证书 。生成证书crt及key文件 openssl pkcs12 -in /usr/local/nginx/ssl/xxx.pfx -clcerts -nokeys -out /usr/local/nginx/ssl/xxx.crt openssl pkcs12 -in /usr/local/nginx/ssl…

python开发客户端_python用700行代码实现http客户端

本文用python在TCP的基础上实现一个HTTP客户端, 该客户端能够复用TCP连接, 使用HTTP1.1协议.一. 创建HTTP请求HTTP是基于TCP连接的, 它的请求报文格式如下:因此, 我们只需要创建一个到服务器的TCP连接, 然后按照上面的格式写好报文并发给服务器, 就实现了一个HTTP请求.1. HTTPC…

家里网线的接法和顺序

对于网线&#xff0c;大伙都熟悉吧&#xff0c;它是电脑连接时必不可少的一种设备。但是许多网友却和小编一样&#xff0c;不知道如何连接网线&#xff0c;导致电脑无法上网&#xff0c;下面我们就来详细介绍一下&#xff1a;如何接网线以及家里网线的接法和顺序&#xff1f;希…

String str=Hello 与 String str=new String(“Hello”)一样吗?

为什么会输出上边的结果呢&#xff0c;String x "Hello" 的方式&#xff0c;Java 虚拟机会将其分配到常量池中&#xff0c;而常量池中没有重复的元素&#xff0c;比如当执行“Hello”时&#xff0c;java虚拟机会先在常量池中检索是否已经有“Hello”,如果有那么就将…

盘点程序员最喜欢的15个网站

程序员作为一个经常和互联网打交道的人群&#xff0c;他们喜欢浏览哪些网站呢&#xff1f;不爱敲代码的程序猿整理了以下网站供大家参考&#xff0c;排名不分先后&#xff1a; 0. Google https://google.com 这个不用多说了吧。 1.GitHub 开发者最最最重要的网站&#xff1a;h…

简单的反射 把datatable 转换成list对象

/// <summary>/// 把datatable 转换成list对象/// </summary>/// <typeparam name"T"></typeparam>/// <param name"dt"></param>/// <returns></returns>public List<T> SelectsAll<T>(Data…

mysql 取 映射数据库中_JAVA与数据库MySQL相连接

JDBC(Java数据库连接体系结构)&#xff1a;是Java实现数据库访问的应用程序编程接口&#xff0c;主要功能是管理存放在数据库中的数据。通过接口对象&#xff0c;应用程序可以完成与数据库的连接&#xff0c;执行SQL语句&#xff0c;从数据库中获取结果&#xff0c;获取状态以及…

抽象类必须要有抽象方法吗?

答案是&#xff1a;不必须。 这个题目主要是考察对抽象类的理解。 说一下我个人的理解吧。 1.如果一个类使用了abstract关键字修饰&#xff0c;那么这个类就是一个抽象类。 2.抽象类可以没有抽象方法 3.一个类如果包含抽象方法&#xff0c;那么这个类必须是抽象类&#xf…

python序列化和反序列化_Python 中 json 数据序列化和反序列化

1.Json 定义定义&#xff1a;JSON(JavaScript Object Notation, JS 对象简谱) 是一种轻量级的数据交换格式。JSON 的数据格式其实就是 python 里面的字典格式&#xff0c;里面可以包含方括号括起来的数组&#xff0c;也就是python里面的列表。特点&#xff1a;简洁和清晰的层次…

重写navigationController的push方法后,出现卡顿现象

在使用navigation的pushViewController进行push的时候&#xff0c;两个页面间的动画会出现卡顿一下再推出的效果&#xff0c;是因为iOS7 viewController背景颜色的问题&#xff0c;看到大神的博客上说&#xff1a;其实不是卡顿&#xff0c;是由于透明色颜色重叠后视觉上的问题&…

硬件:RS232基础知识笔记

个人计算机上的通讯接口之一&#xff0c;由电子工业协会&#xff08;ElectronicIndustriesAssociation&#xff0c;EIA&#xff09;所制定的异步传输标准接口。通常RS-232接口以9个引脚&#xff08;DB-9&#xff09;或是25个引脚&#xff08;DB-25&#xff09;的型态出现&#…