诡异的dll(2)

还是昨天,还是同一个哥们。他要实现一个语音叫号的功能,要先普通话叫号,然后再粤语叫号。

诡异的事情发生了,在调试状态下,怎么运行都是正常的,
但直接双击运行,只能叫第一种声音(粤语或普通话),另一个声音(粤语或普通话)怎么都发不出来。
悲剧的是,他语音叫号也是调用了一个dll。

只好慢慢调试了,开始以为是线程退出的太快,或者变量作用域问题,或变量中繁体中文乱码问题,或者函数退出太快,或者是release时优化掉了…

弄了一个多小时,没有任何进展。无奈之下,我翻出了Process Explorer,去查看环境变量。
结果调试的时候,第一个环境变量是

__COMPAT_LAYER=WinXpsp3

天啊,这不是兼容模式吗。
于是将exe设成兼容模式,双击运行,就好了。

原来,他用BCB6做开发,在Win7 sp1上无法正常运行,就设成了兼容模式,调试时运行的exe当然也继承了兼容模式的环境变量咯。
吐血啊……

回家,搜了一下__COMPAT_LAYER环境变量,可以设置为以下数值:

兼容性:

兼容模式
WIN95 Windows 95
WIN98 Windows 98 / Windows Me
NT4SP5 Windows NT 4.0 (Service Pack 5)
WIN2000 Windows 2000
WINXP Windows XP
WINXPSP2 Windows XP (Service Pack 2)
WINXPSP3 Windows XP (Service Pack 3)
WINSRV03SP1 Windows Server 2003 (Service Pack 1)
WINSRV08SP1 Windows Server 2008 (Service Pack 1)
VISTARTM Windows Vista
VISTASP1 Windows Vista (Service Pack 1)
VISTASP2 Windows Vista (Service Pack 2)
WIN7RTM Windows 7

权限:

权限
RUNASADMIN 以管理员权限运行
RUNASINVOKER 以调用者权限运行

显示模式:

显示模式
DISABLETHEMES 禁用视觉主题
640X480 用640×480屏幕分辨率运行
HIGHDPIAWARE 高DPI设置时禁用显示缩放
256COLOR 用256色运行
DISABLEDWM 禁用桌面元素

另外,也可以通过注册表,设置程序的兼容模式:

Windows Registry Editor Version 5.00

[HKEY_CURRENT_USER\Software\Microsoft\Windows NT\CurrentVersion\AppCompatFlags\Layers]
"D:\\TEST\\Hello.exe"="WINXPSP3"
    HKEY hKey;
    LPCTSTR strSubKey = "Software\\Microsoft\\Windows NT\\CurrentVersion\\AppCompatFlags\\Layers";
    long lRet = ::RegOpenKeyEx( HKEY_CURRENT_USER, strSubKey, 0, KEY_WRITE, &hKey );
    if ( lRet == ERROR_SUCCESS )
    {
        TCHAR achValue[] = { _T("WINXPSP3") };
        CString strExePath = _T("D:\\Test\\Hello.exe");
        lRet  = ::RegSetValueEx( hKey, strExePath, NULL, REG_SZ, (LPBYTE)&achValue, sizeof(achValue) );
        RegCloseKey( hKey );
    } 

诡异的dll(1)

昨天,和哥们一起调试程序。由于上线压力较大,他最近一直在狂改程序,但遇到了一个问题:

从PACS Server取影像时,怎么都取不回来。

首先排查了PACS Server设置,发现稍微有些问题,修改配置后,我以前写的一个测试程序,
就可以正常取回影像了。

但诡异的事情发生了,我写的程序,可以查,可以取,但他写的就是无法取回影像,只能查询。

吐血啊。

经过了一个小时的奋战,实在发现不了问题,他决定把无线网卡禁用后再测一下,
结果就通了~~

原来他取影像用的是一个dll,估计这个dll中,启用监听的时候,会自动选用某一块网卡,
但刚好选了无线网卡,悲剧啊。

能不能不要自作主张选网卡啊~~
能不能调试的时候,先把不需要的网卡禁用了啊~~

汉字繁简体互换

1、chs2cht.h

#pragma once
char* UnicodeToBIG5(const wchar_t* szUnicodeString);
char* UnicodeToGB2312(const wchar_t* szUnicodeString);
wchar_t* GB2312ToUnicode(const char* szGBString);
char* GB2312ToBIG5(const char* szGBString);

2、chs2cht.cpp

#include "chs2cht.h"
#include <atlstr.h>
//GB2312 转 Unicode:
wchar_t* GB2312ToUnicode(const char* szGBString)  
{  
	UINT nCodePage = 936; //GB2312  
	int nLength=MultiByteToWideChar(nCodePage,0,szGBString,-1,NULL,0);  
	wchar_t* pBuffer = new wchar_t[nLength+1];  
	MultiByteToWideChar(nCodePage,0,szGBString,-1,pBuffer,nLength);  
	pBuffer[nLength]=0;  
	return pBuffer;  
}

//BIG5 转 Unicode:  
wchar_t* BIG5ToUnicode(const char* szBIG5String)  
{  
	UINT nCodePage = 950; //BIG5  
	int nLength=MultiByteToWideChar(nCodePage,0,szBIG5String,-1,NULL,0);  
	wchar_t* pBuffer = new wchar_t[nLength+1];  
	MultiByteToWideChar(nCodePage,0,szBIG5String,-1,pBuffer,nLength);  
	pBuffer[nLength]=0;  
	return pBuffer;  
}

//Unicode 转 GB2312:  
char* UnicodeToGB2312(const wchar_t* szUnicodeString)  
{  
	UINT nCodePage = 936; //GB2312  
	int nLength=WideCharToMultiByte(nCodePage,0,szUnicodeString,-1,NULL,0,NULL,NULL);  
	char* pBuffer=new char[nLength+1];  
	WideCharToMultiByte(nCodePage,0,szUnicodeString,-1,pBuffer,nLength,NULL,NULL);  
	pBuffer[nLength]=0;  
	return pBuffer;  
}

//Unicode 转 BIG5:  
char* UnicodeToBIG5(const wchar_t* szUnicodeString)  
{  
	UINT nCodePage = 950; //BIG5  
	int nLength=WideCharToMultiByte(nCodePage,0,szUnicodeString,-1,NULL,0,NULL,NULL);  
	char* pBuffer=new char[nLength+1];  
	WideCharToMultiByte(nCodePage,0,szUnicodeString,-1,pBuffer,nLength,NULL,NULL);  
	pBuffer[nLength]=0;  
	return pBuffer;  
}

//繁体中文BIG5 转 简体中文GB2312  
char* BIG5ToGB2312(const char* szBIG5String)  
{  
	LCID lcid = MAKELCID(MAKELANGID(LANG_CHINESE,SUBLANG_CHINESE_SIMPLIFIED),SORT_CHINESE_PRC);  
	wchar_t* szUnicodeBuff = BIG5ToUnicode(szBIG5String);  
	char* szGB2312Buff = UnicodeToGB2312(szUnicodeBuff);  
	int nLength = LCMapStringA(lcid,LCMAP_SIMPLIFIED_CHINESE, (LPCSTR)szGB2312Buff,-1,NULL,0);  
	char* pBuffer = new char[nLength + 1];  
	LCMapStringA(0x0804,LCMAP_SIMPLIFIED_CHINESE,(LPCSTR)szGB2312Buff,-1,pBuffer,nLength);  
	pBuffer[nLength] = 0;  

	delete[] szUnicodeBuff;  
	delete[] szGB2312Buff;  
	return pBuffer;  
}

//简体中文GB2312 转 繁体中文BIG5  
char* GB2312ToBIG5(const char* szGBString)  
{  
	LCID lcid = MAKELCID(MAKELANGID(LANG_CHINESE,SUBLANG_CHINESE_SIMPLIFIED),SORT_CHINESE_PRC);  
	int nLength = LCMapStringA(lcid,LCMAP_TRADITIONAL_CHINESE,szGBString,-1,NULL,0);  
	char* pBuffer=new char[nLength+1];  
	LCMapStringA(lcid,LCMAP_TRADITIONAL_CHINESE,szGBString,-1,pBuffer,nLength);  
	pBuffer[nLength]=0;  
	wchar_t* pUnicodeBuff = GB2312ToUnicode(pBuffer);  
	char* pBIG5Buff = UnicodeToBIG5(pUnicodeBuff);  
	delete[] pBuffer;  
	delete[] pUnicodeBuff;  
	return pBIG5Buff;  
}

3、test.cpp

#include "chs2cht.h"
#include <iostream> 
#include <locale.h>
#include <atlstr.h>  

using namespace std;

int main(int argc, char** argv)
{
    //“区域设置”为简体中文 
    locale loc( "chs" ); 
    char str[100];  
    cin>>str;  
    
    char * rlt=GB2312ToBIG5(str);  
    CString cStr1;   
    cStr1.Format( TEXT("%s"),rlt); 

    //“区域设置”为繁体中文 
    setlocale(LC_ALL, ".950");  
    cout<<rlt<<endl; 

    return 0;
}

JNI简单例子2

最近又用jni写了一个编码工具,从中抽出了最简单的代码,如下:

1、Compress4j.java

package com.neohope.test.compress.jni;

/**
 *
 * @author Hansen
 */
public class Compress4j {
    private native int encodeR(String inFilePath,String outFilePaht);
    private native int decodeR(String inFilePath,String outFilePaht);
    
    static{
        try{
            System.loadLibrary("compress4j");
        }catch(UnsatisfiedLinkError e){
            //nothing to do
            System.out.println("Error: Couldn't load compress4j");
            System.out.println(e.getMessage());
            e.printStackTrace();
        }
    }
    
    public String encode(String inFilePath,String outFilePath)
    {
        encodeR(inFilePath, outFilePath);
        return "xxxxx";
    }
    
    public String decode(String inFilePath,String outFilePath)
    {
        decodeR(inFilePath, outFilePath);
        return "xxxxx";
    }
}

2、找到生成的classes文件夹,运行命令行:

javah -classpath . -jni com.neohope.test.compress.jni.Compress4j 

3、生成的.h文件如下

/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class com_neohoe_test_compress_jni_Compress4j */

#ifndef _Included_com_neohoe_test_compress_jni_Compress4j
#define _Included_com_neohoe_test_compress_jni_Compress4j
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     com_neohoe_test_compress_jni_Compress4j
 * Method:    encodeR
 * Signature: (Ljava/lang/String;Ljava/lang/String;)I
 */
JNIEXPORT jint JNICALL Java_com_neohoe_test_compress_jni_Compress4j_encodeR
  (JNIEnv *, jobject, jstring, jstring);

/*
 * Class:     com_neohoe_test_compress_jni_Compress4j
 * Method:    decodeR
 * Signature: (Ljava/lang/String;Ljava/lang/String;)I
 */
JNIEXPORT jint JNICALL Java_com_neohoe_test_compress_jni_Compress4j_decodeR
  (JNIEnv *, jobject, jstring, jstring);

#ifdef __cplusplus
}
#endif
#endif

3、编写成的.cc文件如下

#include "com_neohoe_test_compress_jni_Compress4j.h"

JNIEXPORT jint JNICALL Java_com_neohoe_test_compress_jni_Compress4j_encodeR
  (JNIEnv *env, jobject, jstring inpathjs, jstring outpathjs)
{
    //first convert jstring to const char*
    const char* inpath = env->GetStringUTFChars( inpathjs, false);
    const char* outpath = env->GetStringUTFChars( outpathjs, false);

    //...

    return 0;
}

JNIEXPORT jint JNICALL Java_com_neohoe_test_compress_jni_Compress4j_decodeR
  (JNIEnv *env, jobject, jstring inpathjs, jstring outpathjs)
{
    //first convert jstring to const char*
    const char* inpath = env->GetStringUTFChars( inpathjs, false);
    const char* outpath = env->GetStringUTFChars( outpathjs, false);

    //...

    return 0;
}

4、在VS工程中,新增头文件目录
%JAVA_HOME%/include
%JAVA_HOME%/include/win32

5、编译为dll即可使用啦

6、关于mt和md的问题
编译为MT和MTd的话,用JNI是没有任何问题的
编译为MD的话,需要安装VC2010sp1可再发行包
编译为MDd的话,就比较麻烦了,手动复制依赖的dll后,我用c++调用dll可以成功,但用jni就一直提示:缺少依赖的dll

VC的链接顺序

CRT库在链接new, delete, DllMain这些符号的时候,使用的是弱外链接
MFC库同样也包含了new, delete, DllMain这几个符号,而MFC库必须在CRT库之前被链接才会成功。

如果link时出现了符号冲突,做下面的设置即可
project>properties>configuration properties>linker>input
在”Additional dependency”中依次增加 Nafxcwd.lib Libcmtd.lib
在add to “ignore specific library”中依次增加Nafxcwd.lib;Libcmtd.lib

VC服务程序调用远程资源

当VC服务程序调用远程资源时,经常返回路径不存在等问题
这是因为Windows中,服务程序以System用户登录,而不是桌面用户登录
这样就导致,虽然桌面程序已经映射网络资源,但System用户仍无法访问的问题
为了可以访问远程资源,可以调用API:WNetAddConnection2

BOOL AcessNetworkDrtive(CString szDevice,CString szDeviceName,CString szUsername,CString szPassword)
{
	DWORD dwRetVal;
	NETRESOURCE nr;

	memset(&nr, 0, sizeof (NETRESOURCE));
	nr.dwType = RESOURCETYPE_ANY;
	nr.lpLocalName = strDevice.GetBuffer(szDevice.GetLength());
	nr.lpRemoteName = szDeviceName.GetBuffer(szDeviceName.GetLength());
	nr.lpProvider = NULL;

	dwRetVal = WNetAddConnection2(&nr, szUsername, szUsername, CONNECT_UPDATE_PROFILE);

	if (dwRetVal != NO_ERROR)
	{
		CString cError;
		cError.Format(TEXT("[ERROR]WNetAddConnection2 Failed: %u\n"), dwRetVal);
		LogEvent(cError,TEXT("With remote name "),nr.lpRemoteName);
		return dwRetVal;
	}

	return 	dwRetVal;
}

CPP类型转换

1、static_cast

这是应该首先选用的转换方式,一般用于在相关类型间进行转换或在继承链中进行转换。
其运作方式类似于隐式转换 (比如int转为float,或指针转换为 void*), 而且它可以调用显示转换(当然包括隐式转换)。

在很多情况下, 显示声明static_cast是不必要的,
但有一点需要指出的是应当尽量避免type(XXX) 与(type)XXX 语法(详情见下文)。
然而type(XXX,YYY) 是安全的,而且一定会去调用构造函数。

static_cast也可以在父类及子类之间进行。 当子类向父类转换时是可以顺利进行的。但父类向子类转换时,只要子类没有继承virtual父类,就可以一直进行。但是它从父类转为子类时,不会做任何检查,即使类型不完全匹配。

2、const_cast

用来向一个变量增加或删除const属性。 其他任何C++ casts都无法完成此任务。
需要指出的是,当变量为const,该操作符是未定义的。

用它来声明一个非const类型的const引用是安全的。
当重载const类型的类成员变量时,该转换十分有用。
它也可以用来把一个对象增加const属性,比如调用重载的方法。

const_cast也适用于volatile,但很少见。

3、dynamic_cast

一般用来解决多态问题。你可以把指针或参考转换为任何继承链上的类型。
(一个多态类型至少用一个虚函数,无论是声明还是继承的)。
当从子类转换为父类时,dynamic_cast一定会成功。但从父类转换为子类,dynamic_cast会确保对象为子类的一个完全实现。

dynamic_cast将返回其是否可能。如果无法转换,他将返回NULL(对指针),或抛出std::bad_cast异常(对参考)。

dynamic_cast有一些限制:
如果在继承链中,同一类型有多个对象(dreaded diamond)同时你没有使用虚接口,转换不会进行。
他也只能应用于public继承,当protect或private继承时,会失败。

注意:
dynamic_cast需要编译器支持RTTI(运行时类型识别)。

4、reinterpret_cast

这是最危险的转换方式,慎用。 它直接把一种类型转换为另一种
(比如把值从一种指针转换为另一种指针,或把指针储存在int中, 或其他让人讨厌的事情)

使用reinterpret_cast,唯一能保证的是,当你从结构转回为原类型时,你能得到相同的结果。
当然,reinterpret_cast也有一些转换无法进行。

他通常用于比较奇怪的转换,或bit操作,比图把raw数据流转换为实际数据,或把数据储存在对齐指针的低字节。
当其他转换无法使用时,可以尝试reinterpret_cast

5、C-style cast
C-style cast 是使用(type)object 或 type(object)的转换方式。
C-style cast 被定义为下列转换中第一个成功返回的:
const_cast
static_cast
static_cast, then const_cast
reinterpret_cast
reinterpret_cast, then const_cast

在一些时候,他可以被用来替代其他casts,但用于他最后可能会调用reinterpret_cast(应显示调用),因此C-style cast是十分危险的。
除非你能保证,不会调用reinterpret_cast,或者reinterpret_cast一定会失败,否则不要轻易使用。即使如此,也应该考虑显示调用上面的转换。

当使用static_cast时,C-style casts 也可以忽略权限控制,这意味着他可以进行其他cast无法进行的操作。
这是避免C-style casts的一个不错的理由。

(翻译自Stack Overflow的一个帖子,水平有限,哈哈)

参考资料:
http://stackoverflow.com/questions/332030/when-should-static-cast-dynamic-cast-and-reinterpret-cast-be-used
http://www.cplusplus.com/doc/tutorial/typecasting/
http://en.wikibooks.org/wiki/C%2B%2B_Programming/Programming_Languages/C%2B%2B/Code/Statements/Variables/Type_Casting

函数调用约定

1.STDCALL
微软制定的c语言调用约定,也就是传说中的WINAPI
参数从右向左进行压栈(微软官方文档说从左向右压栈),返回值存在eax中,
被调用函数清理栈,不允许变长参数列表
函数名前增加_,函数名后增加@和参数个数(32位机器上一定被4整除)
ret指令中,有可选参数指定有多少字节需要弹栈

2.CDECL
c语言的默认调用约定
参数从右向左进行压栈,返回值存在eax中,
调用函数清理栈,可用变长参数列表
函数名前会增加_

3.FASTCALL
非跨编译器的c语言调用约定,
前2-3个4字节内的参数被存放到寄存器(edx,eax,ecx)中,其他参数,或大于4字节的参数,按从右到左的顺序被存放在栈中
多数情况下,调用函数负责清理栈
仅适用于参数少,而且实时性要求很高的情况下
函数名前增加@,函数名后增加@和参数个数

4.THISCALL
cpp的调用约定
参数从右向左进行压栈,返回值存在eax中,this指针存在ecx中
每种编译器对函数名称有不同的修饰方法,但基于重载的需要,一般都会增加参数类型和类名
当使用extern “C”的时候,cpp编译器会使用c语言的修饰方法来修饰函数名,这样就可以方便的供别人调用了。

c与cpp输出16进制,8进制及ASCII

1.c

#include <stdio.h>
int main(int argc,char **argv)
{
	for(int i=0;i<=127;i++)
	{
		printf("%X-%03o-%c\n",i,i,i);
	}
	return 0;
}

2.cpp

#include <iostream>
#include <iomanip>

using namespace std;

int main(int argc,char **argv)
{
	for(int i=0;i<=127;i++)
	{
		cout<<setw(2)<<setfill('0')<<hex<<i<<"-"<<setw(3)<<setfill('0')<<oct<<i<<"-"<<dec<<(char)i<<endl;
	}

	return 0;
}

cpp中删除一个静态局部变量?

大家都知道,static变量,内容在堆中分配的,new来的对象也是堆中分配的,
那么,如果在cpp中删除一个静态局部变量,后果是什么?
编译错误?还是运行错误?
比如下面的aTest函数,能运行吗?

#include <iostream>
using namespace std;

int aTest()
{
	static int a=0;
	int *b=&a;
	delete b;
	return a++;
}

int main(int argc,char** argv)
{
	for(int i=0;i<100;i++)
	{
		cout<<aTest()<<endl;
	}
	return 0;
}

事实是,
在gcc下,无论debug还是release,都没有问题,
在vc下,debug会报assert错误,release没有问题。
那cpp下,内存管理是谁做的?编译器为什么让它过去了呢?
其实,个人感觉,也就是上面的内存操作并不多,复杂情况下,早就挂了。