顯示具有 pywin32 標籤的文章。 顯示所有文章
顯示具有 pywin32 標籤的文章。 顯示所有文章

2020年6月24日 星期三

在 linux 下使用wine python 串接群益 SKCOM API

        群益api 是使用 windows 的 COM 技術,在 linux 下要靠 wine 來模擬 windows 的環境。wine 要使用 32 bit 的相容性比較好,因此本篇 linux 系統 , python, skcom.dll 皆是使用 32 bit版。

操作概念是 在 linux 下裝 wine 註冊 SKCOM.dll, 用 wine 安裝 windows 版本的 python,  再用這個windows 版的 python 安裝 comtypes, pywin32 等套件來使用 skcom。 整個環境會是在 wine 底下執行的,操作起來還是有點隔閡,不是很順暢,我也還在摸索 

linux 我使用 lubuntu 18.04 alternative-i386 這個桌面版本, 執行Wine時建議是要在有XWindow的環境下執行,安裝一些 windows 程式,會需要 GUI 的操作 

linux wine 的安裝跟使用是參考這兩篇, 研究的超仔細,大推!! 

安裝 wine, winetricks

  • 建議使用32位元的作業系統來安裝Wine,因為Wine目前64位元的支援很差,它主要是以執行32位元的Windows程式為主!
  • 建議是透過compile Wine原始碼的方式來安裝Wine,也是為了避免32位元的函式庫裝得不夠完整!
  • winetricks 可以幫助安裝 VC++ runtime 2010 ,註冊 SKCOM.dll 時需要用到 
  • 剛裝好的 lubuntu 環境,把 apt 更新至最新,並裝下列開發套件,編譯 Wine 需要用到
$ sudo apt update; apt upgrade
$ sudo apt install openssh-client openssh-server vim software-properties-common gcc g++ vim subversion automake autoconf perl patch build-essential libtool doxygen bison flex zlib1g-dev gperf tofrodos wget curl screen git samba vsftpd bash-completion ftp colordiff libkrb5-dev rar unrar unzip vnstat libssl-dev
  • 補足所需的套件
$ time apt install gnome-devel libx11-dev flex bison qt5-qmake libjpeg-dev libxslt1-dev libglu1-mesa-dev freeglut3-dev prelink libasound2-dev libfreetype6-dev libpng-dev libxml2-dev libxrender-dev libgl1-mesa-dev libgphoto2-dev libmpg123-dev libtiff-dev libpulse-dev liblcms2-dev libcapi20-dev libldap-dev libopenal-dev libv4l-dev libsane-dev libcups2-dev libgsm1-dev libosmesa6-dev libgnutls28-dev libpcap0.8-dev oss4-dev libcdk5-dev libgstreamer-plugins-base1.0-dev libarrayfire-opencl-dev
$ time apt install libsdl2-dev libvulkan-dev
  • 清除一下apt-get抓下來的殘餘檔
$ apt clean
  • 用root帳號登入
  • 先把Wine的git repository上的code拉下來!
$ mkdir -pv /root/packages ; cd /root/packages
$ time git clone https://github.com/wine-mirror/wine

  • 檢查套件是否足夠
$ cd ./wine
$ ./configure 

 #訊息大概剩下下面這樣,就差不多了。
configure: MinGW compiler not found, cross-compiling PE files won't be supported.
configure: libhal development files not found, no legacy dynamic device support.
configure: libFAudio development files not found, XAudio2 won't be supported.
configure: vkd3d development files not found (or too old), Direct3D 12 won't be supported.
  • 接下來編譯並且安裝!編譯會花大約40-60分鐘
$ time make
$ time make install
  • 接下來安裝winetricks!直接從github wget下來就可以用了!
$ mkdir -pv /opt/bin ; cd /opt/bin
$ wget https://raw.githubusercontent.com/Winetricks/winetricks/master/src/winetricks
$ chmod +x ./winetricks
$ ./winetricks -V  

#檢查版本,出現下列訊息就沒問題了
20190615-next - sha256sum: 47304e177f259d6f9c05af01ab42c06531fd8a9716e2751d2fadcd664130feea

Wine 使用 SKCOM api

  • 設定Wine的windows環境,在 XWindow環境下。第一次使用須初始化一個windows 環境,打開Terminal,執行以下指令以設定Wine的環境!我的環境是建立在 ~./home/使用者/.wine 下,.wine 就是windows 虛擬的系統,底下有個 drive_c 資料夾,等於 C:\  槽
$ wineboot  

  • 群益API的SKCOM.dll檔需要 VC++ 2010可轉散發套件, 可透過winetricks來安裝 vcrun2010
$ winetricks vcrun2010 

  • 下載群益API,將API 資料夾 CapitalAPI_2.13.22 下的x86 資料夾搬到 ~/.wine/drive_c/skcom/x86  這等於是存在 windows 的 C:\\skcom\\x86, 之後使用python 呼叫會方便一點
  • cd 到 ~/.wine/drive_c/skcom/x86資料夾下,執行以下指令以註冊群益API:
$ regsvr32 ./SKCOM.dll

drive_c 下路徑的內容,skcom, python, strategy 是我自己創建的,資料夾名稱不要有空格,減少麻煩



wine下的 python環境

  • 用wine 來安裝 windows版的 python
  • 到 python 官網,我選了 Python3.8.3 的 Windows x86 executable installer 這個版本下載
  • 我有嘗試使用miniconda,但 conda 或 pip 位置不在預設路徑上,我搞不定,導致python套件裝不起來  

#使用 wine安裝windows版的python
#會有GUI跳出,可將python裝在 C:\\python 路徑下
$ wine python-3.8.3.exe   

#安裝 comtypes 及 pywin32 套件,其他python套件也可以如此安裝
$ wine pip install comtypes pywine32    


  • wine的windows版的 python 環境建置完成,可以開始 CODING!!
$ wine python



將寫好的 Script.py 交由 window python 執行, 要加wine 不然會呼叫到 linux系統下的 python, 兩個系統還是不相容的

$ wine python Script.py

附上 Script.py 的內容


import pythoncom, time, os
from comtypes.client import GetModule, GetEvents, CreateObject
from datetime import datetime

#第一次使用需要將 SKCOM 包成python 的package
#SKCOM.dll 在linux 下,我是放在 ~./home/yi/.wine/driv_c/skcom/x86/SKCOM.dll
#但在 wine 下,是放在下列路徑, drive_c 就是windows的C槽
GetModule(r'C:\\skcom\x86\SKCOM.dll')
#GetModule會在comtypes.gen 下產生 SKCOMLib package, 之後就用下面方式引用
import comtypes.gen.SKCOMLib as sk

#建立COM物件
if 'skC' not in globals(): skC=CreateObject(sk.SKCenterLib, interface=sk.ISKCenterLib)
if 'skQ' not in globals(): skQ=CreateObject(sk.SKQuoteLib, interface=sk.ISKQuoteLib)
if 'skR' not in globals(): skR=CreateObject(sk.SKReplyLib, interface=sk.ISKReplyLib)

#輸入身分證與密碼
ID=''
PW=''
#股票代號
strStock="TX00" 

print(str(datetime.now())[0:-4], 'ID & PW entered')

#建立事件類別
class skQ_events:                                
    def OnConnection(self, nKind, nCode):
        print(datetime.now(), 'OnConnection', nKind, nCode)
        if nKind == 3003:
            #nKind==3003 表連線報價主機就緒
            print("quoate server connected..., nkind= ", nKind)
            ncode=skQ.SKQuoteLib_RequestStocks(1, strStock)
            if ncode==0:
                print(strStock, 'requested')    
    
    def OnNotifyQuote(self, sMarket, sIndex):
        ts=sk.SKSTOCK()
        skQ.SKQuoteLib_GetStockByIndex(sMarket, sIndex, ts)
        print(ts.bstrStockName, ts.bstrStockNo,  ts.nClose/10**ts.sDecimal)
    
class skR_events:
    def OnReplyMessage(self, bstrUserID, bstrMessage, sConfirmCode=0xFFFF):
        print('OnReplyMessage', bstrMessage)
        return sConfirmCode
                            
#Event sink, 事件實體  
EventQ=skQ_events()
EventR=skR_events()

#make connection to event sink
ConnectionQ = GetEvents(skQ, EventQ)
ConnectionR = GetEvents(skR, EventR)

if __name__=='__main__':
    #登入
    nCode=skC.SKCenterLib_Login(ID, PW)
    print("Login,", skC.SKCenterLib_GetReturnCodeMessage(nCode))
    #連線報價主機
    print(f"{datetime.now()}, EnterMonitor,", skC.SKCenterLib_GetReturnCodeMessage(skQ.SKQuoteLib_EnterMonitor()))
    # Event loop, 持續檢查有沒有event產生
    pythoncom.PumpMessages()



輸出結果,大概類似下圖,夾雜很多 fixme的警告,但不影響使用







2017年8月1日 星期二

comtypes error 'tuple' object has no attribute '__ctypes_from_outparam__'

最近使用comtypes 需要使用到pythoncom功能,可以用
pip install pypiwin32

這樣就會附帶安裝pythoncom了

#2018年更新,這個問題在 comtypes 1.1.4 版中已經被修正了
使用comtypes 有時候會出現

將 comtypes 裡的 __init__.py  第 658 行程式碼做些修改可以修正這個bug
路徑大概類似這樣
~python\Lib\site-packages\comtypes\__init__.py
    657             # be iterable.
    658             if len(outargs) == 1:  # rescode is not iterable
--> 659                 return rescode.__ctypes_from_outparam__()
    660 
    661             rescode = list(rescode)
改成
            # be iterable.
            if len(outargs) == 1:  # rescode is not iterable
                try:
                    return rescode.__ctypes_from_outparam__()
                except: #somtimes rescode is tuple
                    return rescode

            rescode = list(rescode)

即可修正


Ref:



2017年7月31日 星期一

用 pywin32 COM makpy utiltiy 找群益api com 元件 GUID 碼

安裝pywin32
裝好後會在 python目錄/lib/site-pacjages/pythonwin 下看到一個 pythonwin IDE,執行後如下
選擇 Tools => COM makpy Uitility => Select library 選SKCOMlib後,會在 ~/lib\site-packages\win32com\gen_py\ 產生一長串檔名的.py
打開後找 class ISKCenterLib, ISKOSQuoteLib 等的 coclass_clsid,像是SKCenterLib的class_clsid是 
{AC30BAB5-194A-4515-A8D3-6260749F8577}
之後就可以用這些class_clsid 給 comtypes.client.CreatObject 串接COM元件,請參考


群益 API in pyhton 報價範例

群益api更新至2.13.8後,改一下順序,先呼叫 SKReplyLib_ConnectByID,再 EnterMonitor,才會可以收到 OnConnection 3003 的回報,然後就可以求報價了。本範例示範如何用python 接群益api的報價,希望能拋磚引玉, 讓更多能手參與。小弟python自學,COM 元件的運作也是一知半解,東拼西湊下還算可用,程式碼很醜請見諒。過程遇到一些錯誤,很多我無法解決,以下是我試誤各種組合後, 可以運作的一組方法,,若有更好的解法,請各位大大多加分享。

另外是事件處理,我用comtypes 中的 showevents 也有錯誤產生,於是用 pyhoncom.PumpWaitingMessages() 來代替

使用comtpyes 若出現
"comtypes com object's method returns: 'tuple' object has no attribute 'ctypes_from_outparam'",需要做一些修正 ,請參考
https://easontseng.blogspot.com/2017/07/install-pythoncom-tuple-object-has-no.html
#這個bug好像在最新版的comtypes中被修正了

弄個FB社團,大家來討論好了。
https://www.facebook.com/groups/1805224676441902/

運行環境:
winXP, Anaconda python3.4 32 bit, CapitalAPI 2.13.8, pywin32 build221, comtypes 1.1.3

參考文獻:
#pythoncom 是安裝 pywin32 附帶的
https://sourceforge.net/projects/pywin32/
#用pywin makepy utility 找 GUID
https://easontseng.blogspot.tw/2017/07/pywin32-com-makpy-utiltiy-api-com.html
#python 元大期貨 api,
http://hlfutures.blogspot.tw/2016/08/api-in-python_8.html

#comtpyes:
https://pythonhosted.org/comtypes/
http://starship.python.net/crew/theller/comtypes/
https://easontseng.blogspot.tw/2017_07_07_archive.html

###########################################################
# pythoncom 是安裝 pywin32 附的,如果 event 沒反應才需要用到
# 第一次使用cc.GetModule, 會在 comtypes\gen\ 下產生幾個.py檔,像是
# _75AAD71C_8F4F_4F1F_9AEE_3D41A8C9BA5E_0_1_0.py
# 如果有 dll 錯誤產生,請檢查 SKCOM.dll 路徑有沒有錯誤
# 如果發生 gen 下沒有 SKCOMLib 錯誤訊息,請將python關閉
# AttributeError: module 'comtypes.gen.SKCOMLib' has no attribute 'SKCenterLib'
# 重新啟動python後,應該就可以找到了
# 如果還是不行,請重新安裝 capital API 元件,注意 API 與python 版本要一致,
# API安裝要以管理員權限執行

# 20190506,程式碼簡化
import pythoncom, time
import comtypes.client as cc

cc.GetModule('C:\\SKCOM\\x86\\SKCOM.dll')
import comtypes.gen.SKCOMLib as sk

ts=sk.SKSTOCK()

skC=cc.CreateObject(sk.SKCenterLib,interface=sk.ISKCenterLib)
skQ=cc.CreateObject(sk.SKQuoteLib,interface=sk.ISKQuoteLib)

#Some Configure
ID='身分證'
PW='密碼'

#想取得報價的股票代碼
strStocks='TSEA'

#define functions
def getStock(nMarket, nIndex, ts):
    skQ.SKQuoteLib_GetStockByIndex(nMarket, nIndex, ts)
    print(ts.bstrStockName, ts.bstrStockNo,  ts.nClose/10**ts.sDecimal)

#建立事件類別
class skQ_events:
    def OnConnection(self, nKind, nCode):
        if nCode == 0 :
            if nKind == 3001 :
                print("skQ OnConnection, nkind= ", nKind)
            elif (nKind == 3003):
                #等到回報3003 確定連線報價伺服器成功後,才登陸要報價的股票
                skQ.SKQuoteLib_RequestStocks(1, strStocks)
                print("skQ OnConnection, request stocks, nkind= ", nKind)
    def OnNotifyQuote(self, sMarketNo, sStockIdx):
        getStock(sMarketNo, sStockIdx, ts)

#Event sink
EventQ=skQ_events()
#make connection to event sink
ConnectionQ = cc.GetEvents(skQ, EventQ)        

#Login
print("Login,", skC.SKCenterLib_GetReturnCodeMessage(skC.SKCenterLib_Login(ID,PW)))
time.sleep(1)

#登錄報價伺服器
print("EnterMonitor,", skC.SKCenterLib_GetReturnCodeMessage(skQ.SKQuoteLib_EnterMonitor()))
#每秒 pump event 一次,這裡示範15秒

for i in range(15):
    time.sleep(1)
    pythoncom.PumpWaitingMessages()