说到 C 语言版本的程序,首先要解决的问题就是兼容性. 作为 20 年开发有 10 多年是在服务端的程序员,我深刻地感受到服务端平台的两极分化之严重,linux 派对 windows 那是超级的不屑一顾:那都是没技术的人才用的,没能力维护 linux 的人才用 windows. 与此同时 windows 派对 linux 也是嗤之以鼻,我曾经的一位经理就时常不屑对我说,我就不信那几个人写的东西能比公司写的好. 奇怪的这两派其实都很能挣钱,BAT 什么的都用 linux 我们就不说了,但股票期货交易这样重要的而且性能要求一样很高的行业内几乎一水的 windows + sql server 恐怕大家就不知道了吧. 所以我真不太同意 linux 性能就比 windows 高的说法.
我觉得形成这种说法的很重要的一个因素是很多高性能的软件没有 windows 版本,比如 nginx 长期不推荐在 windows 下使用, redis 下的 windows 版本居然是微软自己拿过来修改过才能用的.到底真相如何我们就不讨论了,单就为什么这些软件没有 windows 版本,我觉得一个很重要的原因是 C/C++ 语言在两种平台下的兼容性问题.开源界现在大量的用 gcc,而 gcc 的语法现在和 vc 的语法差别是越来越大,我过去经常在 pc 中引用开源代码,有些代码花上一整天的没法在 vc 中编译通过(印象中最好编译的是 apache 的代码).我个人觉得既然开源了,还是应该考虑一下 vc 的兼容性(当然了个人时间是有限的,我写的很多东西也都没有考虑,基本上手上的平台下能编译过去也就算了...).
这种兼容性体现在很多方面,第一步选择 ide (或者称不上 ide 的开发工具) 时基本上都会要求引入库文件. linux 下是 so 或者 a 文件,这里就不说了,单只讨论 windows 下的就有很多区别. 传统 vc 下是要引入 lib 文件,而现在有大量基于 gcc 的多种开发工具,它们要引入的是 a 文件,它们是不通用的(小提示:有些版本的 gcc 能使用 lib 文件).所以如果是拿一个 vc 的示例,那么在 gcc 系的开发工具中是用不了的. 我的解决办法是不用 socket 的库文件! 初学者还没什么,有经验的同学们又要炸锅了:可能吗! 没什么的可能的,前面已经说了这些 socket 函数是操作系统提供的,与开发语言无关,我们其实可以直接使用操作系统的功能,这种"直接使用"也没什么稀奇的就是直接调用 dll 文件罢了,delphi 的所有 socket 都是这样使用的.具体的调用方法就是直接调用 dll 中的函数指针,这在所有的 windows api 开发书籍中都会讲到,一点也不稀奇.本质上各个编译器最后也是要这样调用的,只不过它们按照传统把这种操作弄到了库文件中了而已.
我先上代码,大家先别急着看,我后面会讲解,其实也都挺简单的.
(文件名 socketplus.c)
//一个方便测试 socket 程序的小文件,省得老是找 lib a 文件 //目前是 gcc 专用,如果 vc 要用另外弄一个好了,不要在这上面弄条件编译 #ifndef _SOCKET_PLUS_C_ #define _SOCKET_PLUS_C_ #include <stdio.h> #include <windows.h> #include <time.h> #include <winsock.h> //#include <> #include "lstring.c" //#pragma comment (lib,"*.lib") //#pragma comment (lib,"libwsock32.a") //#pragma comment (lib,"libwsock32.a") // 系统错误信息提示 void PrintError(DWORD last_err); //-------------------------------------------------- //直接引入的 dll 函数 //SOCKET PASCAL socket(int,int,int); //char * (*fun1)(char * p1,char * p2); #ifndef _MSC_VER //很多同学不会写函数指针声明//函数指针的写法是,先写正常的函数声明,然后将函数名加上括号,然后在函数名前再加上*号即可!!! SOCKET PASCAL (*_socket)(int,int,int); //SOCKET (PASCAL *_socket)(int,int,int); //vc 要这样写,vc6,vc2010 都是如此 //就是将 PASCAL 或者 stdcall 放到函数名的括号中 int PASCAL (*_WSAStartup)(WORD,LPWSADATA); unsigned long PASCAL (*_inet_addr)(const char*); u_short PASCAL (*_htons)(u_short); int PASCAL (*_connect)(SOCKET,const struct sockaddr*,int); int PASCAL (*_WSAGetLastError)(void); int PASCAL (*_send)(SOCKET,const char*,int,int); int PASCAL (*_recv)(SOCKET,char*,int,int); int PASCAL (*_select)(int nfds,fd_set*,fd_set*,fd_set*,const struct timeval*); struct hostent * PASCAL (*_gethostbyname)(const char*); #endif #ifdef _MSC_VER SOCKET (PASCAL *_socket)(int,int,int); //vc 要这样写,vc6,vc2010 都是如此//就是将 PASCAL 或者 stdcall 放到函数名的括号中 int (PASCAL *_WSAStartup)(WORD,LPWSADATA); unsigned long (PASCAL*_inet_addr)(const char*); u_short (PASCAL*_htons)(u_short); int (PASCAL *_connect)(SOCKET,const struct sockaddr*,int); int (PASCAL *_WSAGetLastError)(void); int (PASCAL *_send)(SOCKET,const char*,int,int); int (PASCAL *_recv)(SOCKET,char*,int,int); int (PASCAL *_select)(int nfds,fd_set*,fd_set*,fd_set*,const struct timeval*); struct hostent * (PASCAL *_gethostbyname)(const char*); #endif //-------------------------------------------------- int CreateTcpClient() { //LoadLibrary("wsock32.dll"); return _socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); } int InitWinSocket() { WSADATA wData; //Result := WSAStartup(MakeWord(2, 2), wData) = 0; return _WSAStartup(MAKEWORD(2, 2), &wData); } int ConnectIP(SOCKET so, char * ip, int port) { //sock: TSocket; //SockAddr: TSockAddr; //struct sockaddr SockAddr; struct sockaddr_in SockAddr; int err; int Result = 1; memset(&SockAddr, 0, sizeof(SockAddr)); SockAddr.sin_family = AF_INET; SockAddr.sin_port = _htons(port); SockAddr.sin_addr.s_addr = _inet_addr(ip); //if (_connect(so, (struct sockaddr *)(&SockAddr), sizeof(SOCKADDR_IN)) == SOCKET_ERROR) if (_connect(so, &SockAddr, sizeof(SOCKADDR_IN)) == SOCKET_ERROR) //其实是不用转换的 { PrintError(0); err = _WSAGetLastError(); //其实这个和 GetLastError 是一样的 //ShowMessageFmt('connect socket error,[%d]', [WSAGetLastError]); MessageBox(0, "connect socket error", "", 0); Result = 0; //int err = _WSAGetLastError();//如果前面调用了别的 api 这里是取到 0 的,而不是错误信息码 PrintError(err); if (INVALID_SOCKET == so) printf("connect error:INVALID_SOCKET\r\n"); return 0; } return 1; }// //clq 这是我新加的函数,目的是可以根据域名来访问,并且原来的代码只能访问局域网 int ConnectHost(SOCKET so, char * host, int port) { const char * address = host; int is_connect = 0; int err = 0; // Create an address structure and clear it struct sockaddr_in addr; memset(&addr, 0, sizeof(addr)); // Fill in the address if possible//先尝试当做IP来解析 addr.sin_family = AF_INET; addr.sin_addr.s_addr = _inet_addr(address); // Was the string a valid IP address?//如果不是IP就当做域名来解析 if (addr.sin_addr.s_addr == -1) { // No, so get the actual IP address of the host name specified struct hostent *pHost; pHost = _gethostbyname(address); if (pHost != NULL) { if (pHost->h_addr == NULL) return 0;//false; addr.sin_addr.s_addr = ((struct in_addr *)pHost->h_addr)->s_addr; } else return 0;//false; } addr.sin_port = _htons(port); //返回:-1 连接失败;0 连接成功 if (_connect(so, (struct sockaddr *)&addr, sizeof(addr)) == 0) { is_connect = 1;//true; } else { is_connect = 0;//false; //连接失败 //printfd3("WSAGetLastError:%d, WSAEWOULDBLOCK:%d\r\n", WSAGetLastError()-WSABASEERR, WSAEWOULDBLOCK-WSABASEERR); PrintError(0); err = _WSAGetLastError(); //其实这个和 GetLastError 是一样的 //ShowMessageFmt('connect socket error,[%d]', [WSAGetLastError]); MessageBox(0, "connect socket error", "", 0); is_connect = 0; //int err = _WSAGetLastError();//如果前面调用了别的 api 这里是取到 0 的,而不是错误信息码 PrintError(err); if (INVALID_SOCKET == so) printf("connect error:INVALID_SOCKET\r\n"); } return is_connect; }// #ifndef faveLoadFunction #define faveLoadFunction FARPROC WINAPI LoadFunction(HINSTANCE h, LPCSTR fun_name) { FARPROC WINAPI r = 0; r = GetProcAddress(h, fun_name); if (r == 0) printf("load function %s error\r\n", fun_name); else printf("load function %s ok\r\n", fun_name); return r; }// #endif void LoadFunctions_Socket() { //HINSTANCE hs = LoadLibrary("wsock32.dll"); //根据不同的编译环境,有可能要从 LoadLibrary 改成 LoadLibraryA HINSTANCE hs = LoadLibraryA("wsock32.dll"); //根据不同的编译环境,有可能要从 LoadLibrary 改成 LoadLibraryA if (hs == 0) printf("load wsock32.dll error\r\n", hs); else printf("load wsock32.dll ok\r\n", hs); _socket = GetProcAddress(hs, "socket"); printf("_socket:%d\r\n", _socket); if (_socket == 0) printf("load _socket error\r\n", hs); //-------------------------------------------------- //直接装载各个 dll 函数 _socket = LoadFunction(hs, "socket"); _WSAStartup = LoadFunction(hs, "WSAStartup"); _inet_addr = LoadFunction(hs, "inet_addr"); _htons = LoadFunction(hs, "htons"); _connect = LoadFunction(hs, "connect"); _WSAGetLastError = LoadFunction(hs, "WSAGetLastError"); _send = LoadFunction(hs, "send"); _recv = LoadFunction(hs, "recv"); _select = LoadFunction(hs, "select"); _gethostbyname = LoadFunction(hs, "gethostbyname"); } // 系统错误信息提示 void PrintError(DWORD last_err) { //进行出错。 //if (!CreateDirectory(_T("c:\\"),0)) { char buf[512];//char buf[128]; LPVOID lpMsgBuf; DWORD dw; memset(&buf, 0, sizeof(buf)); dw = last_err;//GetLastError(); if (dw == 0) dw = GetLastError(); //实际上是可以代替 WSAGetLastError 的 //dw = 5;//10035;//6000054; if (dw == 0) return; FormatMessage ( FORMAT_MESSAGE_FROM_SYSTEM,//FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM, //FORMAT_MESSAGE_ALLOCATE_BUFFER 是指要分配内存 NULL, dw, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPTSTR) &buf,//(LPTSTR) &lpMsgBuf, 500,//0, //因为是自己分配的内存,所以要指出分配了多大 NULL ); printf("PrintError(出错码=%d):%s\r\n", dw, buf); //奇怪,这里就是不对//是倒数第 2 个 参数的问题 FormatMessage ( FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM, //FORMAT_MESSAGE_ALLOCATE_BUFFER 是指要分配内存 NULL, dw, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPTSTR) &lpMsgBuf, 0, NULL ); wsprintf(buf, "出错信息 (出错码=%d): %s", dw, lpMsgBuf); printf("PrintError(出错码=%d):%s\r\n", dw, lpMsgBuf); //printf("PrintError(出错码=%d):%s\r\n", dw, &buf); //奇怪,这个是不对的 printf("PrintError(出错码=%d):%s\r\n", dw, buf); LocalFree(lpMsgBuf); //输出提示 //OutputDebugString(szBuf); } }// //尚未精测试,可能有误 int SendBuf(SOCKET so, char * s, int len) { int r = 0; int count = 0; char * p = s; //r := 0; int Result = 0; //count := 0; //p := @s[1]; while (Result<len) { r = _send(so, p, len - Result, 0); if (r > 0) { Result = Result + r; p = p + r; }; count++; if (count>10) //超过多少次就不发送了 { MessageBox(0, "send error", "", 0); return Result; } } return Result; }// //注意,返回的字符串要自己释放 //lstring RecvBuf(SOCKET so) //算了,还是传可自动释放的字符串进去方便点 //void RecvBuf(SOCKET so, lstring * buf)//用这个格式也可以,不过与其他语言不通用 lstring * RecvBuf(SOCKET so, struct MemPool * pool) { char buf[1024+1]; int r = 0; //lstring s = String(""); //lstring * s = NewString("", _buf->pool); //CheckPString(_buf); lstring * s = NewString("", pool); memset(&buf, 0, sizeof(buf)); r = _recv(so, buf, sizeof(buf)-1, 0); //留下一个 #0 结尾 if (r > 0) { //SetLength(s, r); //Move(buf, s[1], r); //s.Append(&s, StringConst(buf, r)); LString_AppendCString(s, buf, r); } if (r == 0) //一般都是断开了 { MessageBox(0, "recv error.[socket close]", "", 0); //return String(""); //不应该再生成一个新的 return s; } return s; }// //是否可读取 int SelectRead(SOCKET so) { fd_set fd_read; //fd_read:TFDSet; struct timeval timeout; // : TTimeVal; int Result = 0; FD_ZERO( &fd_read ); FD_SET(so, &fd_read ); //个数受限于 FD_SETSIZE timeout.tv_sec = 0; //秒 timeout.tv_usec = 500; //毫秒 timeout.tv_usec = 0; //毫秒 //linux 第一个参数一定要赋值 ////int r = ::select(socket_handle+1, &fd_read, NULL, NULL, &l_timeout); //if select( 0, &fd_read, nil, nil, &timeout ) > 0 then //至少有1个等待Accept的connection if (_select( so+1, &fd_read, NULL, NULL, &timeout ) > 0) //至少有1个等待Accept的connection Result = 1; return Result; }// #endif
当然,我这些代码倒也不是为了写示例方便大家测试,而是因为工作的关系我需要在多个 ide 和多种编译器中切换,早就写好用来测试网络应用的(主要是xmpp协议,所以以后我们也顺便介绍一下 xmpp 协议的一些简单实现).
其中有以下几点需要说明一下:
1.函数需要从 dll 中取出其地址,在代码中实现为 LoadFunctions_Socket();
2.LoadLibraryA/LoadLibrary 函数可以理解为将一个 dll 中的代码读取到程序的内存中;
3.GetProcAddress 函数可以理解为找到一个函数的地址;
4.不能直接使用原始的 socket 函数名,要使用时,在前面加一个划线;
5.只引入了我用到的少数几个 socket 函数,有需要的网友可以自己引入:方法是先声明一个同原型的函数指针,然后加入到 LoadFunctions_Socket() 中就可以了.
6.代码主要工作在 windows 版本的 gcc, vc6 和 vc2010 下也做过测试. 随着代码的增加 vc 下如果不能运行,大家可以自己改下函数指针的声明和 api 函数的版本(换下 a 和 w 的版本). 至于 a/w 版本是什么意思,不懂的同学我们以后再说明吧.
7.我直接 include 了 c 的文件,方便在各个开发工具中切换,不喜欢这种方式的同学请自己改成 h 文件的方式吧.
我主要的工作环境是 cfree,原因主要是 vc 产生的临时文件实在是太大了,设置不产生临时文件又会产生别的问题,而用 dev c++ 这些传统的 gcc 环境的话,代码提示又是一个问题. 找了很久我最终选用了 cfree 软件,不过要说明的是 cfree 是收费的,但费用不足百元,所以虽然网上有很多注册码可以用我还是推荐大家注册一下.遗憾的是,国内的共享环境之恶劣导致我付费时很是花了几天时候,最终是找作者要到了他的淘宝账号直接转的钱,作者一度以为我是骗子让我去联系第三方的注册网站,我觉得那个第三方的注册网站更象骗子,所以坚持直接转给他.之所以我认为第三方注册机构更象骗子是因为我也写过共享软件 ...
cfree 对我来说有以下吸引力的优点:
1.没有太大的临时文件;
2.代码提示还不错;
3.不需要建立工程文件;
4.注册费用低,我是它的正版用户;
5.通过配置可以把编辑器界面弄得象我喜欢的 vscode;
6.我有一个 32 位的 windows 2003 工作环境,那里用不了 vscode,也不用了现在最新的 vc;
7.作为 gcc 系,几乎不需要配置编译环境.
这里要提一下开源的 codelite,也是不错的软件,代码提示也做得很好,但生成的临时文件大了点,而且需要配置编译环境(对初学者很致命),如果没有 cfree 我就用它了.
另外一定要说的是它们的调试都很差,如果有无法理解的代码,请打开您的 VC ... 不过我现在调试用的是我能找到的最简便的网友简化版的非官方版本 BCB2009 (其实人家叫 CodeGear C++ Builder 2009,bcb 的称呼只是历史原因),因为这个版本安装非常方便,要不我就用 bcb6 了.原版 bcb2009 是否方便我就不清楚了. 找不到简化版本的同学用最常见的 vc6 调试就行了,一样好用.
好了,下面给出测试的 demo 代码. c 语言中还有些要注意的字节对齐等问题也来不及说了,我们下篇再说吧.
gSo = CreateTcpClient(); r = ConnectHost(gSo, "newbt.net", 25); if (r == 1) printf("连接成功!\r\n"); s = NewString("EHLO\r\n", m); SendBuf(gSo, s->str, s->len); printf(s->str); s->Append(s, s); printf(s->str); s->AppendConst(s, "中文\r\n"); printf(s->str); rs = RecvBuf(gSo, m); //注意这个并不只是收取一行 printf("\r\nRecvBuf:\r\n"); printf(rs->str); rs = RecvBuf(gSo, m); //注意这个并不只是收取一行 printf("\r\nRecvBuf:\r\n"); printf(rs->str);
这是通讯过程,代码并不复杂,大家可以看到简单封装后并不复杂,所以按自己顺手的封装一下很有必要.
以下是完整代码:
(文件名 socket_test1.c)
#include <stdio.h> #include <windows.h> #include <time.h> #include <winsock.h> #include "lstring.c" #include "socketplus.c" #include "lstring_functions.c" //vc 下要有可能要加 lib //#pragma comment (lib,"*.lib") //#pragma comment (lib,"libwsock32.a") //#pragma comment (lib,"libwsock32.a") //SOCKET gSo = 0; SOCKET gSo = -1; void main() { int r; mempool mem, * m; lstring * s; lstring * rs; //-------------------------------------------------- mem = makemem(); m = &mem; //内存池,重要 //-------------------------------------------------- //直接装载各个 dll 函数 LoadFunctions_Socket(); InitWinSocket(); //初始化 socket, windows 下一定要有 gSo = CreateTcpClient(); r = ConnectHost(gSo, "newbt.net", 25); if (r == 1) printf("连接成功!\r\n"); s = NewString("EHLO\r\n", m); SendBuf(gSo, s->str, s->len); printf(s->str); s->Append(s, s); printf(s->str); s->AppendConst(s, "中文\r\n"); printf(s->str); rs = RecvBuf(gSo, m); //注意这个并不只是收取一行 printf("\r\nRecvBuf:\r\n"); printf(rs->str); rs = RecvBuf(gSo, m); //注意这个并不只是收取一行 printf("\r\nRecvBuf:\r\n"); printf(rs->str); //-------------------------------------------------- Pool_Free(&mem); //释放内存池 printf("gMallocCount:%d \r\n", gMallocCount); //看看有没有内存泄漏//简单的检测而已 //-------------------------------------------------- getch(); //getch().不过在VC中好象要用getch(),必须在头文件中加上<conio.h> }
除了通讯 demo 部分外,其他代码大家都不用细看,都是临时的辅助封装函数而已,用到生产环境中后果我可不负责(虽然我认为改一改也是可以用的).就当是抛砖引玉吧.
代码里其实还用到了一个很重要的字符串类,不过也是来不及解说了,先给出代码如下:
(文件名为 lstring.c 和 lstring_functions.c,本来我是要放到 github 上的,不过估计也是没时间维护了,所以直接贴一下吧)
//没办法还是得另定义一个字符串 #ifndef _L_STRING_C_ #define _L_STRING_C_ #include <stdio.h> #include <malloc.h> //有些编译,如果 malloc.h 在后面的话会报 malloc 函数冲突,解决办法很简单,把含有 malloc 的头文件放前面,好让我们的 malloc 定义能覆盖它就可以了 #include <string.h> //#include <time.h> //#include <winsock.h> #include <windows.h> #include <time.h> //#include <crt/eh.h> //据说 MinGW终于支持使用seh实现c++ eh了 //https://sourceforge.net/p/mingw-w64/mingw-w64/ci/18a7e88bcbe8bc0de4e07dac934ebf0653c4da7c/tree/mingw-w64-headers/crt/eh.h int gMallocCount = 0; //简单的内存泄漏检测 void * malloc_v2(size_t size) { gMallocCount++; return malloc(size); } void * free_v2(void * p) { gMallocCount--; free(p); } #define malloc malloc_v2 #define free free_v2 typedef struct LString _LString; //相互引用的提前声明好象必须用 typedef //内存池中的一项//算了, string 的自动释放就很复杂了,还是专注于 string 的吧 struct MemPool_Item{ //void * data; //要释放的数据,可以是不同类型的 _LString * data; //要释放的数据,可以是不同类型的 //void (*FreeFunc)(struct MemPool_Item * item); //对应节点的释放函数//太复杂,就当做原始的 free 函数释放就行了 struct MemPool_Item * next; }; //内存池,用于释放一次函数过程中分配的内存 struct MemPool{ struct MemPool_Item * Items; int Count; char * str; byte _const; //是否是只读的,如果是只读的就是从数组中构造的,不要释放它 int id; //只是为调试释放而已 }; struct LString{ char * _init; //只是用来判断是否进行了初始化,不可靠的简单判断,为 0 就是初始化了 int len; char * str; byte _const; //是否是只读的,如果是只读的就是从数组中构造的,不要释放它 byte _malloc; //这个结构是不是 malloc 生成的,如果是还要 free 掉结构体本身 struct MemPool * pool; //字符串所在的函数体自动释放内存池,如果是 NULL 就是要手工释放的 //在操作字符串的临时函数中可以使用它 void (*Append)(struct LString * s, struct LString * add); void (*AppendConst)(struct LString * s, char * add); }; #define mempool struct MemPool //函数体临时使用字符串时 //#define USEPOOL struct MemPool _function_mem_ = makemem(); //#define ENDUSEPOOL Pool_Free(&_function_mem_); //输出打印的级别 //最普通的错误信息 #define printf_err printf //最低级别的打印 #define printf_err1 //#define printf_err1 printf mempool makemem() { mempool mem; memset(&mem, 0, sizeof(mem)); srand((unsigned) time(NULL)); //用时间做种,每次产生随机数不一样 mem.id = rand(); //number = rand() % 101; //产生0-100的随机数 return mem; } mempool * newmempool() { mempool * pmem; pmem = malloc(sizeof(struct MemPool)); memset(pmem, 0, sizeof(struct MemPool)); srand((unsigned) time(NULL)); //用时间做种,每次产生随机数不一样 pmem->id = rand(); //number = rand() % 101; //产生0-100的随机数 return pmem; } //太复杂,就当做一个字符串池,用在一个函数体结束时自动释放这个过程中产生的所有临时字符串,类似于 php 的自动释放原理 //加入一个节点, pool 并不分配内存,只是把大家加到一个链表中统一释放而已 //函数参数的定义的函数指针是一样的 void Pool_AddItem(struct MemPool * pool, void * p) { struct MemPool_Item * item = NULL; _LString * s = NULL; if (pool == NULL) return; //-------------------------------------------------- //简单的验证 s = p;//item->data; if (s->_init != NULL) //简单的初始化检测 { printf("Pool_AddItem: error 未初始化的字符串指针!!!\r\n"); } //-------------------------------------------------- item = malloc(sizeof(struct MemPool_Item)); memset(item, 0, sizeof(struct MemPool_Item)); //item->FreeFunc = FreeFunc; item->data = p; //下面两步是替换掉头节点 item->next = pool->Items; pool->Items = item; pool->Count++; }// //释放一大片,可以做些简单的检测 void Pool_Free(struct MemPool * pool) { struct MemPool_Item * item = NULL; int i = 0; _LString * s = NULL; for(i=0; i<pool->Count; i++) { printf_err1("Pool_Free:%d, %d\r\n", pool->Count, i); item = pool->Items; //item->FreeFunc(item->data); //item->FreeFunc(item); //这样更清晰一点 s = item->data; if (s->_init != NULL) //简单的初始化检测 { printf("Pool_Free: error 未初始化的字符串指针!!!\r\n"); } //free(item->data); free(s->str); if (s->_malloc == 1) //结构体本身也要释放的话 free(s); pool->Items = pool->Items->next; //向下移动一个指针位置 free(item); //节点自己的内存也要释放 } pool->Count = 0; } //自动释放 //void autofree(struct MemPool * pool, void * p) //{ // Pool_AddItem(pool, p); //} void freemempool(mempool * pool) { Pool_Free(pool); free(pool); } #define freemem Pool_Free struct LStringRef{ //const struct LBuf * buf; //这个是保存内存内容的地方,不应该变 //本意是让这个指针值固定,但这样导致里面的值也变不了 struct LString * buf; //这个是保存内存内容的地方,不应该变 //C 语言的特点,为了在传递参数时不使用指针,只能是把指针放到成员中 void (*Append)(struct LString * s, struct LString * add); //int (*AppendConst)(struct LString * s, const char * add); }; #define lstring struct LString #define PLString struct LString * #define stringref struct LStringRef //#define GetStr s.str //下面这几个宏可是可以用,不过太容易冲突了,最好是逻辑清晰度要求很高的地方才用 #define GetStr buf->str #define GetLen buf->len //#define str() buf->str //#define len() buf->len #define str__ buf->str #define len__ buf->len //为了能自动释放,只能是指针,结构体在参数传递时会丢失字段值,所以完全的模仿 C++ 是不可能的 //绑定各个成员函数 void BindStringFunctions(lstring * s); //lstring String(char * str); //自动释放//只释放字符串 void autofree_s(struct MemPool * pool, struct LString * s) { if (s == NULL) return; s->pool = pool; //加上这个标志,这样根据这个 s 操作出来的 string 都可以通过它自动释放了 //Pool_AddItem(pool, p); //Pool_AddItem(pool, s->str); Pool_AddItem(pool, s); } //自动释放//不只释放字符串,连指针一起释放 void autofree_pstring(struct MemPool * pool, struct LString * s) { if (s == NULL) return; s->pool = pool; //加上这个标志,这样根据这个 s 操作出来的 string 都可以通过它自动释放了 Pool_AddItem(pool, s); //Pool_AddItem(pool, s->str); } // ////分配并返回一个字符串 //stringref MakeString(char * str) //{ // stringref s; // // s.buf = malloc(sizeof(struct LString)); // memset(s.buf, 0, sizeof(struct LString)); // // s.buf->_const = 0; // s.buf->len = strlen(str); // s.buf->str = NULL; // // if (str != NULL) // { // s.buf->str = malloc(s.buf->len + 1); //还要留最后 \0 的位置 // memset(s.buf->str, 0, s.buf->len + 1); // // strcpy(s.buf->str, str); // } // // //绑定各个成员函数 // //BindStringFunctions(&s); // // return s; // //}// // ////分配并返回一个字符串指针 //lstring * PString(lstring s) //{ // //lstring s = String(str); // // lstring * p = malloc(sizeof(struct LString)); // // *p = s; // p->_malloc = 1; //要释放结构体本身 // // autofree_pstring(s->pool, p); // // return p; // //}// // ////释放 //pfree 是否释放指针本身 //void FreeStringRef(stringref * s, int pfree) //{ // if (s->buf == NULL || s->buf->str == NULL) // { // printf("FreeString() error: string is NULL"); // // return; // } // // if (s->buf->_const != 0) // { // printf("FreeString() error: string is readonly"); // // //return; // } // else // { // free(s->buf->str); // s->buf->str = NULL; // } // // free(s->buf); // // if (pfree) // free(s); // //}// void _FreeString(lstring * s, int pfree) { if (s == NULL) { printf("FreeString() error: lstring * s is NULL"); return; } if (s->str == NULL) { printf("FreeString() error: s->str is NULL"); return; } if (s->_const != 0) { printf("FreeString() error: string is readonly"); //return; } else { free(s->str); s->str = NULL; } if (pfree) //是否释放指针本身 free(s); }// void FreeString(lstring s) { _FreeString(&s, 0); //只释放数据 } void FreePString(lstring * s) { _FreeString(s, 1); //释放数据和指针 } ////释放//给内存池调用的 //void FreeString_ForPool(struct MemPool_Item * poolitem) //{ // //void * p = poolitem->data; // lstring * p = poolitem->data; // // if (p == NULL) return; // // printf("准备释放:%s\r\n", p->str); // // // FreePString(p); // // //}// // //分配一个内存池释放的 //lstring * StringPool(struct MemPool * pool, char * str) //{ // // lstring * s = PString(str); // Pool_AddItem(pool, s, FreeString_ForPool); // // return s; //} //分配一个内存池释放的 //stringref StringRef(struct MemPool * pool, char * str) //{ // // lstring * s = PString(str); // Pool_AddItem(pool, s, FreeString_ForPool); // // return *s; //} //用结构体就做不了自动释放,所以还是用指针吧,毕竟不是 C++ //lstring String(char * str, struct MemPool * pool) //{ // // lstring s; // // memset(&s, 0, sizeof(struct LString)); // // s.len = strlen(str); // s.str = malloc(s.len+1); // memset(s.str, 0, s.len+1); // // memcpy(s.str, str, s.len); // // //绑定各个成员函数 // BindStringFunctions(&s); // // autofree_s(pool, s); // // return s; //} //各个常用函数中尽量使用这个函数分配新字符串,因为它生成的可自动释放,避免 autofree 满天飞 //这个应该是基础函数,不要使用其他函数实现 lstring * NewString(char * str, struct MemPool * pool) { //lstring s = String(str); lstring * p = malloc(sizeof(struct LString)); memset(p, 0, sizeof(struct LString)); p->_malloc = 1; p->len = strlen(str); p->str = malloc(p->len+1); memset(p->str, 0, p->len+1); memcpy(p->str, str, p->len); //绑定各个成员函数 BindStringFunctions(p); autofree_pstring(pool, p); return p; } //一般用于参数传递 lstring StringCopy(lstring str) { lstring s; memset(&s, 0, sizeof(struct LString)); s.len = str.len; s.str = malloc(s.len+1); memset(s.str, 0, s.len+1); memcpy(s.str, str.str, str.len); //绑定各个成员函数 BindStringFunctions(&s); return s; } //复制一个字符串给另外一个内存池,如果对方为 NULL 那么就变成自由的字符串了,不过最好是不要这样做,应当从设计上要求每个字符串生成时都有medh池 lstring * PStringCopyToPool(lstring * s, struct MemPool * pool) { lstring * p = malloc(sizeof(struct LString)); memset(p, 0, sizeof(struct LString)); p->_malloc = 1; //结构体本身是分配的内存,也要释放 p->len = s->len; p->str = malloc(s->len+1); memset(p->str, 0, s->len+1); memcpy(p->str, s->str, s->len); //绑定各个成员函数 BindStringFunctions(p); //if (autofree == 1) autofree_pstring(s->pool, p); autofree_pstring(pool, p); return p; }// //复制一个字符串 //lstring * PStringCopy(lstring * s, int autofree) lstring * PStringCopy(lstring * s) { return PStringCopyToPool(s, s->pool); // lstring * p = malloc(sizeof(struct LString)); // // memset(p, 0, sizeof(struct LString)); // // p->_malloc = 1; //结构体本身是分配的内存,也要释放 // // p->len = s->len; // // p->str = malloc(s->len+1); // memset(p->str, 0, s->len+1); // // memcpy(p->str, s->str, s->len); // // //绑定各个成员函数 // BindStringFunctions(p); // // //if (autofree == 1) // autofree_pstring(s->pool, p); // // return p; }// //不分配内存,只是将一个缓冲区按 string 方式操作而已,类似 golang 的 bytes ,所以不要释放它 //这个也是基础函数 ,虽然它的内存不用释放,但也还是要传 pool ,以便给生成的子字符串自动释放的机会 lstring StringConst(char * str, int len, struct MemPool * pool) { lstring s; //s._const = 0; s._const = 1; s.len = len; //strlen(str); s.str = str; //malloc(s.len); s.pool = pool; //绑定各个成员函数 BindStringFunctions(&s); return s; }// void LString_Append(lstring * _s, lstring * _add) { lstring s = (*_s); //牺牲一点点性能来换语法//golang 就没有 -> lstring add = (*_add);//牺牲一点点性能来换语法//golang 就没有 -> char * tmp = malloc(s.len + add.len + 1); //还要留最后 \0 的位置 memset(tmp, 0, s.len + add.len + 1); memcpy(tmp, s.str, s.len); memcpy(tmp + s.len, add.str, add.len); //这里要注意//加上后半段 free(s.str); s.str = tmp; s.len = s.len + add.len; // char * tmp = malloc(s->len + add->len + 1); //还要留最后 \0 的位置 // memset(tmp, 0, s->len + add->len + 1); // // memcpy(tmp, s->str, s->len); // memcpy(tmp + s->len, add->str, add->len); //这里要注意//加上后半段 // // free(s->str); // // s->str = tmp; // s->len = s->len + add->len; *_s = s; }// //加入传统 C 字符串 void LString_AppendCString(lstring * s, char * str, int len) { lstring add = StringConst(str, len, s->pool); printf("add len:%d", add.len); LString_Append(s, &add); }// void LString_AppendConst(lstring * s, char * add) { //printf("add len:%d", add->len); LString_AppendCString(s, add, strlen(add)); }// //int LString_AppendConst(lstring * _s, const char * _add) //{ // printf("ok4 \r\n"); // lstring add = StringConst((char *)_add, strlen(_add)); // // printf("ok1"); // LString_Append(_s, &add); // printf("ok"); //}// //检查字符串指针是否合法,只是简单的方法,不可靠,但有一定作用 int CheckPString(lstring * s) { if (s == NULL) { printf("CheckPString: error, string is NULL!!!\r\n"); //就目前的要自动释放的需求来说,是不能为 NULL 的,因为那样 pool 就没有传入了 return 0; } printf_err1("CheckPString: s->_init != NULL\r\n"); if (s->_init != NULL) //其实对于现代的编译器和操作系统来说,如果 s 没有初始化,在这里就很可能崩溃,所以实际上是检测不出来的,所以首尾都打印一下看看这个过程没结束就是这里出错了,对性能要求高的地方,去掉 printf 宏就可以不打印了 { printf("CheckPString: error, string is not init!!!\r\n"); //就目前的要自动释放的需求来说,是不能为 NULL 的,因为那样 pool 就没有传入了 return 0; } printf_err1("CheckPString: s->_init != NULL ok.\r\n"); return 1; }// //绑定各个成员函数 void BindStringFunctions(lstring * s) { printf_err1("BindStringFunctions \r\n"); s->Append = LString_Append; s->AppendConst = LString_AppendConst; }// #endif
//操作 lstring * 的各种底层函数 //因为 lstring 包含了传统 C 的 0 结尾,所以大部分可以直接代用 C 的函数,当然最好参照 golang 重写 #ifndef _L_STRING_FUNCTIONS_C_ #define _L_STRING_FUNCTIONS_C_ #include <stdio.h> #include <string.h> #include "lstring.c" //delphi 转换方便函数//但 C 语言索引是从 0 开始,不是 d7 的从 1 开始,一定要注意 //查找子串位置 //php 的 strpos 基本上也是这样 int pos(lstring * substr, lstring * s) { char * p = strstr(s->str, substr->str); if (p == NULL) return -1; return p - (s->str); } //substr(字符串,截取开始位置,截取长度)//从 0 开始 //lstring * substr(lstring * s, int start, int len) lstring * substring(lstring * s, int start, int len) { lstring r; lstring * sub; char * p = NULL; //if (start + len > s->len) return NULL;//如果太多 if (start + len > s->len) len = (s->len) - start;//如果太多返回后面的 p = s->str + start; r = StringConst(p, len, s->pool); //r = StringCopy(r); //StringConst 不分配内存的,所以要复制一个新的出来 //sub = PString(r); //autofree_pstring(s->pool, sub);//返回字符串跟着 s 一起释放 sub = PStringCopy(&r); return sub; } //字符串是否相等 //类似 java 的 equals int str_equals(lstring * s1, lstring * s2) { if (s1 == NULL && s2 == NULL) return 1; if (s1 == NULL || s2 == NULL) return 0; //两者不可能都为空的情况下有一个为空,那就是不相等了 if (s1->len != s2->len) return 0; //长度不等肯定也不是 if ( 0 == strncmp(s1->str, s2->str, s1->len) ) return 1; return 0; } int streq(lstring * s1, lstring * s2) { return str_equals(s1, s2); } int str_equals_c(lstring * s1, char * s2) { if ( 0 == strcmp(s1->str, s2) ) return 1; return 0; } //3) 前加#,将标记转换为字符串. //#define C(x) #x //则C(1+1) 即 ”1+1”. //#define C(x) #x #define C(x) #define C1(a1, x, a2) strcmp(a1, a2) //int ee_-() int t() { C(==); C1("",==,""); //可以这样模拟一个 == 号出来 } //常用简写而已 int seq(lstring * s1, char * s2) { return str_equals_c(s1, s2); } //替换单个字符,在需要高效时使用,因为替换一长串比较慢 //str_replace lstring * str_replace_ch(lstring * s, char ch, char newch) { lstring * r = PStringCopy(s); int i; for (i=0; i<r->len; i++) { if (r->str[i] == ch) r->str[i] = newch; } return r; }// //主要用于判断空字符串//delphi 风格 int length(lstring * s) { if (s == NULL) return 0; return s->len; } //delphi 风格 int Length(lstring * s) { return length(s); } //主要用于判断空字符串//golang 风格 int len(lstring * s) { if (s == NULL) return 0; return s->len; } //转换为小写,注意这个在中文下会有问题//为了兼容字符串自动释放,当参数为空时只好返回空,要不找不到父节点 lstring * lowercase(lstring * s) { lstring * r = NULL; int i; if (s == NULL) { printf("lowercase: error, string is NULL!!!"); //就目前的要自动释放的需求来说,是不能为 NULL 的,因为那样 pool 就没有传入了 return NULL; } //检查字符串指针是否合法,只是简单的方法,不可靠,但有一定作用 CheckPString(s); r = PStringCopy(s); //r->pool = s->pool; //让原字符串释放它 //autofree_pstring(s->pool, r); //让原字符串释放它 for (i = 0; i < r->len; i++) { r->str[i] = tolower(r->str[i]); } //没有返回值,确实有问题 return r; } //转换为大写,注意这个在中文下会有问题//为了兼容字符串自动释放,当参数为空时只好返回空,要不找不到父节点 lstring * uppercase(lstring * s) { lstring * r = NULL; int i; if (s == NULL) { printf("lowercase: error, string is NULL!!!"); //就目前的要自动释放的需求来说,是不能为 NULL 的,因为那样 pool 就没有传入了 return NULL; } //检查字符串指针是否合法,只是简单的方法,不可靠,但有一定作用 CheckPString(s); r = PStringCopy(s); //r->pool = s->pool; //让原字符串释放它 //autofree_pstring(s->pool, r); //让原字符串释放它 for (i = 0; i < r->len; i++) { r->str[i] = toupper(r->str[i]); } //没有返回值,确实有问题 return r; } //与传统 get_value 不同,这个的匹配结尾字符串为查找互的第一个而不是最后一个 //应该不区分大小写 //要注意 C 语言的字符串是从 0 起始,而原来 delphi 的是从 1 起始的,所以 d7 转换过来的算法不能全部照搬的 lstring * get_value_first(lstring *s, lstring * b_sp, lstring * e_sp) { //开始复制的位置 int b_pos = 0; //复制结束的位置 int e_pos = 0; lstring * ls; lstring * r = NULL; if (len(e_sp) == 0) e_pos = length(s); ls = lowercase(s); b_sp = lowercase(b_sp); e_sp = lowercase(e_sp); //检查字符串指针是否合法,只是简单的方法,不可靠,但有一定作用 CheckPString(ls); CheckPString(b_sp); CheckPString(e_sp); //-------------------------------------------------- b_pos = pos(b_sp, ls); //if (length(b_sp) == 0) b_pos = 1; if (length(b_sp) == 0) b_pos = 0; //if (b_pos == 0) if (b_pos == -1) //没找到 { r = NewString("", s->pool); //autofree_pstring(s->pool, r); //返回的字符串跟着 s 一起释放 return r; }; b_pos = b_pos + length(b_sp); r = substring(s, b_pos, length(s)); //result = copy(s, b_pos, length(s)); //-------------------------------------------------- //e_pos = pos(e_sp, lowercase(r)) - 1; e_pos = pos(e_sp, lowercase(r)); if (e_pos == -1) e_pos = length(r); //r = substring(r, 1, e_pos); r = substring(r, 0, e_pos); return r; } //c 语言的参数 lstring * getValueFirst_c(lstring *s, char * b_sp, char * e_sp) { return get_value_first(s, NewString(b_sp, s->pool), NewString(e_sp, s->pool)); } //c 语言的参数 lstring * get_value_first_c(lstring *s, char * b_sp, char * e_sp) { return get_value_first(s, NewString(b_sp, s->pool), NewString(e_sp, s->pool)); } #endif