某检测场有一批扫码器,购于微光互联,型号 TX800-U,用于在不同办理窗口间扫描纸质材料上的二维码,简化录入过程。扫码器通过 USB 接入 PC 系统 (windows),自动安装驱动,接电即可使用,扫描的信息会直接输出在光标所在位置,扫码器大概长这样:

在一次 IT 系统升级后,发现它们扫描不了车辆外观检验单上的车牌二维码了,扫车架号二维码是没问题的,两者的区别就是是否带汉字,车牌第一个字符为地区标识,例如“京”,而车架号全部由数字和大写字母组成。
拿到设备后,第一时间验证了上面的问题,扫码后都有滴的一声,但是车牌号没有任何信息上屏。为了验证这个问题确实和汉字有关,找到一个制造二维码的网站 (草料二维码),造了几个不同的二维码供扫码器扫描,发现带汉字的果然扫不出来,而只要去掉汉字,就能正确上屏。
同样的二维码,通过微信扫一扫是可以得到汉字结果的,另外升级 IT 系统前扫码器也是正常的,所以初步判断这个问题和汉字编码相关,可能是升级系统后修改了默认字符编码集导致扫码器出问题了。
联系了厂家的售后,拉了个微信群,开发人员说这是已知问题,要想解决需要二次开发。
二次开发不就是调用 sdk 接口吗,这个我熟啊。从官网找到对应产品和型号:

下载了 windows 上的 C/C++ 二次开发 sdk:

另外发现一个配置工具,感觉蛮有用,一起搞下来:

话说这公司够实诚,设计图纸都开源了 ?
撸了一遍文档,大概明白了,这个扫码器默认工作在普通模式,这种模式下会将扫到字符直接输出到系统光标位置;如果想要二次开发,需要先将扫码器设置为开发者模式,在这种模式下,扫到的信息不会输出到光标,而是借由 sdk 接口返回给调用者,在这里就可以对数据进行任意加工了。来看看如何配置开发者模式,共有两种方式
其实第一步最终也是生成一个二维码,殊途同归,不过可以选择的设置项更丰富一些,先来看看这种方式吧

打开后的界面是这样,当扫码器处于普通模式时会自动识别并连接设备:
如果已经处于开发者模式,则无法自动连接,这里直接点“下一页”

工作方式选择“开发”后点“下一页”:

这里有一些高级设置,在当前场景下主要关注扫码设置这页,里面有诸如码制、前后缀、添加回车换行符、扫码间隔时间、扫码后动作、背光灯开关等,一般选择默认即可。配置好后点右侧的“生成配置码”,得到一张二维码配置图:

扫码器扫这个码后再重新加电就可以按新模式工作了。同理可以设置扫码器按普通模式工作:

上面那个配置工具的优点是灵活,缺点是只支持 windows 平台,如果没有 windows 机器,可以直接使用开发文档中几个预定义的配置二维码:

这个文档位于 C/CPP 开发包解压后的如下路径:“USB接口C-CPP语言SDK20220411\USB接口C&CPP语言SDK20220411\扫码器C&CPP简易开发指南v2.1.pdf”,其它开发包是否有这个文件没有验证过。
对比两组图,生成的二维码和文档中的几乎一样,看起来后者也像通过工具生成的。
进入开发模式后再扫码就只是滴滴叫不上屏了,此时需要使用 sdk 写一个程序来获取扫码器的输出,在 C/CPP 开发包有中一个现成的 demo:USB接口C-CPP语言SDK20220411\USB接口C&CPP语言SDK20220411\Demohidprotocal\Release\Demohidprotocal.exe,这是 release 版本,选择 debug 版本也行,启动后界面如下:

表示连接扫码器成功,分别扫描车架号和车牌号:
vbar_open success!
开始解码:
二维码长度:18
LFV3A23C083027701
二维码长度:10
浜琈D0926
车架号是正常的,而车牌号果然是乱码。
找到 Demohidprotocol 源码 (USB接口C-CPP语言SDK20220411\USB接口C&CPP语言SDK20220411\Demohidprotocal\Demohidprotocal\main.cpp):
#include "channel.h"
#include <stdio.h>
#include <windows.h>
#include <string.h>
struct vbar_channel *dev;
/*背光灯开关控制 state为1时打开补光灯,为0时关闭补光灯*/
void lightswitch(int state)
{
unsigned char buf[1025] = { 0 };
if (state == 1)
{
buf[0] = 0x55;
buf[1] = 0xAA;
buf[2] = 0x24;
buf[3] = 0x01;
buf[4] = 0x00;
buf[5] = 0x01;
buf[6] = 0xDB;
vbar_channel_send(dev, buf, 1024);
}
else
{
buf[0] = 0x55;
buf[1] = 0xAA;
buf[2] = 0x24;
buf[3] = 0x01;
buf[4] = 0x00;
buf[5] = 0x00;
buf[6] = 0xDA;
vbar_channel_send(dev, buf, 1024);
}
}
/*扫码开关控制 state为1时打开扫码,为0时关闭扫码*/
void scanswitch(int state)
{
unsigned char buf[1025] = {0};
if (state == 1)
{
buf[0] = 0x55;
buf[1] = 0xAA;
buf[2] = 0x05;
buf[3] = 0x01;
buf[4] = 0x00;
buf[5] = 0x00;
buf[6] = 0xfb;
vbar_channel_send(dev, buf, 1024);
}
else
{
buf[0] = 0x55;
buf[1] = 0xAA;
buf[2] = 0x05;
buf[3] = 0x01;
buf[4] = 0x00;
buf[5] = 0x01;
buf[6] = 0xfa;
vbar_channel_send(dev, buf, 1024);
}
}
int main() {
dev = vbar_channel_open(1, 1);
if (!dev) {
printf("open dev fail!\n");
return -1;
}
else
{
printf("open dev success!\n");
}
printf("开始解码:\r\n");
scanswitch(1);
//接收扫码
unsigned char bufresult[1024] = {0};
unsigned char bufferrecv_1[1024] = {0};
unsigned char readBuffers[2048] = {0};
while (1)
{
if (vbar_channel_recv(dev, bufresult, 1024, 200) > 0)
{
if (bufresult[0] == 0x55 && bufresult[1] == 0xAA && bufresult[2] == 0x30)
{
int datalen = bufresult[4] + (bufresult[5] << 8);
if (datalen <= 1017)
{
for (int s1 = 0; s1 < datalen; s1++)
{
readBuffers[s1] = bufresult[6 + s1];
}
}
if (1017 < datalen && datalen <= 2041)
{
for (int s1 = 0; s1 < 1018; s1++)
{
readBuffers[s1] = bufresult[6 + s1];
}
vbar_channel_recv(dev, bufferrecv_1, 1024, 200);
for (int s2 = 0; s2 < datalen + 7 - 1025; s2++)
{
readBuffers[s2 + 1018] = bufferrecv_1[s2];
}
}
printf("二维码长度:%d\n", datalen);
readBuffers[datalen + 7] = '\0';
printf("%.*s\n", datalen, readBuffers);
}
}
}
}
谜之编码风格,另外这接口设计的也有点凌乱,程序中出现了好多魔数:1017/1018/2041/200/7,看着头大。所幸读取的数据位于 readBuffers 缓冲中,只要对它做个编码转换就 OK 啦。
windows 中文版编码一般是 gb2312,汉字源编码则可能是 utf-8,为了验证这一点,搬出来了 iconv:
$ echo "浜琈D0926" | iconv -f 'utf-8' -t 'cp936'
京MD0926
看来确实如此,注意这里使用 cp936 而不是 gb2312 作为 iconv 的第二个参数。如果没有 iconv,也有许多线上的编码转换工具可用:

确定了字符集转换方向,直接从网上搜罗来一些现成的实现:
std::wstring utf8_to_unicode(std::string const& utf8)
{
int need = MultiByteToWideChar(CP_UTF8, 0, utf8.c_str(), -1, NULL, 0);
if (need > 0)
{
std::wstring unicode;
unicode.resize(need);
MultiByteToWideChar(CP_UTF8, 0, utf8.c_str(), -1, &unicode[0], need);
return unicode;
}
return std::wstring();
}
std::string unicode_to_gb2312(std::wstring const& unicode)
{
int need = WideCharToMultiByte(936, 0, unicode.c_str(), -1, NULL, 0, NULL, NULL);
if (need > 0)
{
std::string gb2312;
gb2312.resize(need);
WideCharToMultiByte(936, 0, unicode.c_str(), -1, &gb2312[0], need, 0, 0);
return gb2312;
}
return std::string();
}
std::string utf8_to_gb2312(std::string const& utf8)
{
std::wstring unicode = utf8_to_unicode(utf8);
return unicode_to_gb2312(unicode);
}
windows 上编码转换都是先转到 unicode,再转其它编码,比较好理解。那么 demo 中的输出就可以改为:
std::string gb2312 = utf8_to_gb2312(std::string((char *)readBuffers, datalen));
printf("%.*s\n", gb2312.lenght(), gb2312.c_str());
再次运行:
二维码长度:10
京MD0926
恢复正常!
上面的过程虽然能正确解析 utf-8 数据了,但还需要用户复制 console 输出的结果,很不方便,如果能将结果直接输出到剪贴板上岂不是很爽?说干就干:
void copy_to_system_clipboard(std::string const& data)
{
printf("ready to copy data: %s\n", data.c_str());
BOOL ret = OpenClipboard(NULL);
if (!ret)
{
printf("open clipboard failed\n");
return;
}
do
{
ret = EmptyClipboard();
if (!ret)
{
printf("empty clipboard failed\n");
break;
}
HGLOBAL hdata = GlobalAlloc(GMEM_MOVEABLE, data.length() + 1);
if (hdata == NULL)
{
printf("alloc data for clipboard failed");
break;
}
char* str = (char *) GlobalLock(hdata);
memcpy(str, data.c_str(), data.length());
str[data.length()] = 0;
GlobalUnlock(hdata);
// HANDLE h = SetClipboardData(CF_UNICODETEXT, hdata);
HANDLE h = SetClipboardData(CF_TEXT, hdata);
if (!h)
{
printf("set clipboard data failed");
break;
}
printf("copy to clipboard ok\n");
} while (0);
CloseClipboard();
}
基本上是抄了网上一个例子实现的,只是增加了一些错误提示。调用点稍微改造就大功告成:
printf("%.*s\n", datalen, readBuffers);
std::string gb2312 = utf8_to_gb2312(std::string((char *)readBuffers, datalen));
copy_to_system_clipboard(gb2312);
再次运行:
二维码长度:10
浜琈D0926
ready to copy data: 京MD0926
copy to clipboard ok
此时在任一文本框中按 Ctrl+V,均能得到号牌数据。
这里请注意 copy_to_system_clipboard 中的 SetClipboardData 调用,使用 CF_TEXT 而不是 CF_UNICODETEXT,否则会得到下面的乱码输出:
ꦾ䑍㤰㘲
另外测试中发现可以同时启动多个 demo,相互之间不冲突,均能从接口拿到扫描后的数据,神奇。
上面的解决方案已经很好了,但是如果能像之前一样输出到光标就更棒了,用户可以无疑切换。作为资深 MFCer,立刻想到了一种解决方案:查找当前桌面前台 (Foreground) 窗口,找到它的活动子窗口并投递 WM_SETTEXT 消息。下面是参考网上一个例子的实现:
void set_text_to_active_windows(std::string const& data)
{
int ret = 0;
std::wstring unicode;
HWND wnd = GetForegroundWindow();
//HWND wnd = GetActiveWindow();
//HWND wnd = GetDesktopWindow();
if (wnd == NULL)
{
printf("no active windows\n");
return;
}
printf("get active window\n");
DWORD SelfThreadId = GetCurrentThreadId();
DWORD ForeThreadId = GetWindowThreadProcessId(wnd, NULL);
if (!AttachThreadInput(ForeThreadId, SelfThreadId, true))
{
printf("attach thread input failed\n");
return;
}
printf("attach thread input\n");
//wnd = GetFocus();
wnd = GetActiveWindow();
if (wnd == NULL)
{
printf("no focus windows\n");
return;
}
printf("get focus window\n");
AttachThreadInput(ForeThreadId, SelfThreadId, false);
unicode = gb2312_to_unicode(data);
ret = SendMessage(wnd, WM_SETTEXT, 0, (LPARAM)unicode.c_str());
printf("send text to active window return %d: %s\n", ret, data.c_str());
}
调用点仅需稍加改造就可以了:
printf("%.*s\n", datalen, readBuffers);
std::string gb2312 = utf8_to_gb2312(std::string((char *)readBuffers, datalen));
copy_to_system_clipboard(gb2312);
set_text_to_active_windows(gb2312);
编译运行,先启动一个 notepad 应用,将光标置于其中,便于稍后看输出结果,然而扫码后没有任何输出。将上面的 GetForegroundWindow 替换为 GetActiveWindow 或 GetDesktopWindows 都没有效果,更神奇的是加的许多 printf 调试日志也没有输出,这真是见了鬼了:
open dev success!
开始解码:
二维码长度:10
浜琈D0926
ready to copy data: 京MD0926
copy to clipboard ok
send text to active window return 0: 京MD0926
二维码长度:18
LFV3A23C083027701
ready to copy data: LFV3A23C083027701
copy to clipboard ok
send text to active window return 0: LFV3A23C083027701
只输出最终的一个调用结果。一开始怀疑是 console 程序和 win32 界面程序的不同,决定新建一个新的 win32 应用试试,由于 Win32 应用的主线程要做消息循环,这里启动一个单独的线程跑扫码的逻辑:
int APIENTRY wWinMain(_In_ HINSTANCE hInstance,
_In_opt_ HINSTANCE hPrevInstance,
_In_ LPWSTR lpCmdLine,
_In_ int nCmdShow)
{
UNREFERENCED_PARAMETER(hPrevInstance);
UNREFERENCED_PARAMETER(lpCmdLine);
// TODO: Place code here.
// Initialize global strings
LoadStringW(hInstance, IDS_APP_TITLE, szTitle, MAX_LOADSTRING);
LoadStringW(hInstance, IDC_DEMOHIDPROTOCAL, szWindowClass, MAX_LOADSTRING);
MyRegisterClass(hInstance);
// Perform application initialization:
if (!InitInstance (hInstance, nCmdShow))
{
return FALSE;
}
hThread = CreateThread(NULL, 0, qrscanner_loop, NULL, 0, NULL);
HACCEL hAccelTable = LoadAccelerators(hInstance, MAKEINTRESOURCE(IDC_DEMOHIDPROTOCAL));
MSG msg;
// Main message loop:
while (GetMessage(&msg, nullptr, 0, 0))
{
if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
return (int) msg.wParam;
}
qrscanner_loop 就是之前 console main 那一堆东西了,为了展示信息,在默认的视图中间填充一个 edit 控件:
BOOL InitInstance(HINSTANCE hInstance, int nCmdShow)
{
hInst = hInstance; // Store instance handle in our global variable
HWND hWnd = CreateWindowW(szWindowClass, szTitle, WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, nullptr, nullptr, hInstance, nullptr);
if (!hWnd)
{
return FALSE;
}
RECT rect = { 0 };
GetClientRect(hWnd, &rect);
hEdit = CreateWindowW(TEXT("Edit"), TEXT(""),
WS_CHILD | WS_VISIBLE | ES_LEFT | ES_MULTILINE | ES_WANTRETURN | WS_VSCROLL | ES_AUTOVSCROLL,
rect.left, rect.top, rect.right-rect.left, rect.bottom-rect.top, hWnd, (HMENU)10002, hInstance, NULL);
ShowWindow(hWnd, nCmdShow);
UpdateWindow(hWnd);
return TRUE;
}
至于编辑框随视图大小变化而变化这种基本功,就不赘述了,后面会放出完整源码。注意这里的 hEdit,它存储着编辑框的句柄,后面会用到。
console 改 win32 最大的变化是 printf 日志输出没有了,为了解决这个问题,改写 printf 为 my_printf,在里面做些文章:
extern HWND hEdit;
void my_printf(char const* format, ...)
{
char line[4096] = { 0 };
va_list vp;
va_start(vp, format);
vsprintf(line, format, vp);
va_end(vp);
// replace '\n' to '\r\n'
if (strlen(line) > 0)
line[strlen(line) - 1] = '\r';
strcat(line, "\n");
//std::wstring data = gb2312_to_unicode(line);
// SendMessage(hEdit, WM_SETTEXT, 0, (WPARAM)data.c_str());
SendMessage(hEdit, EM_SETSEL, -2, -1);
SendMessageA(hEdit, EM_REPLACESEL, true, (long)line);
SendMessage(hEdit, WM_VSCROLL, SB_BOTTOM, 0);
OutputDebugStringA(line);
}
基本就是将日志发往刚才的 hEdit,注意这里不使用 WM_SETTEXT 以免冲掉历史消息,最后上张效果图:

再看下新版 set_text_to_active_windows 的实现:
void set_text_to_active_windows(std::string const& data)
{
int ret = 0;
wchar_t const* str;
HWND wnd = GetForegroundWindow();
//HWND wnd = GetActiveWindow();
//HWND wnd = GetDesktopWindow();
if (wnd == NULL)
{
my_printf("no active windows\n");
return;
}
my_printf("get active window\n");
DWORD SelfThreadId = GetCurrentThreadId();
DWORD ForeThreadId = GetWindowThreadProcessId(wnd, NULL);
if (!AttachThreadInput(ForeThreadId, SelfThreadId, true))
{
my_printf("attach thread input failed\n");
return;
}
my_printf("attach thread input\n");
//wnd = GetFocus();
wnd = GetActiveWindow();
if (wnd == NULL)
{
my_printf("no focus windows\n");
return;
}
my_printf("get focus window\n");
AttachThreadInput(ForeThreadId, SelfThreadId, false);
//std::wstring unicode = gb2312_to_unicode(data);
std::wstring_convert<std::codecvt_utf8_utf16<wchar_t>> converter;
std::wstring tst = converter.from_bytes(data);
str = tst.data();
//ret = SendMessageA(wnd, WM_SETTEXT, 0, (LPARAM)data.c_str());
for (int i=0; str[i] != '\n'; ++i)
{
ret = SendMessage(hEdit, WM_CHAR, str[i], 0);
}
my_printf("send text to active window return %d: %s\n", ret, data.c_str());
}
与之前版本相比,除了 printf 变为 my_printf,最大的变化是在结尾部分:使用 WM_CHAR 消息代替 WM_SETTEXT。这样做是为了更好的模拟光标行为,毕竟不能假设用户光标一定位于 windows edit 控件上,有可能位于绘制界面框架 (Qt) 或描述界面框架 (Electron) 生成的 App 的控件上,这个消息可以实现字符被一个个输入编辑框的效果,兼容上述所有控件。
满怀期待的启动应用后,出现和 console 程序一样的行为——光标下没有任何输出,且不打印任何调试日志,遇到中文字符还会崩溃:

看崩溃点没什么头绪,表现还不如 console 呢,这下把我整不会了,最终这个方案宣告失败。不过留着还是有意义的,万一有人基于它实现了光标输出呢…
本文尝试解决扫码器在遇到中文时不输出字符的问题,总体上解决了这个问题,优雅的解决方案因技术问题没有实现,不优雅的解决方案针对检测场的需求来说也够用了。
最早想的技术方案其实是不想动 demo 程序的,当时想通过在外面包一层 shell 脚本来解决,熟悉的读者知道我喜欢用这种方式解决一些问题,当时主要有两个原因导致想这样干:
后面亲自试过后,发现有两个问题 shell 脚本无法绕开:
最终是将公司的 windows 本带回来专门搞这个事情,那个开发环境配置的比较全面,不用浪费时间再配了。说到这里,突然想到为何没有人搞在线的 VS 开发环境?linux 上的 gcc 这种环境一搜一大把,提交个文件或直接在 web 界面里写 c++ 代码,就能编译出可执行文件,而免费的 VS 线上开发环境却几乎没有!如果有人搞个 VS 的在线编译环境,肯定能火,哪怕编译一次收个十元二十元的,我估计也有人用。
上面说了一些解决过程中的探索,下面谈谈这个扫码器的问题,如果它能将编码转换功能集成在硬件里,通过配置来决定如何进行编码转换,那么这个场景就不需要二次开发 sdk 了!只要运行下 VguangConfig 并做一些勾选工作就可以了,如果再将常用的几种编码转换做成二维码配置放在文档中,直接扫对应的码就搞定了!后续给厂家反馈时,厂家表示可以考虑,其实就是增加一个 iconv.dll 的事儿,不难!
最后说一下系统升级导致扫码器不能用的问题,这就是典型的没做系统集成测试案例啊!新系统没有兼容老系统的一些隐性规则,导致下游出问题,其实完全可以让升级系统的软件厂商改进一下它这个二维码的生成方式,是用 utf8 还是 gb2312,搞成可配置的,操作人员通过配置来保持以前的编码方式不变,这个问题也能得到解决。
扫码器 sdk 官网就可以下载,两个应用的源码及可执行文件链接如下:
console 版可以直接用,win32 版还是个半成品,感兴趣的读者可以尝试探索一下。
console 版的可执行文件为 debug 版本,release 版本不知为何编译报错:
1>------ Build started: Project: Demohidprotocal, Configuration: Release Win32 ------
1>main.obj : error LNK2001: unresolved external symbol __imp__SetClipboardData@8
1>main.obj : error LNK2001: unresolved external symbol __imp__EmptyClipboard@0
1>main.obj : error LNK2001: unresolved external symbol __imp__CloseClipboard@0
1>main.obj : error LNK2001: unresolved external symbol __imp__OpenClipboard@4
1>D:\BaiduNetdiskDownload\USB接口C&CPP语言SDK20220411\Demohidprotocal\Release\Demohidprotocal.exe : fatal error LNK1120: 4 unresolved externals
========== Build: 0 succeeded, 1 failed, 0 up-to-date, 0 skipped ==========
看起来和操作系统剪贴板相关,因时间关系没有进一步去研究。
[1]. 草料二维码
[2]. 微光互联
[3]. Windows下的字符集转换(ASCII、UICODE、UTF8、GB2312和BIG5互转)
[4]. 编码转换
[5]. 剪贴板操作
[6]. Windows/Mac/Linux/ssh将shell内容输出到剪贴板
我在从html页面生成PDF时遇到问题。我正在使用PDFkit。在安装它的过程中,我注意到我需要wkhtmltopdf。所以我也安装了它。我做了PDFkit的文档所说的一切......现在我在尝试加载PDF时遇到了这个错误。这里是错误:commandfailed:"/usr/local/bin/wkhtmltopdf""--margin-right""0.75in""--page-size""Letter""--margin-top""0.75in""--margin-bottom""0.75in""--encoding""UTF-8""--margin-left""0.75in""-
为了将Cucumber用于命令行脚本,我按照提供的说明安装了arubagem。它在我的Gemfile中,我可以验证是否安装了正确的版本并且我已经包含了require'aruba/cucumber'在'features/env.rb'中为了确保它能正常工作,我写了以下场景:@announceScenario:Testingcucumber/arubaGivenablankslateThentheoutputfrom"ls-la"shouldcontain"drw"假设事情应该失败。它确实失败了,但失败的原因是错误的:@announceScenario:Testingcucumber/ar
我对最新版本的Rails有疑问。我创建了一个新应用程序(railsnewMyProject),但我没有脚本/生成,只有脚本/rails,当我输入ruby./script/railsgeneratepluginmy_plugin"Couldnotfindgeneratorplugin.".你知道如何生成插件模板吗?没有这个命令可以创建插件吗?PS:我正在使用Rails3.2.1和ruby1.8.7[universal-darwin11.0] 最佳答案 随着Rails3.2.0的发布,插件生成器已经被移除。查看变更日志here.现在
我尝试运行2.x应用程序。我使用rvm并为此应用程序设置其他版本的ruby:$rvmuseree-1.8.7-head我尝试运行服务器,然后出现很多错误:$script/serverNOTE:Gem.source_indexisdeprecated,useSpecification.Itwillberemovedonorafter2011-11-01.Gem.source_indexcalledfrom/Users/serg/rails_projects_terminal/work_proj/spohelp/config/../vendor/rails/railties/lib/r
我正在使用puppet为ruby程序提供一组常量。我需要提供一组主机名,我的程序将对其进行迭代。在我之前使用的bash脚本中,我只是将它作为一个puppet变量hosts=>"host1,host2"我将其提供给bash脚本作为HOSTS=显然这对ruby不太适用——我需要它的格式hosts=["host1","host2"]自从phosts和putsmy_array.inspect提供输出["host1","host2"]我希望使用其中之一。不幸的是,我终其一生都无法弄清楚如何让它发挥作用。我尝试了以下各项:我发现某处他们指出我需要在函数调用前放置“function_”……这
我正在尝试在我的centos服务器上安装therubyracer,但遇到了麻烦。$geminstalltherubyracerBuildingnativeextensions.Thiscouldtakeawhile...ERROR:Errorinstallingtherubyracer:ERROR:Failedtobuildgemnativeextension./usr/local/rvm/rubies/ruby-1.9.3-p125/bin/rubyextconf.rbcheckingformain()in-lpthread...yescheckingforv8.h...no***e
我花了三天的时间用头撞墙,试图弄清楚为什么简单的“rake”不能通过我的规范文件。如果您遇到这种情况:任何文件夹路径中都不要有空格!。严重地。事实上,从现在开始,您命名的任何内容都没有空格。这是我的控制台输出:(在/Users/*****/Desktop/LearningRuby/learn_ruby)$rake/Users/*******/Desktop/LearningRuby/learn_ruby/00_hello/hello_spec.rb:116:in`require':cannotloadsuchfile--hello(LoadError) 最佳
这是一道面试题,我没有答对,但还是很好奇怎么解。你有N个人的大家庭,分别是1,2,3,...,N岁。你想给你的大家庭拍张照片。所有的家庭成员都排成一排。“我是家里的friend,建议家庭成员安排如下:”1岁的家庭成员坐在这一排的最左边。每两个坐在一起的家庭成员的年龄相差不得超过2岁。输入:整数N,1≤N≤55。输出:摄影师可以拍摄的照片数量。示例->输入:4,输出:4符合条件的数组:[1,2,3,4][1,2,4,3][1,3,2,4][1,3,4,2]另一个例子:输入:5输出:6符合条件的数组:[1,2,3,4,5][1,2,3,5,4][1,2,4,3,5][1,2,4,5,3][
我在pry中定义了一个函数:to_s,但我无法调用它。这个方法去哪里了,怎么调用?pry(main)>defto_spry(main)*'hello'pry(main)*endpry(main)>to_s=>"main"我的ruby版本是2.1.2看了一些答案和搜索后,我认为我得到了正确的答案:这个方法用在什么地方?在irb或pry中定义方法时,会转到Object.instance_methods[1]pry(main)>defto_s[1]pry(main)*'hello'[1]pry(main)*end=>:to_s[2]pry(main)>defhello[2]pry(main)
我使用的是Firefox版本36.0.1和Selenium-Webdrivergem版本2.45.0。我能够创建Firefox实例,但无法使用脚本继续进行进一步的操作无法在60秒内获得稳定的Firefox连接(127.0.0.1:7055)错误。有人能帮帮我吗? 最佳答案 我遇到了同样的问题。降级到firefoxv33后一切正常。您可以找到旧版本here 关于ruby-无法在60秒内获得稳定的Firefox连接(127.0.0.1:7055),我们在StackOverflow上找到一个类