现在开发客户端程序,主流的开发语言中Java、.Net很容易破解,MFC开发效率低,Delphi7是巅峰,后面走了弯路,现在XE系列一言难尽。至于QT,哎。
VB6现在已经基本退出历史舞台,惋惜的话就不多说了。。
最近楼主看上了FreeBasic,其国内的版本叫VisualFreeBasic(下面简称vfb),开发者勇芳是一位技术大牛。
vfb自带一个浏览器组件,封装的是国内一个浏览器大咖龙泉扫地僧的Miniblink。作为一个浏览器组件,竟然没有提供文件下载的案例,为此我跟VFB的一位技术大牛驰骋乾坤准备为此做一个例子。
Miniblink的免费版本node.dll导出的是纯C接口, 也就是__cdecl,而不是常见的__stdcall,这个也给增加了一些麻烦。收费版本mb.dll则采用stdcall接口,但是我们秉着不改vfb的浏览器组件的原则写例子,等下面有时间,我们再写个vfb的控件。
Miniblink下载有两个事件,分别是wkeDownload和wkeDownload2
wkeDownload的声明区别如下:
void wkeOnDownload(wkeWebView webView, wkeDownloadCallback callback, void* param)
typedef bool(WKE_CALL_TYPE*wkeDownloadCallback)(wkeWebView webView, void* param, const char* url);
从上面的回调可以看出,wkeDownload只返回了一个url,对于没有身份认证、GET的请求才能下载,很鸡肋。
wkeDownload2的声明就复杂多了:
typedef void* wkeNetJob;
typedef enum _wkeLoadingResult {
WKE_LOADING_SUCCEEDED,
WKE_LOADING_FAILED,
WKE_LOADING_CANCELED
} wkeLoadingResult;
typedef enum _wkeDownloadOpt {
kWkeDownloadOptCancel,
kWkeDownloadOptCacheData,
} wkeDownloadOpt;
typedef void(WKE_CALL_TYPE*wkeNetJobDataRecvCallback)(void* ptr, wkeNetJob job, const char* data, int length);
typedef void(WKE_CALL_TYPE*wkeNetJobDataFinishCallback)(void* ptr, wkeNetJob job, wkeLoadingResult result);
typedef struct _wkeNetJobDataBind {
void* param;
wkeNetJobDataRecvCallback recvCallback;
wkeNetJobDataFinishCallback finishCallback;
}wkeNetJobDataBind;
typedef wkeDownloadOpt(WKE_CALL_TYPE*wkeDownload2Callback)(
wkeWebView webView,
void* param,
size_t expectedContentLength,
const char* url,
const char* mime,
const char* disposition,
wkeNetJob job,
wkeNetJobDataBind* dataBind);
Type DownInfo
FileName As String
Length As Integer
Down As Integer
End Type
这个确实完善多了,但是对接的难度也大,估计也是因为这个市面上没见过如何使用这个接口的例子。vfb封装了wkeDownload2的事件,但是具体怎么写没有例子。
首先,我们根据C++的文档,声明两个枚举和一个结构体wkeNetJobDataBind。要特别注意的是,wkeNetJobDataBind的两个回调,得声明是cdecl导出的,不然这个接口wkeNetJobDataRecvCallback只会被调用一次,然后程序就会平栈错误直接崩溃(这里特别感谢驰骋乾坤的指点,不然我找不出这个BUG)。
enum wkeLoadingResult
WKE_LOADING_SUCCEEDED
WKE_LOADING_FAILED
WKE_LOADING_CANCELED
End enum
enum wkeDownloadOpt
kWkeDownloadOptCancel
kWkeDownloadOptCacheData
End enum
Type DownInfo
FileName As String
Length As Integer
Down As Integer
End Type
Type wkeNetJobDataBind
param As Any Ptr
recvCallback As Sub cdecl(param As Any Ptr ,job As wkeNetJob ,dataIn As Any Ptr ,length As Integer)
finishCallback As Sub cdecl(param As Any Ptr ,job As wkeNetJob ,result As wkeLoadingResult)
End Type
下面是两个回调的实现,当然也必须要说cdecl导出类型的:
Sub wkeNetJobDataRecvCallback cdecl(ByVal param As Any Ptr ,ByVal job As wkeNetJob ,ByVal dataIn As Any Ptr ,ByVal length As Integer)
Dim d As DownInfo Ptr = param
Dim dataOut(length) As Byte
CopyMemory(Varptr(dataOut(0)) ,dataIn ,length)
Dim f As Integer = FreeFile()
Open d->FileName For Binary As #f
Put #f, LOF(f),dataOut()
Close #f
End Sub
Sub wkeNetJobDataFinishCallback cdecl(ByVal param As Any Ptr ,ByVal job As wkeNetJob ,ByVal result As wkeLoadingResult)
Dim d As DownInfo Ptr = param
If MsgBox("是否需要打开文件?" ,"下载完成" ,MB_YESNO) = 6 Then
ShellExecute Me.hWnd, "Open", d->FileName, "", "", 0
End If
Delete d
End Sub
wkeNetJobDataRecvCallback负责文件字节流的接收;wkeNetJobDataFinishCallback负责下载完成的处理,例如打开。
wekDownload2事件的实现:
1、从disposition想办法获取下载的文件名字(不一定有)
2、如果没有,则从url的路径获取名字
3、引导用户选择保存的文件目录
4、如果本地存在同名文件,则自动删除
5、把两个回调的地址给接上去
6、事件函数返回1,表示开始接收
Function MainForm_Miniblink1_Download2(hWndForm As hWnd ,hWndControl As hWnd ,WebView As wkeWebView ,expectedContentLength As size_t ,url As CWSTR ,mime As CWSTR ,disposition As CWSTR ,job As wkeNetJob ,wkeNetJobDataBind As Any Ptr) As BOOL '页面下载事件。点击某些链接,触发下载会调用
Dim strFileName As String = ""
If Len(disposition) > 0 Then
strFileName = GetStrCenter(disposition ,"filename=""" ,"""")
End If
Dim nIndex As Integer
If Len(strFileName) <= 0 Then
nIndex = InStrRev(url ,"/")
If nIndex > 0 Then
Dim nQuery As Integer = InStr(nIndex ,url ,"?")
If nQuery > 0 Then
strFileName = Mid(url ,nIndex + 1 ,nQuery - nIndex - 1)
Else
strFileName = Mid(url, nIndex + 1)
End If
End If
End If
Dim strSaveFileName As String = FF_SaveFileDialog(hWndForm , "下载保存", strFileName, "", "所有的文件 (*.*)|*.*")
If strSaveFileName = "" Then
Function = 0
Exit Function
End If
nIndex = InStr(strSaveFileName ,"|")
If nIndex > 0 Then
strSaveFileName = Left(strSaveFileName, nIndex - 1)
End If
If FileExists(strSaveFileName) Then
If MsgBox("文件已经存在,是否覆盖?" ,"文件覆盖提示" ,MB_YESNO) = 7 Then
Function = 0
Exit Function
End If
End If
Static d As DownInfo
d.FileName = strSaveFileName
d.Length = expectedContentLength
d.Down = 0
Static t As wkeNetJobDataBind
t.param = Varptr(d)
t.finishCallback = @wkeNetJobDataFinishCallback
t.recvCallback = @wkeNetJobDataRecvCallback
MoveMemory(wkeNetJobDataBind ,Varptr(t) ,Len(t))
Function = 1
End Function
驰骋乾坤曰:
当然,vfb也可以不用动不动就MoveMemory,可以直接操作地址:
Dim t As wkeNetJobDataBind ptr = wkeNetJobDataBind
t->param = Varptr(d)
t->finishCallback = @wkeNetJobDataFinishCallback
t->recvCallback = @wkeNetJobDataRecvCallback