#include "pbvm.h" vm_state *last_vm = NULL; value * get_lvalue(vm_state *vm, lvalue_ref *value_ref){ lvalue *v=value_ref->ptr; //if (value_ref->isnull&1) return 0; switch(v->flag){ case 0:// immediate value (local variable?) return v->value; case 1:// instance field? return ot_get_field_lv(vm, v->value, v->parent); default:// array element? return ot_get_field_item_lv(vm, v->value, v->parent, v->item); } } void Throw_Exception(vm_state *vm, wchar_t *text, ...){ pb_class *exception_obj; va_list va; va_start(va,text); rt_create_obinst(vm,L"n_ex",&exception_obj); wchar_t x[512]; wvsprintf(x,text,va); ob_set_ptr_field(vm,exception_obj,1,ob_dup_string(vm,x)); GET_THROW(vm)=exception_obj; } void BuildKMPTable(wchar_t *find, int *kmp_table){ int pos=2, cnd=0; kmp_table[0] = -1; if (!find[0]) return; kmp_table[1] = 0; if (!find[1]) return; while (find[pos]){ if (find[pos -1] == find[cnd]){ kmp_table[pos++] = ++cnd; }else if(cnd>0){ if (find[cnd] == find[kmp_table[cnd]]) cnd = kmp_table[cnd]; if(cnd>0) cnd = kmp_table[cnd]; }else{ kmp_table[pos++] = 0; } } } wchar_t * StringSearch(wchar_t *start, wchar_t *find, int *kmp_table){ wchar_t *p=start; long i=0; while(*p){ if (*p == find[i]){ i++;p++; if (!find[i]) return p - i; }else if (i>0){ i=kmp_table[i]; }else p++; } return NULL; } wchar_t * StringSearchi(wchar_t *start, wchar_t *find, int *kmp_table){ wchar_t *p=start; long i=0; while(*p){ if ((iswupper(*p)?towlower(*p):*p)==find[i]){ i++;p++; if (!find[i]) return p - i; }else if (i>0){ i=kmp_table[i]; }else p++; } return NULL; } // boolean split(readonly source, readonly delim, ref string values[]) DWORD __declspec(dllexport) __stdcall Split (vm_state *vm, DWORD arg_count){ wchar_t *source, *delim; DWORD source_null, delim_null, isnull; lvalue_ref *lv_values; value ret; last_vm = vm; source = (wchar_t *)ot_get_valptr_arg(vm, &source_null); delim = (wchar_t *)ot_get_valptr_arg(vm, &delim_null); lv_values=ot_get_next_lvalue_arg(vm, &isnull); ret.value=FALSE; ret.type=7; ret.flags=0x0500; if (source&&*source&&delim&&*delim){ wchar_t *next, *last=source, *dest; DWORD delim_len, index=0, new_size; pb_array *values; int *kmp_table; delim_len=wcslen(delim); values = ot_array_create_unbounded(vm, MAKELONG(-1,6), 0); kmp_table=new int[delim_len+1]; BuildKMPTable(delim, kmp_table); while(1){ next=StringSearch(last, delim, kmp_table); //next=wcsstr(last, delim); if (next==NULL){ new_size=wcslen(last); }else{ new_size=next - last; } value *v=ot_array_index(vm, values, index); if (!(v->flags&IS_NULL)) ot_free_val_ptr(vm, v); dest = (wchar_t *)pbstg_alc(vm, (new_size+1) *2, GET_HEAP(vm)); wcsncpy(dest, last, new_size); dest[new_size]=0; v->value=(DWORD)dest; v->flags=0x0d00; v->type=6; if (!next) break; index++; last=next+delim_len; } delete kmp_table; ot_assign_ref_array(vm, lv_values->ptr, values, 0, 0); ret.value=TRUE; } ot_set_return_val(vm, &ret); return 1; } // boolean token(ref source, readonly delim, ref string segment) // calling this repeatedly is quite slow, you should try to use the split method instead, but this method is sometimes useful. DWORD __declspec(dllexport) __stdcall Token2 (vm_state *vm, DWORD arg_count){ wchar_t *source, *delim, *next, *dest; DWORD isnull, delim_len, new_size; lvalue_ref *lv_source, *lv_token; value ret, *v_source; last_vm = vm; ret.value=TRUE; ret.type=7; ret.flags=0x0500; lv_source=ot_get_next_lvalue_arg(vm, &isnull); v_source = get_lvalue(vm, lv_source); if (!v_source || !v_source->value || !*((wchar_t *)v_source->value)) ret.value=FALSE; delim = (wchar_t *)ot_get_valptr_arg(vm, &isnull); if (isnull || !delim || !*delim ) ret.value=FALSE; lv_token=ot_get_next_lvalue_arg(vm, &isnull); if (ret.value){ source = (wchar_t *)v_source->value; next=wcsstr(source, delim); if (next==NULL){ new_size=wcslen(source); dest = (wchar_t *)pbstg_alc(vm, (new_size+1) *2, GET_HEAP(vm)); wcsncpy(dest, source, new_size); dest[new_size]=0; ot_assign_ref_string(vm, lv_token->ptr, dest, 0); ot_assign_ref_string(vm, lv_source->ptr, NULL, 1); }else{ new_size = (next - source); dest = (wchar_t *)pbstg_alc(vm, (new_size+1) *2, GET_HEAP(vm)); wcsncpy(dest, source, new_size); dest[new_size]=0; ot_assign_ref_string(vm, lv_token->ptr, dest, 0); delim_len=wcslen(delim); next+=delim_len; new_size = wcslen(next); dest = (wchar_t *)pbstg_alc(vm, (new_size+1) *2, GET_HEAP(vm)); wcsncpy(dest, next, new_size); dest[new_size]=0; ot_assign_ref_string(vm, lv_source->ptr, dest, 0); } } if (!ret.value){ ot_assign_ref_string(vm, lv_token->ptr, NULL, 1); } ot_set_return_val(vm, &ret); return 1;} // boolean token(readonly source, readonly delim, ref long pos, ref string segment) // calling this repeatedly is quite slow, you should try to use the split method instead DWORD __declspec(dllexport) __stdcall Token (vm_state *vm, DWORD arg_count){ wchar_t *source, *delim, *next, *dest; DWORD isnull, len, end, pos, delim_len, new_size; lvalue_ref *lv_pos, *lv_token; value ret, *v_pos; last_vm = vm; ret.value=TRUE; ret.type=7; ret.flags=0x0500; source = (wchar_t *)ot_get_valptr_arg(vm, &isnull); if (isnull || !source || !*source ) ret.value=FALSE; delim = (wchar_t *)ot_get_valptr_arg(vm, &isnull); if (isnull || !delim || !*delim ) ret.value=FALSE; lv_pos=ot_get_next_lvalue_arg(vm, &isnull); v_pos = get_lvalue(vm, lv_pos); if (!v_pos) ret.value=FALSE; lv_token=ot_get_next_lvalue_arg(vm, &isnull); if (ret.value){ // comments show worked example for; token(" X ","X",pos=1,ret) len=wcslen(source); // len=3 pos=v_pos->value; if (pos<1) pos=1; if (pos>len){ ret.value=FALSE; }else{ delim_len=wcslen(delim); // delim_len=1 next=wcsstr(source + pos -1, delim); // next -> " ^X " if (next==NULL){ end=len+1; }else{ end = (next - source); // end = 1 } ot_assign_ref_long(vm, lv_pos->ptr, end + delim_len + 1, 0); // pos = 3 new_size = end - pos + 1; // new_size = 1 dest = (wchar_t *)pbstg_alc(vm, (new_size+1) *2, GET_HEAP(vm)); wcsncpy(dest, source + pos -1, new_size); dest[new_size]=0; ot_assign_ref_string(vm, lv_token->ptr, dest, 0); } } if (!ret.value){ ot_assign_ref_long(vm, lv_pos->ptr, 1, 0); ot_assign_ref_string(vm, lv_token->ptr, NULL, 1); } ot_set_return_val(vm, &ret); return 1; } // boolean next_tag(readonly string as_source, readonly string as_start, readonly string as_end, ref long al_start_pos, ref string as_tag) DWORD __declspec(dllexport) __stdcall Next_Tag (vm_state *vm, DWORD arg_count){ wchar_t *source, *start_delim, *end_delim; DWORD isnull; lvalue_ref *lv_pos, *lv_tag; value ret, *v_pos; last_vm = vm; ret.value=TRUE; ret.type=7; ret.flags=0x0500; source = (wchar_t *)ot_get_valptr_arg(vm, &isnull); if (isnull || source==NULL || *source==0) ret.value=FALSE; start_delim = (wchar_t *)ot_get_valptr_arg(vm, &isnull); if (isnull || start_delim==NULL || *start_delim==0) ret.value=FALSE; end_delim = (wchar_t *)ot_get_valptr_arg(vm, &isnull); if (isnull || end_delim==NULL || *end_delim==0) ret.value=FALSE; lv_pos=ot_get_next_lvalue_arg(vm, &isnull); v_pos = get_lvalue(vm, lv_pos); if (!v_pos) ret.value=FALSE; lv_tag=ot_get_next_lvalue_arg(vm, &isnull); if (ret.value){ DWORD len, pos; // comments show worked example for; token(" <X> ","<",">",pos=1,ret) len=wcslen(source); // len=5 pos=v_pos->value; if (pos<1) pos=1; if (pos>len){ ret.value=FALSE; }else{ wchar_t *start, *test, *end, *dest; DWORD start_len, end_len, new_size; start_len=wcslen(start_delim); // start_len=1 end_len=wcslen(end_delim); // end_len=1 test=source + pos -1; while (1){ // find the next end delimiter end=wcsstr(test, end_delim); // end -> " <X^> " if (end==NULL){ ret.value=FALSE; break; } // find the last start delimiter before the end delimiter start=NULL; test=source; while(1){ test = wcsstr(test, start_delim); if (test==NULL || test > end) break; start=test++; } if (start==NULL){ // try again, and find the next end delimiter test=end + 1; continue; } // start -> " ^<X> " new_size = end - start - start_len; // new_size = 1 dest = (wchar_t *)pbstg_alc(vm, (new_size+1) *2, GET_HEAP(vm)); wcsncpy(dest, start + start_len, new_size); dest[new_size]=0; ot_assign_ref_string(vm, lv_tag->ptr, dest, 0); // as_tag="X" ot_assign_ref_long(vm, lv_pos->ptr, start - source +1, 0); // al_start_pos=2 break; } } } if (!ret.value){ ot_assign_ref_long(vm, lv_pos->ptr, 1, 0); ot_assign_ref_string(vm, lv_tag->ptr, NULL, 1); } ot_set_return_val(vm, &ret); return 1; } int __cdecl compare( void *context, const void *arg1, const void *arg2 ) { /* Compare all of both strings: */ return wcscmp( (( wchar_t** )context)[*(long *)arg1], (( wchar_t** )context)[*(long *)arg2]); } int __cdecl comparei( void *context, const void *arg1, const void *arg2 ) { /* Compare all of both strings: */ return _wcsicmp( (( wchar_t** )context)[*(long *)arg1], (( wchar_t** )context)[*(long *)arg2]); } DWORD __declspec(dllexport) __stdcall Sort (vm_state *vm, DWORD arg_count){ DWORD ignored; value ret; long count; DWORD insensitive=FALSE; lvalue_ref *lv_array1, *lv_array2; value *v_array1, *v_array2; pb_array *array1, *array2; last_vm = vm; ret.type=1; ret.flags=0x500; ret.value=1; lv_array1 = ot_get_next_lvalue_arg(vm,&ignored); v_array1 = get_lvalue(vm, lv_array1); if (v_array1){ array1=(pb_array *)v_array1->value; count = ot_array_num_items(vm, array1); }else{ ret.value=-1; } if (arg_count>=2){ insensitive = ot_get_simple_intarg(vm, &ignored); } if (arg_count>=3){ lv_array2 = ot_get_next_lvalue_arg(vm, &ignored); v_array2 = get_lvalue(vm, lv_array2); if (v_array2){ array2=(pb_array *)v_array2->value; if (count != ot_array_num_items(vm, array2)){ ret.value=-1; } }else{ ret.value=-1; } } if (ret.value==1){ wchar_t **strings; value *values; long *index; strings=new wchar_t*[count]; index = new long[count]; for (long i=0;i<count;i++){ value *v=ot_array_index(vm, array1, i); strings[i]=(wchar_t*)v->value; index[i]=i; } if (arg_count>=3){ values=new value[count]; for (long i=0;i<count;i++){ value *v=ot_array_index(vm, array2, i); values[i].value=v->value; values[i].flags=v->flags; values[i].type=v->type; } } qsort_s( index, count, sizeof( long ), (insensitive?comparei:compare), strings ); for (long i=0;i<count;i++){ value *v = ot_array_index(vm, array1, i); v->value=(DWORD)strings[index[i]]; } if (arg_count>=3){ for (long i=0;i<count;i++){ value *v = ot_array_index(vm, array2, i); v->value=values[index[i]].value; v->flags=values[index[i]].flags; v->type=values[index[i]].type; } delete values; } delete strings; delete index; } ot_set_return_val(vm, &ret); return 1; } /*usage: long ll_values[]={1,2,3,4,5} ll_i = index(4, ll_values) */ DWORD __declspec(dllexport) __stdcall Index (vm_state *vm, DWORD arg_count){ value ret; last_vm = vm; value *v_find = ot_get_next_evaled_arg_no_convert(vm); value *v_array = ot_get_next_evaled_arg_no_convert(vm); int len=0; void *p; bool ptr = false; switch(v_find->type){ case pbvalue_int: case pbvalue_boolean: case pbvalue_uint: case pbvalue_char: len=2; break; case pbvalue_byte: len=1; break; case pbvalue_long: case pbvalue_ulong: case pbvalue_real: len=4; break; case pbvalue_longlong: case pbvalue_double: len=8; ptr=true; break; case pbvalue_dec: len=16; ptr=true; break; case pbvalue_blob: ptr=true; { blob *b=(blob*)v_find->value; // since the length is at the start, if it doesn't match memcmp won't look any further into potentially unallocated memory len = b->len+4; } break; case pbvalue_string: ptr=true; break; case pbvalue_date: case pbvalue_time: case pbvalue_datetime: len=12; ptr=true; break; } if (ptr){ p=(char*)v_find->value; }else{ p=(char*)&v_find->value; } ret.value=0; ret.type=pbvalue_long; ret.flags=0x100; pb_array *parray=(pb_array *)v_array->value; long count = ot_array_num_items(vm, parray); for (long i=0;i<count;i++){ value *v=ot_array_index(vm, parray, i); if (v->type == v_find->type){ if (v_find->flags&IS_NULL){ if (v->flags&IS_NULL){ ret.value=i+1; break; } }else if (v->flags&IS_NULL){ continue; }else if (v->type == pbvalue_string){ if (wcscmp((wchar_t*)p,(wchar_t*)v->value)==0){ ret.value=i+1; break; } }else if (ptr){ if (memcmp(p,(void*)v->value,len)==0){ ret.value=i+1; break; } }else{ if (memcmp(p,(void*)&v->value,len)==0){ ret.value=i+1; break; } } } } ot_set_return_val(vm, &ret); return 1; } // in most cases should perform about the same as pos // doesn't have some of the same worst cases as pos though... DWORD __declspec(dllexport) __stdcall Fast_Pos (vm_state *vm, DWORD arg_count){ DWORD source_null, find_null; wchar_t *source, *find; value v; last_vm = vm; source = (wchar_t *)ot_get_valptr_arg(vm, &source_null); find = (wchar_t *)ot_get_valptr_arg(vm, &find_null); v.value=0; v.type=pbvalue_long; v.flags=0x100; if (!(source_null||find_null)){ int find_len = wcslen(find); if (find_len>0){ int *kmp_table = new int[find_len]; BuildKMPTable(find, kmp_table); wchar_t *p=StringSearch(source, find, kmp_table); delete kmp_table; if (p) v.value=p - source +1; } } ot_set_return_val(vm, &v); return 1; } // should perform better than last pos in all cases. // in some synthetic examples it should be MUCH faster. DWORD __declspec(dllexport) __stdcall Last_Pos (vm_state *vm, DWORD arg_count){ DWORD source_null, find_null; wchar_t *source, *find; value v; last_vm = vm; source = (wchar_t *)ot_get_valptr_arg(vm, &source_null); find = (wchar_t *)ot_get_valptr_arg(vm, &find_null); v.value=0; v.type=pbvalue_long; v.flags=0x100; if (!(source_null||find_null)){ int find_len = wcslen(find); if (find_len>0){ int *kmp_table = new int[find_len]; find_len --; int find_pos = find_len; int cnd=find_len; int pos=cnd; kmp_table[pos--]=cnd; if (pos>=0){ kmp_table[pos--]=cnd; while (pos>=0){ if (find[pos+1]==find[cnd]){ kmp_table[pos--] = --cnd; }else if (cnd < find_len){ if (find[cnd]==find[kmp_table[cnd]]) cnd=kmp_table[cnd]; if (cnd < find_len) cnd=kmp_table[cnd]; }else{ kmp_table[pos--] = find_len; } } } wchar_t *p=source+wcslen(source) -1; while (p>=source){ if (*p==find[find_pos]){ if (find_pos==0){ v.value=p - source + 1; break; } find_pos --; p --; }else if (find_pos<find_len){ find_pos= kmp_table[find_pos]; }else{ p --; } } delete kmp_table; } } ot_set_return_val(vm, &v); return 1; } // string Replace_All (readonly string source, readonly string find, readonly string replace [, boolean case_sensitive]) DWORD __declspec(dllexport) __stdcall Replace_All (vm_state *vm, DWORD arg_count){ DWORD source_null, find_null, replace_null, ignored; wchar_t *source, *find, *replace; DWORD insensitive=FALSE; value v; last_vm = vm; source = (wchar_t *)ot_get_valptr_arg(vm, &source_null); find = (wchar_t *)ot_get_valptr_arg(vm, &find_null); replace = (wchar_t *)ot_get_valptr_arg(vm, &replace_null); if (arg_count>=4) insensitive = ot_get_simple_intarg(vm, &ignored); v.value=0; v.flags=0x0d01; v.type=pbvalue_string; if (!(source_null||find_null)){ long count=0, buff_size, len_find, len_replace=0; int *kmp_table; wchar_t *p, *dest, *last, *find_copy; len_find=wcslen(find); if (len_find>0){ kmp_table=new int[len_find+1]; if (insensitive){ find_copy=new wchar_t[len_find+1]; // force the find string to lower case... er... for (int i=0;i<len_find+1;i++){ find_copy[i]=(iswupper(find[i])?towlower(find[i]):find[i]); } BuildKMPTable(find_copy, kmp_table); p=source; while ((p=StringSearchi(p, find_copy, kmp_table))!=NULL){ count++; p+=len_find; } }else{ BuildKMPTable(find, kmp_table); p=source; while ((p=StringSearch(p, find, kmp_table))!=NULL){ count++; p+=len_find; } } } if (count>0){ if (replace_null){ len_replace=0; }else{ len_replace=wcslen(replace); } buff_size=wcslen(source) + (count * len_replace) - (count * len_find) +1; }else{ buff_size=wcslen(source)+1; } dest = (wchar_t*)pbstg_alc(vm, buff_size *2, GET_HEAP(vm)); *dest=0; v.value=(DWORD)dest; v.flags=0x0d00; if (count>0){ p=source; last=p; if (insensitive){ while ((p=StringSearchi(p, find_copy, kmp_table))!=NULL){ long cpy = (p - last); wcsncpy(dest, last, cpy); dest+=cpy; *dest=0; if (len_replace>0){ wcscpy(dest,replace); dest+=len_replace; } p+=len_find; last=p; } delete find_copy; }else{ while ((p=StringSearch(p, find, kmp_table))!=NULL){ long cpy = (p - last); wcsncpy(dest, last, cpy); dest+=cpy; *dest=0; if (len_replace>0){ wcscpy(dest,replace); dest+=len_replace; } p+=len_find; last=p; } } delete kmp_table; wcscpy(dest,last); }else{ wcscpy(dest,source); } } ot_set_return_val(vm, &v); return 1; } // boolean append(ref string, readonly string) // boolean append(ref blob, readonly string) DWORD __declspec(dllexport) __stdcall Append (vm_state *vm, DWORD arg_count){ DWORD isnull; value v; last_vm = vm; v.value=TRUE; v.type=7; v.flags=0x0500; lvalue_ref *lv_source=ot_get_next_lvalue_arg(vm, &isnull); value *v_source = get_lvalue(vm, lv_source); if (v_source->flags&IS_NULL) v.value=FALSE; if (v.value){ int source_len=0; wchar_t *dest_ptr=NULL; int realloc_size; int buff_size = pbstg_sz(vm,(void *)v_source->value); const int count = arg_count -1; wchar_t **strings = (wchar_t**)_alloca(sizeof(wchar_t**) * count); int *string_lengths = (int *)_alloca(sizeof(int) * count); int append_len=0; for (int i=0;i<count;i++){ strings[i] = (wchar_t *)ot_get_valptr_arg(vm, &isnull); if (isnull || !*strings[i]){ string_lengths[i]=0; continue; } string_lengths[i] = wcslen(strings[i]); append_len+=string_lengths[i]*2; } if (v_source->type==pbvalue_string){ dest_ptr = (wchar_t *)v_source->value; if (*dest_ptr){ if (buff_size>=32){ if (*(unsigned short*)(v_source->value + buff_size - 6)==0xABCD){ source_len = *(int*)(v_source->value + buff_size - 4); if (*(unsigned short*)(v_source->value + source_len+2)!=0xABCD) source_len=0; } } if (source_len==0){ source_len = wcslen(dest_ptr)*2; } } realloc_size = source_len + append_len + 10; }else{ source_len = ((blob *)v_source->value)->len+2; if (source_len<4 || *(wchar_t*)(v_source->value + source_len)) source_len+=2; realloc_size = source_len + append_len + 2; } if (realloc_size>6144){ // round up to the nearest 4k realloc_size|=0xFFF; } if (realloc_size > buff_size){ v_source->value = (DWORD)pbstg_realc(vm, (void *)v_source->value, realloc_size, GET_HEAP(vm)); buff_size = pbstg_sz(vm,(void *)v_source->value); } dest_ptr = (wchar_t *)(v_source->value + source_len); for (int i=0;i<count;i++){ if (string_lengths[i]==0) continue; if (strings[i] == dest_ptr){ // if you're trying to append the source string onto the source string, it may have just moved. // (that's also why wcsncpy is used below, or we'd keep appending the string till we hit an invalid page of memory) strings[i] = (wchar_t *)v_source->value; } wcsncpy(dest_ptr, strings[i], string_lengths[i]); dest_ptr+=string_lengths[i]; } *dest_ptr=0; if (v_source->type==pbvalue_string){ if (buff_size>=32){ *(unsigned short*)(v_source->value+source_len+append_len+2)=0xABCD; *(unsigned short*)(v_source->value+buff_size-6)=0xABCD; *(int*)(v_source->value+buff_size-4)=source_len + append_len; } }else{ ((blob *)v_source->value)->len = source_len+append_len -2; } } ot_set_return_val(vm, &v); return 1; } // string value_info(readonly any) DWORD __declspec(dllexport) __stdcall Value_Info (vm_state *vm, DWORD arg_count){ // DWORD isnull; value v; wchar_t info[256]; last_vm = vm; value *v_value = ot_get_next_evaled_arg_no_convert(vm); DWORD len=0; DWORD size=0; if (!(v_value->flags&(IS_NULL|IS_ARRAY))){ if (v_value->type==pbvalue_string){ len = (wcslen((wchar_t*)v_value->value)+1)*2; }else if (v_value->type==pbvalue_blob){ len = ((blob*)v_value->value)->len + 4; } if (len>0) size = pbstg_sz(vm,(void *)v_value->value); } wnsprintf(info, 256, L"Type: %d, Flags: %u, Value: %d, UsedLen: %d, BuffSize %d",v_value->type,v_value->flags, v_value->value, len, size); len=wcslen(info); wchar_t *dest = (wchar_t *)pbstg_alc(vm, (len+1)*2, GET_HEAP(vm)); wcscpy(dest,info); v.value=(DWORD)dest; v.flags=0x0d00; v.type=pbvalue_string; ot_set_return_val(vm, &v); return 1; } BOOL APIENTRY DllMain( HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved ) { switch (ul_reason_for_call) { case DLL_PROCESS_ATTACH: Install_Crash_Hook(); break; case DLL_THREAD_ATTACH: break; case DLL_THREAD_DETACH: break; case DLL_PROCESS_DETACH: Uninstall_Crash_Hook(); break; } return TRUE; }
File: dllmain.cpp
Size: 22131
Date: Tue, 08 May 2012 23:13:40 +0200
Type: cpp
Size: 22131
Date: Tue, 08 May 2012 23:13:40 +0200
Type: cpp