引言:開始嘗試使用MarkDown語法寫文檔,發現圖片必須用外鏈的形式才能插入到文章中,而自己平時最常用的插入圖片方式就是QQ截屏,覺得很不方便所以制作的小工具輔助上傳,因為時間和水平有限,其實代碼寫的很粗糙,以后有時間會不斷改進。
http抓包和分析
上傳用的網址是極簡圖床 極簡圖床,這個圖床內部使用的是六牛雲,因為這個圖床的上傳不需要身份認證比較簡單,所以就用它做的,后期還是要換到六牛雲比較保險。
抓包使用的是Fiddler 4 ,開啟之后相當於一個代理,電腦幾乎所有的網絡請求都會被監視到,但是瀏覽器的包需要設置一下代理,IP寫本機,端口號8888
== 注意 ==:這個代理設置會影響到IE瀏覽器,如果關掉了fiddler沒有刪掉代理,容易出現的一個奇葩問題就是TFS無法登陸。
抓包的結果是這樣的:
數據包里面有幾個需要注意的點:
- url的參數里面包含了圖片的格式
- 上傳需要帶Cookie才能成功
- 圖片不是以那種表單的形式上傳,而是直接把base64編碼的字符串放到了http的主體里面
httpwebrequest模擬post
在這里面我遇到了一個問題,就是cookie的獲取,我用網上給的那種在爬蟲程序中獲取cookie 的方式並沒有成功,后來我想這種字符串類型的cookie不就是http頭里面的一部分信息么,然后就把抓包里面的cookie直接以鍵值對的方式寫到了http頭里面,這個也不知道以后會有什么弊端,代碼如下:
public static string PostHttp(string url, byte[] body)
{
HttpWebRequest httpWebRequest = (HttpWebRequest)WebRequest.Create(url);
httpWebRequest.ContentType = "text/plain";
httpWebRequest.Method = "POST";
httpWebRequest.Timeout = 20000;
httpWebRequest.Headers.Add("Cookie", cookieStr);
byte[] btBodys = body;
httpWebRequest.ContentLength = btBodys.Length;
httpWebRequest.GetRequestStream().Write(btBodys, 0, btBodys.Length);
HttpWebResponse httpWebResponse = (HttpWebResponse)httpWebRequest.GetResponse();
StreamReader streamReader = new StreamReader(httpWebResponse.GetResponseStream());
string responseContent = streamReader.ReadToEnd();
httpWebResponse.Close();
streamReader.Close();
httpWebRequest.Abort();
httpWebResponse.Close();
return responseContent;
}
HttpClient,HttpWebRequest,HttpWebResponse這三個類是封裝的http請求的類,其中HttpClient的封裝程度最高,關於這三個類的具體使用和注意事項准備再寫一個總結。
返回的值是Json形式,這個在Fiddler抓的包里面能看見,用Json.Net解析一下就可以拿到外鏈字符串了。
監聽剪貼板
這個是目前來說我覺得比較麻煩的地方,因為涉及到了對windows這個操作系統的編程,比如剪貼板的變化,或者快捷鍵(ctrl+v)的監聽,這些有一部分要調用windows操作系統的API,更有一些需要使用一種叫HOOK的技術,甚至可以hook住QQ截屏(ctrl+alt+a)這個函數的地址,這個應該是實現效果的最優解。
但是因為自己水平有限,所以選擇了最簡單的調用Windows API的實現方式。
1·剪貼板介紹
剪貼板是非常方便的進程間通訊,下面是引用的一段解釋
剪貼板是Windows系統一段可連續的。可隨存放信息的大小而變化的內存空間,用來臨時存放交換信息。內置在windows並且使用系統的內部資源RAM,或虛擬內存來臨時保存剪切和復制的信息,可以存放的信息種類是多種多樣的。剪切或復制時保存在剪貼板上的信息,只有再剪貼或復制另外的信息,或停電、或退出windows,或有意地清除時,才可能更新或清除其內容,即剪貼或復制一次,就可以粘貼多次。
2·剪貼板引用
C#定義了一個類System.Windows.Forms.Clipboard來簡化剪切板操作,要使用剪貼板要先引入三個函數:
[System.Runtime.InteropServices.DllImport("user32")]
private static extern IntPtr SetClipboardViewer(IntPtr hwnd);
[System.Runtime.InteropServices.DllImport("user32")]
private static extern IntPtr ChangeClipboardChain(IntPtr hwnd,IntPtr hWndNext);
[System.Runtime.InteropServices.DllImport("user32")]
private static extern int SendMessage(IntPtr hwnd,int wMsg,IntPtr wParam,IntPtr lParam);
還有兩個常量:
const int WM_DRAWCLIPBOARD = 0x308;
const int WM_CHANGECBCHAIN = 0x30D;
-
IntPtr SetClipboardViewer(IntPtr hwnd):
用於往觀察鏈中添加一個窗口句柄,這個窗口就可成為觀察鏈中的一員了,返回值指向下一個觀察者。 -
IntPtr ChangeClipboardChain(IntPtr hwnd,IntPtr hWndNext): 刪除由hwnd指定的觀察鏈成員,這是一個窗口句柄,第二個參數hWndNext是觀察鏈中下一個窗口的句柄
-
int SendMessage(IntPtr hwnd,int wMsg,IntPtr wParam,IntPtr lParam): 發送消息,還有一個很重要的作用是將WM_DRAWCLIPBOARD 消息傳遞到下一個觀察鏈中的窗口
觀察鏈:其實有很多程序在一起監視剪貼板,比如迅雷在檢測到你復制了下載鏈接的時候,就會自動啟動。
3·剪貼板編程
定義完成后,可以分三部來使用,第一步把自己的窗口添加到觀察鏈中成為觀察者,並保存下一個觀察者的句柄;第二步監視剪切板,並把剪切板變化的消息發送給下一個觀察者;第三步撤消自己定義的觀察者,並通知下一個觀察者。
第一步:把自己的窗口添加到觀察鏈中成為觀察者,並保存下一個觀察者的句柄
//存放觀察鏈中下一個窗口句柄
IntPtr NextClipHwnd;
private void Form1_Load(object sender, System.EventArgs e)
{
//獲得觀察鏈中下一個窗口句柄
NextClipHwnd=SetClipboardViewer(this.Handle);
}
第二步:監視剪切板,並把剪切板變化的消息發送給下一個觀察者,這里需要重載WndProc方法;
這里用到兩個消息常量:
const int WM_DRAWCLIPBOARD = 0x308;
const int WM_CHANGECBCHAIN = 0x30D;
protected override void WndProc(ref System.Windows.Forms.Message m)
{
switch(m.Msg)
{
case WM_DRAWCLIPBOARD:
//將WM_DRAWCLIPBOARD消息傳遞到下一個觀察鏈中的窗口
SendMessage(NextClipHwnd,m.Msg,m.WParam,m.LParam);
IDataObject iData = Clipboard.GetDataObject();
//檢測文本
if(iData.GetDataPresent(DataFormats.Text)|iData.GetDataPresent(DataFormats.OemText))
{
this.richTextBox1.Text=(String)iData.GetData(DataFormats.Text);
}
//檢測圖像
if (iData.GetDataPresent(DataFormats.Bitmap))
{
pictureBox1.Image=Clipboard.GetImage();
NewClipData();
}
//檢測自定義類型
if (iData.GetDataPresent("myFormat"))
{
MyObj myobj=(MyObj)iData.GetData("myFormat");
this.richTextBox1.Text=myobj.ObjName;
}
break;
default:
base.WndProc(ref m);
break;
}
}
第三步:撤消自己定義的觀察者,並通知下一個觀察者。
private void Form1_Closed(object sender, System.EventArgs e)
{
//從觀察鏈中刪除本觀察窗口(第一個參數:將要刪除的窗口的句柄;第二個參數://觀察鏈中下一個窗口的句柄 )
ChangeClipboardChain(this.Handle,NextClipHwnd);
//將變動消息WM_CHANGECBCHAIN消息傳遞到下一個觀察鏈中的窗口
SendMessage(NextClipHwnd,WM_CHANGECBCHAIN,this.Handle,NextClipHwnd);
}
我自己的代碼並非完全按照上面的格式書寫。
讀取圖片然后轉碼
這一部分主要是編碼,字節數組和流的操作,就直接貼代碼了。
//讀取圖片並且進行操作
private void Handler()
{
Image image = Clipboard.GetImage();
if (image != null)
{
string base64str = ImageToBase64String(image);
byte[] decodedByteArray = Encoding.UTF8.GetBytes(base64str);
string responsdata = PostHttp(uri, decodedByteArray);
ImageInfo info = JsonConvert.DeserializeObject<ImageInfo>(responsdata);
Clipboard.SetDataObject("");
}
}
//圖片轉base64編碼
private string ImageToBase64String(Image imageData)
{
string base64;
MemoryStream memory = new MemoryStream();
imageData.Save(memory, ImageFormat.Png);
base64 = System.Convert.ToBase64String(memory.ToArray());
memory.Close();
memory = null;
return base64;
}
一些知識點的總結
- [DllImport("user32.dll", CharSet = CharSet.Auto)]:這是導入Windows系統自帶的user32.dll中的函數(API),也可以用於C++編寫的帶有導出函數的DLL
- private IntPtr nextClipboardViewer :C#中的IntPtr類型稱為“平台特定的整數類型”,它們用於本機資源,如窗口句柄。資源的大小取決於使用的硬件和操作系統,但其大小總是足以包含系統的指針(因此也可以包含資源的名稱)。 所以,在您調用的API函數中一定有類似窗體句柄這樣的參數,那么當您聲明這個函數時,您應該將它顯式地聲明為IntPtr類型。
- 句柄:句柄,是整個Windows編程的基礎。一個句柄是指使用的一個唯一的整數值,即一個4字節(64位程序中為8字節)長的數值,來標識應用程序中的不同對象和同類中的不同的實例,諸如,一個窗口,按鈕,圖標,滾動條,輸出設備,控件或者文件等。應用程序能夠通過句柄訪問相應的對象的信息,但是句柄不是指針,程序不能利用句柄來直接閱讀文件中的信息。如果句柄不在I/O文件中,它是毫無用處的。 句柄是Windows用來標志應用程序中建立的或是使用的唯一整數,Windows大量使用了句柄來標識對象。
結語
做的工具雖然小,但是涉及到了好多方面的知識,其中Windows編程這一塊是新的領域,http是加深了理解,IO操作和Image操作是學過但是已經不熟悉了,接下來的時間查缺補漏,然后把東西好好完善一下。
下載鏈接:下載地址