Skip to content

Commit 6c5a990

Browse files
author
lixungeng
committed
Add weixin 4.0 OCR support
1 parent de82245 commit 6c5a990

23 files changed

+11138
-6362
lines changed

README.md

Lines changed: 31 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,48 @@
11
# wcocr: demonstrate how to use WeChatOCR.exe
22

33
Great thanks to IEEE by his [Project IEEE/QQImpl](https://github.com/EEEEhex/qqimpl)] and [article](https://bbs.kanxue.com/thread-278161.htm).
4-
This project is based on it.
4+
This project is based on it and reduced the product size by using `protobuf-lite` instead of `protobuf`.
55

6-
This project reduced the product size by using `protobuf-lite` instead of `protobuf`,
7-
and provided a direct Python interface for calling in sync mode.
6+
This project provided a direct Python interface for calling in sync mode as well as other languages support including but not limited with c++/java/c#.
87

9-
To use this project, you need to prepare the `wechatocr.exe` and the wechat folder.
10-
For example, `wechatocr.exe` might be:
8+
# Prepare for usage
9+
10+
To work with this project, you need to prepare the wechat OCR binary and the wechat runtime folder.
11+
12+
For wechat 3.x, the wechat OCR binary is `wechatocr.exe`, it might be:
1113

1214
```
1315
C:\Users\yourname\AppData\Roaming\Tencent\WeChat\XPlugin\Plugins\WeChatOCR\7061\extracted\WeChatOCR.exe
1416
```
15-
and the wechat folder might be:
17+
and the wechat runtime folder might be:
1618
```
1719
C:\Program Files (x86)\Tencent\WeChat\[3.9.8.25]
1820
```
1921

22+
**Wechat 4.0 is now supported!**
23+
24+
For wechat 4.0, the wechat OCR binary is `wxocr.dll`, it might be:
25+
26+
```
27+
C:\Users\yourname\AppData\Roaming\Tencent\xwechat\XPlugin\plugins\WeChatOcr\8011\extracted\wxocr.dll
28+
```
29+
30+
and the wechat runtime folder might be:
31+
32+
```
33+
C:\Program Files\Tencent\Weixin\4.0.0.26
34+
```
35+
36+
## Warning
37+
38+
Wechat 4.0 OCR binary is `wxocr.dll`, but this project built a dll named `wcocr.dll`
39+
40+
**Their names are similar, DO NOT confuse them.**
41+
42+
43+
2044
## C++ interface
45+
2146
You can use the following code to test it:
2247
```cpp
2348
CWeChatOCR ocr(wechatocr_path, wechat_path);

compile-proto.bat

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
@echo off
2+
cd /d %~dp0
3+
cd src
4+
..\spt\protoc.exe --cpp_out=lite:. -I ../pb ocr_common.proto ocr_wx3.proto ocr_wx4.proto
5+
pause

java/Test.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ interface SetResCallback extends Callback {
1818
public static void main(String [] args) {
1919
System.out.println("ocr begin...");
2020
String ocr_exe = "C:\\Users\\xungeng\\AppData\\Roaming\\Tencent\\WeChat\\XPlugin\\Plugins\\WeChatOCR\\7079\\extracted\\WeChatOCR.exe";
21-
String wechat_dir = "C:\\Program Files (x86)\\Tencent\\WeChat\\[3.9.10.19]";
21+
String wechat_dir = "C:\\Program Files\\Tencent\\Weixin\\4.0.0.26";
2222
String tstpng = "test.png";
2323
AtomicReference<String> result = new AtomicReference<>();
2424
WechatOCR.dll.wechat_ocr(new WString(ocr_exe), new WString(wechat_dir), tstpng, new WechatOCR.SetResCallback() {

pb/ocr_common.proto

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
syntax = "proto3";
2+
package ocr_common;
3+
4+
message Point{
5+
optional float x = 1;
6+
optional float y = 2;
7+
}
8+
9+
message Box{
10+
optional Point topleft = 1;
11+
optional Point topright = 2;
12+
optional Point bottomright = 3;
13+
optional Point bottomleft = 4;
14+
}
15+
16+
message OCRResultChar{
17+
optional Box char_box = 1;
18+
optional string chars = 2;
19+
}
20+
21+
message OCRResultLine {
22+
Box line_box = 1;
23+
string text = 2; //UTF8格式的字符串
24+
float rate = 3; //单行的识别率
25+
repeated OCRResultChar blocks = 4;
26+
float left = 5; //识别矩形的left\top\right\bottom的坐标
27+
float top = 6;
28+
float right = 7;
29+
float bottom = 8;
30+
optional bool unknown_0 = 9; //未知
31+
optional Box box10 = 10; //未知
32+
}

pb/ocr_protobuf.proto

Lines changed: 0 additions & 65 deletions
This file was deleted.

pb/ocr_wx3.proto

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
syntax = "proto3";
2+
package wx3;
3+
import "ocr_common.proto";
4+
5+
message OcrInputBuffer {
6+
// 看起来有两种传递图片的方式。
7+
// 第一种是只传文件路径 pic_path = "C:/path/to/xxx.png"
8+
// 第二种是用字节流传,猜测需要用到u2,u3,pic_data变量。暂时微信没有用到,可能是为将来保留
9+
optional string pic_path = 1;
10+
optional uint32 u2 = 2;
11+
optional uint32 u3 = 3;
12+
optional bytes pic_data = 4;
13+
}
14+
15+
message OcrOutputBuffer {
16+
repeated ocr_common.OCRResultLine lines = 1; //repeated 每行的结果
17+
optional uint32 img_width = 2;
18+
optional uint32 img_height = 3;
19+
optional string unk4 = 4;
20+
}
21+
22+
message OcrRespond {
23+
optional int32 type = 1; // type=1像是初始化成功回调。如果是正常OCR请求,回答的type=0
24+
optional uint64 task_id = 2;
25+
optional int32 err_code = 3;
26+
optional OcrOutputBuffer ocr_result = 4;
27+
}
28+
29+
message OcrRequest {
30+
int32 type = 1; //为0执行ocr,为1会直接返回init信息. 与OcrRespond.type意义相同
31+
// 经过反复核查,在腾讯proto文件中,这个task_id确实是64位的。但在,在执行过程中,高32位会被丢弃,且第32位为1会出错。
32+
// 也就是协议上是有64位的uint64,实际上只能有31位。必须是>0的整形数字,范围是[1,2147483647]
33+
// 由于 task_id = 1会被用于初始化,所以最好取值为 [2, 0x7fffFFFF]
34+
uint64 task_id = 2;
35+
OcrInputBuffer input = 3;
36+
}

pb/ocr_wx4.proto

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
syntax = "proto3";
2+
package wx4;
3+
import "ocr_common.proto";
4+
5+
message OCRSupportMessage {
6+
optional bool supported = 1;
7+
}
8+
9+
message ReqType {
10+
optional bool t1 = 1;
11+
optional bool t2 = 2;
12+
optional bool t3 = 3;
13+
}
14+
15+
message ParseOCRReqMessage {
16+
optional uint64 task_id = 1;
17+
optional string pic_path = 2;
18+
optional uint32 xx3 = 3;
19+
optional uint32 xx4 = 4;
20+
optional bytes pic_data = 5;
21+
optional ReqType rt = 6;
22+
}
23+
24+
message OCRResultInfo {
25+
repeated ocr_common.OCRResultLine lines = 3;
26+
optional uint32 img_width = 4;
27+
optional uint32 img_height = 5;
28+
optional string cpu_report = 6;
29+
optional uint64 time_used = 7;
30+
}
31+
32+
message QRResultInfo {
33+
}
34+
message MMFGResultInfo {
35+
}
36+
37+
message ParseOCRRespMessage {
38+
optional uint64 task_id = 1;
39+
optional int32 err_code = 2;
40+
optional OCRResultInfo res = 3;
41+
optional ReqType rt = 4;
42+
optional bytes qrcode = 5; // ¶þάÂëʶ±ð
43+
optional bytes mmfg = 6; // what is mmfg?
44+
}

src/main.cpp

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,8 @@ bool wechat_ocr(const wchar_t* ocr_exe, const wchar_t * wechat_dir, const char *
4747
return false;
4848
}
4949
CWeChatOCR::result_t res;
50-
wechat_ocr.doOCR(imgfn, &res);
50+
if (!wechat_ocr.doOCR(imgfn, &res))
51+
return false;
5152
string json;
5253
json += "{";
5354
json += "\"errcode\":" + std::to_string(res.errcode) + ",";
@@ -95,10 +96,12 @@ HRESULT DllRegisterServer(void)
9596

9697
CWeChatOCR wechat_ocr(_wgetenv(L"WECHATOCR_EXE"), _wgetenv(L"WECHAT_DIR"));
9798
if (!wechat_ocr.wait_connection(5000)) {
99+
fprintf(stderr, "wechat_ocr.wait_connection failed\n");
98100
return E_FAIL;
99101
}
100102
wechat_ocr.doOCR(getenv("TEST_PNG"), nullptr);
101103
wechat_ocr.wait_done(-1);
104+
fprintf(stderr, "debug play ocr DONE!\n");
102105
return S_OK;
103106
}
104107
#endif

src/mojocall.cpp

Lines changed: 42 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,12 @@
22
#include "mojocall.h"
33
#include "mmmojo.h"
44

5+
#ifdef _DEBUG
6+
#define DBG_PRINT(fmt, ...) fprintf(stderr, "[DEBUG]" fmt, __VA_ARGS__)
7+
#else
8+
#define DBG_PRINT(fmt, ...)
9+
#endif
10+
511
CMojoCall::~CMojoCall()
612
{
713
Stop();
@@ -46,30 +52,38 @@ bool CMojoCall::Start(LPCWSTR exepath)
4652
//设置回调函数
4753
SetMMMojoEnvironmentCallbacks(env, MMMojoEnvironmentCallbackType::kMMUserData, this);
4854
void (*ReadOnPush)(uint32_t request_id, const void* request_info, void* user_data) = [](uint32_t request_id, const void* request_info, void* user_data) {
55+
DBG_PRINT("ReadOnPush: %u\n", request_id);
4956
return ((CMojoCall*)user_data)->ReadOnPush(request_id, request_info);
50-
};
57+
};
5158
void (*ReadOnPull)(uint32_t request_id, const void* request_info, void* user_data) = [](uint32_t request_id, const void* request_info, void* user_data) {
59+
DBG_PRINT("ReadOnPull: %u\n", request_id);
5260
return ((CMojoCall*)user_data)->ReadOnPull(request_id, request_info);
53-
};
61+
};
5462
void (*ReadOnShared)(uint32_t request_id, const void* request_info, void* user_data) = [](uint32_t request_id, const void* request_info, void* user_data) {
63+
DBG_PRINT("ReadOnShared: %u\n", request_id);
5564
return ((CMojoCall*)user_data)->ReadOnShared(request_id, request_info);
56-
};
65+
};
5766

5867
void (*OnConnect)(bool is_connected, void* user_data) = [](bool is_connected, void* user_data) {
68+
DBG_PRINT("OnConnect: %d\n", is_connected);
5969
return ((CMojoCall*)user_data)->OnRemoteConnect(is_connected);
60-
};
70+
};
6171
void (*OnDisConnect)(void* user_data) = [](void* user_data) {
72+
DBG_PRINT("OnDisConnect\n");
6273
return ((CMojoCall*)user_data)->OnRemoteDisConnect();
63-
};
74+
};
6475
void (*OnProcessLaunched)(void* user_data) = [](void* user_data) {
76+
DBG_PRINT("OnProcessLaunched\n");
6577
return ((CMojoCall*)user_data)->OnRemoteProcessLaunched();
66-
};
78+
};
6779
void (*OnProcessLaunchFailed)(int error_code, void* user_data) = [](int error_code, void* user_data) {
80+
DBG_PRINT("OnProcessLaunchFailed: %d\n", error_code);
6881
return ((CMojoCall*)user_data)->OnRemoteProcessLaunchFailed(error_code);
69-
};
82+
};
7083
void (*OnError)(const void* errorbuf, int errorsize, void* user_data) = [](const void* errorbuf, int errorsize, void* user_data) {
84+
DBG_PRINT("OnError: %.*s\n", errorsize, (const char*)errorbuf);
7185
return ((CMojoCall*)user_data)->OnRemoteError(errorbuf, errorsize);
72-
};
86+
};
7387

7488
SetMMMojoEnvironmentCallbacks(env, MMMojoEnvironmentCallbackType::kMMReadPush, ReadOnPush);
7589
SetMMMojoEnvironmentCallbacks(env, MMMojoEnvironmentCallbackType::kMMReadPull, ReadOnPull);
@@ -123,30 +137,39 @@ bool CMojoCall::SendPbSerializedData(const void* pb_data, size_t data_size, int
123137

124138
void CMojoCall::OnRemoteConnect(bool is_connected)
125139
{
126-
std::lock_guard<std::mutex> lock(m_mutex_conn);
127-
m_connected = is_connected;
128-
m_cv_conn.notify_all();
140+
std::lock_guard<std::mutex> lock(m_mutex_state);
141+
m_state = is_connected ? MJC_CONNECTED : MJC_FAILED;
142+
m_cv_state.notify_all();
129143
}
130144

131145
void CMojoCall::OnRemoteDisConnect() {
132-
std::lock_guard<std::mutex> lock(m_mutex_conn);
133-
m_connected = false;
134-
m_cv_conn.notify_all();
146+
std::lock_guard<std::mutex> lock(m_mutex_state);
147+
m_state = MJC_FAILED;
148+
m_cv_state.notify_all();
135149
}
136150

137151
bool CMojoCall::wait_connection(int timeout)
138152
{
139153
if (timeout < 0) {
140-
std::unique_lock<std::mutex> lock(m_mutex_conn);
141-
m_cv_conn.wait(lock, [this] {return m_connected; });
154+
std::unique_lock<std::mutex> lock(m_mutex_state);
155+
m_cv_state.wait(lock, [this] {return m_state != MJC_PENDING; });
142156
}
143157
else
144158
{
145-
std::unique_lock<std::mutex> lock(m_mutex_conn);
146-
if (!m_cv_conn.wait_for(lock, std::chrono::milliseconds(timeout), [this] {return m_connected; }))
159+
std::unique_lock<std::mutex> lock(m_mutex_state);
160+
if (!m_cv_state.wait_for(lock, std::chrono::milliseconds(timeout), [this] {return m_state != MJC_PENDING; }))
147161
{
148162
return false;
149163
}
150164
}
151-
return m_connected;
165+
return m_state >= MJC_CONNECTED;
152166
}
167+
168+
void CMojoCall::OnRemoteProcessLaunchFailed(int error_code)
169+
{
170+
std::lock_guard<std::mutex> lock(m_mutex_state);
171+
if (m_state == MJC_PENDING) {
172+
m_state = MJC_FAILED;
173+
m_cv_state.notify_all();
174+
}
175+
}

0 commit comments

Comments
 (0)