其实就是IFELanguage接口的应用
因为VB6里不直接支持这个接口,所以需要调用它,只能自己写tlb、动态调用、自己实现接口
写tlb当然可以,需要一点c基础,但是毕竟相对困难
动态调用,就是使用DispCallFunc或者手撸com的方法来调用
这里借用t.tui写好的应用汉字转拼音类来讲
从相关文档我们可以看到IFELanguage接口的vTable如下:
Enum pIFELanguageVTable
pQueryInterface = 0
pAddRef = 4
pRelease = 8
pOpen = 12
pClose = 16
pGetJorphResult = 20
pGetConversationModeCaps = 24
pGetPhonetic = 28
pGetConversion = 32
End Enum
动态调用接口最大的问题是内存对齐
在VB6/VBA程序读取内存时,默认是4字节对齐的,在tlb里可以强制修改对齐约定(tlb还有这个好处,哈哈)
但是VB6/VBA代码里是做不到的,所以我们在调用的时候,要特别注意这点
IFELanguage里两个核心的结构体如下:
typedef struct tagMORRSLT {
DWORD dwSize; // total size of this block.
WCHAR *pwchOutput; // conversion result string.
WORD cchOutput; // lengh of result string.
union {
WCHAR *pwchRead; // reading string
WCHAR *pwchComp;
};
union {
WORD cchRead; // length of reading string.
WORD cchComp;
};
WORD *pchInputPos; // index array of reading to input character.
WORD *pchOutputIdxWDD; // index array of output character to WDD
union {
WORD *pchReadIdxWDD; // index array of reading character to WDD
WORD *pchCompIdxWDD;
};
WORD *paMonoRubyPos; // array of position of monoruby
WDD *pWDD; // pointer to array of WDD
INT cWDD; // number of WDD
VOID *pPrivate; // pointer of private data area
WCHAR BLKBuff[]; // area for stored above members.
// WCHAR wchOutput[cchOutput];
// WCHAR wchRead[cchRead];
// CHAR chInputIdx[cwchInput];
// CHAR chOutputIdx[cchOutput];
// CHAR chReadIndx[cchRead];
// ???? Private
// WDD WDDBlk[cWDD];
}MORRSLT;
.
typedef struct tagWDD{
WORD wDispPos; // Offset of Output string
union {
WORD wReadPos; // Offset of Reading string
WORD wCompPos;
};
WORD cchDisp; //number of ptchDisp
union {
WORD cchRead; //number of ptchRead
WORD cchComp;
};
DWORD WDD_nReserve1; //reserved
WORD nPos; //part of speech
// implementation-defined
WORD fPhrase : 1;//start of phrase
WORD fAutoCorrect: 1;//auto-corrected
WORD fNumericPrefix: 1;//kansu-shi expansion(JPN)
WORD fUserRegistered: 1;//from user dictionary
WORD fUnknown: 1;//unknown word (duplicated information with nPos.)
WORD fRecentUsed: 1; //used recently flag
WORD :10; //
VOID *pReserved; //points directly to WORDITEM
} WDD;
翻译成VB6如下:
Private Type MORRSLT
dwSize As Long '4 存储结构长度
pwchOutput As Long '4 转换结果字符串指针,你转拼音就是拼音
cchOutput As Integer '2 转换结果字符串长度
pwchReadComp As Long '4 输入或对比字符串指针
cchReadComp As Integer '2 输入或对比字符串长度
pchInputPos As Long '4
pchOutputIdxWDD As Long '4
pchReadIdxWDD As Long '4
paMonoRubyPos As Long '4
pWDD As Long '4 WDD数组指针
cWDD As Integer '2 WDD结构的个数
pPrivate As Long '4 基本上都是零,这个声明不能删除
'BLKBuff As Long '4 这个没有存在的意义,只是个存储数据的缓冲区
End Type
.
Type WDD
wDispPos As Integer '输出字串偏移
wReadPos_CompPos As Integer '读取或比较字串的偏移
cchDisp As Integer '
cchRead_Comp As Integer
WDD_nReserve1 As Long
nPos As Integer '
fFlags As Integer '标志位,意思具体看上面
pReserved As Long '保留指针,指向WORDITEM
End Type
以上就是正常翻译过来的结构体声明,但是在实际使用中很明显是不能这样使用的
为什么呢,就是前面说的内存对齐,我们来看t.tui是怎么处理的:
Private Type VB_MORRSLT
dwSize As Long '4
pwchOutput As Long '4
cchOutput As Integer '2+(2),VBA内存对齐闹得,折腾了好一阵才确认问题所在,唉
Block1 As Long '4 如果要用这个,需要在使用时修正,其直接跳过了一个WORD
pchInputPos As Long '4
pchOutputIdxWDD As Long '4
pchReadIdxWDD As Long '4
paMonoRubyPos As Long '4
pWDD As Long '4
cWDD As Integer '2
pPrivate As Long '4 这个虽然没用,但是不能删除
'BLKBuff As Long '4 这个没有存在的意义,只是个存储数据的缓冲区
End Type
以上结构,除了第3个元素做了特殊处理以外,其他跟我上面的翻译一毛一样。
但是因为这么处理就干掉了原结构体中的4个元素,虽然我们暂时没用到。
为什么会干掉呢,简单解释一下,如下图:
内存对齐
4字节对齐的意思就是如右侧黄色标记所示,总是一次读取4个字节
每次读取的起点必然是0,4,8,C(16进制)
读取出来后,存到成员里时,成员只有2个字节,就只存储了低位,高位数据就丢失了。
黄色部分,就把左侧的4个元素都干掉了,当然这里问题不大,因为在转拼音的应用里,压根儿不需要这4个成员。
(老实说,目前我并不知道这4个成员是干啥的)
#############我是分割线#############
当然,我就是来提解决方案的,以WDD为例:
我们可以直接把这个结构体简化:
Enum WDDIndex
wDispPos = 1
wReadCompPos = 2
cchDisp = 3
cchReadComp = 4
WDD_nReserve1 = 5 'DWORD
nPos = 7
fFlags = 8
pReserved = 9 'DWORD
End Enum
Private Type VB_WDD
Data(1 To 10) As Integer
End Type
这样复杂的WDD结构体改成了一个数组成员,这样内存读取的时候就只需要读取一次,然后因为是数组,也不会发生错位的情况,哈哈。
这里数组的成员为什么用Integer,没有为什么,只是在我们如果需要分词的话,那两个Long的成员刚好用不上,而其他成员又都是Integer,我们就偷懒采用了Integer数组了。
实际使用时,根据结构体的实际情况来选定数据数组的类型,如下是使用Byte数组的情况:
Enum myIndex
index1 = 1
index2 = 2
index3 = 3
index4 = 5
index5 = 7
End Enum
Private Type MyType
Data(1 To 10) As Byte
End Type
使用第1个元素,直接myType.Data(1)即可
使用第3个元素,需要MakeInteger(myType.Data(3), myType.Data(4))
使用第5个元素,需要MakeLong(……) '有点长,省略了,哈哈
.
Public Function HiByte(ByVal w As Integer) As Byte
Dim hi As Integer
If w And &H8000 Then hi = &H4000
HiByte = (w And &H7FFE) 256
HiByte = (HiByte Or (hi 128))
End Function
Public Function LoByte(w As Integer) As Byte
LoByte = w And &HFF
End Function
Public Function HiWord(dw As Long) As Integer
If dw And &H80000000 Then
HiWord = (dw 65535) - 1
Else
HiWord = dw 65535
End If
End Function
Public Function LoWord(dw As Long) As Integer
If dw And &H8000& Then
LoWord = &H8000 Or (dw And &H7FFF&)
Else
LoWord = dw And &HFFFF&
End If
End Function
Public Function MakeInteger(ByVal LoByte As Byte, ByVal HiByte As Byte) As Integer
MakeInteger= ((HiByte * &H100) + LoByte)
End Function
Public Function MakeLong(ByVal LoWord As Integer, ByVal HiWord As Integer) As Long
MakeLong = ((HiWord * &H10000) + LoWord)
End Function
当然了,你还可以利用读取规则,来使用多个数组,只是这样就又需要多个偏移枚举了。