資料包裝在接收資料結構中,其中m_pReportV3指向RCV_REPORT_STRUCTExV3記錄數組。 JSL 可以使用窺視載入 DLL 並解析二進位數據,而 Python 可以透過ctypes處理 Windows 訊息循環。
Challenge
JSL 本身無法建立 Windows 訊息循環,因此我整合了 Python 來管理它。 DLL 使用視窗句柄和訊息 ID 進行初始化,然後將資料傳送到該視窗。我需要在 JSL 中解析這些資料並用指定的欄位填充 JMP 表。
My Proposed Solution
下面是我的實作。它使用 JSL 定義表和解析數據,使用 Python 處理訊息循環。我已添加評論來解釋每個步驟。您能否審查一下並提出改進建議?
// StockReceiver.jsl
// Create a JMP data table with required columns
dt = New Table("Stock Data",
New Column("Market", Character, Width(4)), // Market identifier (e.g., 'HS')
New Column("Code", Character, Width(10)), // Stock code
New Column("Name", Character, Width(32)), // Stock name
New Column("Time", Numeric, Format("yyyy-mm-dd hh:mm:ss")), // Transaction time
New Column("LastClose", Numeric, Format("Fixed Dec", 12, 2)), // Previous close
New Column("Open", Numeric, Format("Fixed Dec", 12, 2)), // Opening price
New Column("High", Numeric, Format("Fixed Dec", 12, 2)), // Highest price
New Column("Low", Numeric, Format("Fixed Dec", 12, 2)), // Lowest price
New Column("NewPrice", Numeric, Format("Fixed Dec", 12, 2)), // Latest price
New Column("Volume", Numeric, Format("Fixed Dec", 12, 2)), // Volume
New Column("Amount", Numeric, Format("Fixed Dec", 12, 2)), // Amount
New Column("BuyPrice1", Numeric, Format("Fixed Dec", 12, 3)), // Bid price 1
New Column("BuyVolume1", Numeric, Format("Fixed Dec", 12, 2)),// Bid volume 1
New Column("SellPrice1", Numeric, Format("Fixed Dec", 12, 3)),// Ask price 1
New Column("SellVolume1", Numeric, Format("Fixed Dec", 12, 2)),// Ask volume 1
New Column("BuyPrice2", Numeric, Format("Fixed Dec", 12, 3)), // Bid price 2
New Column("BuyVolume2", Numeric, Format("Fixed Dec", 12, 2)),// Bid volume 2
New Column("SellPrice2", Numeric, Format("Fixed Dec", 12, 3)),// Ask price 2
New Column("SellVolume2", Numeric, Format("Fixed Dec", 12, 2)),// Ask volume 2
New Column("BuyPrice3", Numeric, Format("Fixed Dec", 12, 3)), // Bid price 3
New Column("BuyVolume3", Numeric, Format("Fixed Dec", 12, 2)),// Bid volume 3
New Column("SellPrice3", Numeric, Format("Fixed Dec", 12, 3)),// Ask price 3
New Column("SellVolume3", Numeric, Format("Fixed Dec", 12, 2)),// Ask volume 3
New Column("BuyPrice4", Numeric, Format("Fixed Dec", 12, 3)), // Bid price 4
New Column("BuyVolume4", Numeric, Format("Fixed Dec", 12, 2)),// Bid volume 4
New Column("SellPrice4", Numeric, Format("Fixed Dec", 12, 3)),// Ask price 4
New Column("SellVolume4", Numeric, Format("Fixed Dec", 12, 2)),// Ask volume 4
New Column("BuyPrice5", Numeric, Format("Fixed Dec", 12, 3)), // Bid price 5
New Column("BuyVolume5", Numeric, Format("Fixed Dec", 12, 2)),// Bid volume 5
New Column("SellPrice5", Numeric, Format("Fixed Dec", 12, 3)),// Ask price 5
New Column("SellVolume5", Numeric, Format("Fixed Dec", 12, 2)) // Ask volume 5
);
// Global variables for managing the receiver
global:hwnd = 0; // Window handle
global:dll = 0; // DLL object
global:running = 1; // Flag to control the message loop
// Function to parse RCV_DATA and extract RCV_REPORT_STRUCTExV3 fields
ParseStockData = Function({lParam},
// Check the data type (RCV_REPORT = 0x3f001234)
dataType = Peek(lParam, 0, "int");
If(dataType == 1061153620, // RCV_REPORT
// Get number of records and pointer to report data
packetNum = Peek(lParam, 4, "int"); // m_nPacketNum
reportPtr = Peek(lParam, 276, "int"); // m_pReportV3 offset
// Process each record
For(i = 0, i < packetNum, i++,
baseOffset = reportPtr + i * Peek(reportPtr, 0, "short"); // Offset based on m_cbSize
// Extract fields from RCV_REPORT_STRUCTExV3
time = Peek(baseOffset, 2, "int"); // m_time (time_t)
market = Peek(baseOffset, 6, "short"); // m_wMarket
code = Peek(baseOffset, 8, "char[10]"); // m_szLabel
name = Peek(baseOffset, 18, "char[32]"); // m_szName
lastClose = Peek(baseOffset, 50, "float"); // m_fLastClose
open = Peek(baseOffset, 54, "float"); // m_fOpen
high = Peek(baseOffset, 58, "float"); // m_fHigh
low = Peek(baseOffset, 62, "float"); // m_fLow
newPrice = Peek(baseOffset, 66, "float"); // m_fNewPrice
volume = Peek(baseOffset, 70, "float"); // m_fVolume
amount = Peek(baseOffset, 74, "float"); // m_fAmount
buyPrice1 = Peek(baseOffset, 78, "float"); // m_fBuyPrice[0]
buyPrice2 = Peek(baseOffset, 82, "float"); // m_fBuyPrice[1]
buyPrice3 = Peek(baseOffset, 86, "float"); // m_fBuyPrice[2]
buyPrice4 = Peek(baseOffset, 126, "float"); // m_fBuyPrice4
buyPrice5 = Peek(baseOffset, 134, "float"); // m_fBuyPrice5
buyVolume1 = Peek(baseOffset, 90, "float"); // m_fBuyVolume[0]
buyVolume2 = Peek(baseOffset, 94, "float"); // m_fBuyVolume[1]
buyVolume3 = Peek(baseOffset, 98, "float"); // m_fBuyVolume[2]
buyVolume4 = Peek(baseOffset, 130, "float"); // m_fBuyVolume4
buyVolume5 = Peek(baseOffset, 138, "float"); // m_fBuyVolume5
sellPrice1 = Peek(baseOffset, 102, "float"); // m_fSellPrice[0]
sellPrice2 = Peek(baseOffset, 106, "float"); // m_fSellPrice[1]
sellPrice3 = Peek(baseOffset, 110, "float"); // m_fSellPrice[2]
sellPrice4 = Peek(baseOffset, 142, "float"); // m_fSellPrice4
sellPrice5 = Peek(baseOffset, 150, "float"); // m_fSellPrice5
sellVolume1 = Peek(baseOffset, 114, "float"); // m_fSellVolume[0]
sellVolume2 = Peek(baseOffset, 118, "float"); // m_fSellVolume[1]
sellVolume3 = Peek(baseOffset, 122, "float"); // m_fSellVolume[2]
sellVolume4 = Peek(baseOffset, 146, "float"); // m_fSellVolume4
sellVolume5 = Peek(baseOffset, 154, "float"); // m_fSellVolume5
// Convert time_t to readable format (milliseconds for JMP)
timeStr = Format(DateTime(time * 1000), "yyyy-mm-dd hh:mm:ss");
// Add row to table
dt << Add Rows(1);
row = N Rows(dt);
:Market[row] = Char(market); // Convert market code to string
:Code[row] = code;
:Name[row] = name;
:Time[row] = timeStr;
:LastClose[row] = lastClose;
:Open[row] = open;
:High[row] = high;
:Low[row] = low;
:NewPrice[row] = newPrice;
:Volume[row] = volume;
:Amount[row] = amount;
:BuyPrice1[row] = buyPrice1;
:BuyPrice2[row] = buyPrice2;
:BuyPrice3[row] = buyPrice3;
:BuyPrice4[row] = buyPrice4;
:BuyPrice5[row] = buyPrice5;
:BuyVolume1[row] = buyVolume1;
:BuyVolume2[row] = buyVolume2;
:BuyVolume3[row] = buyVolume3;
:BuyVolume4[row] = buyVolume4;
:BuyVolume5[row] = buyVolume5;
:SellPrice1[row] = sellPrice1;
:SellPrice2[row] = sellPrice2;
:SellPrice3[row] = sellPrice3;
:SellPrice4[row] = sellPrice4;
:SellPrice5[row] = sellPrice5;
:SellVolume1[row] = sellVolume1;
:SellVolume2[row] = sellVolume2;
:SellVolume3[row] = sellVolume3;
:SellVolume4[row] = sellVolume4;
:SellVolume5[row] = sellVolume5;
);
Return(1); // Message processed
,
Return(0); // Unknown data type, not processed
);
);
// Load the DLL
dll = Load DLL("I:\DZH\HailiLive\StockDrv.dll");
If(Is Empty(dll),
Throw("Failed to load StockDrv.dll");
);
global:dll = dll;
// Run Python code to handle Windows message loop
Run Python(
"
import ctypes
from ctypes import wintypes
import jmp
# Constants for message handling
WM_APP = 0x8000
My_Msg_StkData = WM_APP + 1
RCV_REPORT = 0x3f001234
# Windows API type definitions
HWND = wintypes.HWND
UINT = wintypes.UINT
WPARAM = wintypes.WPARAM
LPARAM = wintypes.LPARAM
WNDPROC = ctypes.WINFUNCTYPE(ctypes.c_long, HWND, UINT, WPARAM, LPARAM)
# Load user32.dll for Windows API calls
user32 = ctypes.WinDLL('user32')
# Define WNDCLASSEX structure for window registration
class WNDCLASSEX(ctypes.Structure):
_fields_ = [
('cbSize', UINT),
('style', UINT),
('lpfnWndProc', WNDPROC),
('cbClsExtra', ctypes.c_int),
('cbWndExtra', ctypes.c_int),
('hInstance', wintypes.HINSTANCE),
('hIcon', wintypes.HICON),
('hCursor', wintypes.HCURSOR),
('hbrBackground', wintypes.HBRUSH),
('lpszMenuName', wintypes.LPCWSTR),
('lpszClassName', wintypes.LPCWSTR),
('hIconSm', wintypes.HICON)
]
# Window procedure to handle messages
@WNDPROC
def WndProc(hWnd, msg, wParam, lParam):
if msg == My_Msg_StkData and wParam == RCV_REPORT:
# Call JSL to process the data
result = jmp.run_jsl('ParseStockData(' + str(lParam) + ');')
return result
return user32.DefWindowProcW(hWnd, msg, wParam, lParam)
# Get global variables from JSL
hInstance = ctypes.windll.kernel32.GetModuleHandleW(None) # Current module handle
dll = jmp.get_global('dll') # DLL object
running = jmp.get_global('running') # Loop control flag
# Register window class
wcex = WNDCLASSEX()
wcex.cbSize = ctypes.sizeof(WNDCLASSEX)
wcex.style = 0x3 # CS_HREDRAW | CS_VREDRAW
wcex.lpfnWndProc = WNDPROC(WndProc)
wcex.cbClsExtra = 0
wcex.cbWndExtra = 0
wcex.hInstance = hInstance
wcex.hIcon = 0
wcex.hCursor = 0
wcex.hbrBackground = 0
wcex.lpszMenuName = None
wcex.lpszClassName = 'StockDataWindow'
wcex.hIconSm = 0
if not user32.RegisterClassExW(ctypes.byref(wcex)):
raise Exception('Failed to register window class')
# Create window
hWnd = user32.CreateWindowExW(
0, 'StockDataWindow', 'Stock Receiver', 0,
0, 0, 100, 100, 0, 0, hInstance, None
)
if not hWnd:
raise Exception('Failed to create window')
jmp.set_global('hwnd', hWnd) # Store window handle in JSL
# Initialize the DLL
stock_init = dll.Stock_Init
stock_init.argtypes = [HWND, UINT, ctypes.c_int]
stock_init.restype = ctypes.c_int
result = stock_init(hWnd, My_Msg_StkData, 4) # 4 = RCV_WORK_SENDMSG
if result != 1:
raise Exception('Stock_Init failed: ' + str(result))
# Message loop
msg = ctypes.wintypes.MSG()
while running[0]:
if user32.GetMessageW(ctypes.byref(msg), 0, 0, 0) > 0:
user32.TranslateMessage(ctypes.byref(msg))
user32.DispatchMessageW(ctypes.byref(msg))
else:
break
# Cleanup
stock_quit = dll.Stock_Quit
stock_quit.argtypes = [HWND]
stock_quit.restype = ctypes.c_int
stock_quit(hWnd)
user32.DestroyWindow(hWnd)
user32.UnregisterClassW('StockDataWindow', hInstance)
print('Stock data receiver stopped')
",
"Stock data receiver started" // Output when Python starts
);
// Function to stop the receiver
StopReceiver = Function({},
global:running = 0; // Signal Python to exit loop
If(global:hwnd != 0,
Call DLL("Stock_Quit", dll, global:hwnd); // Cleanup DLL
global:hwnd = 0;
);
Write("Stock data receiver stopped.\!n");
);
// Display startup message
Write("Stock data receiver initialized. Run StopReceiver() to stop.\!n");關鍵細節
- DLL函數:
- Stock_Init(HWND hWnd,UINT Msg,int nWorkMode) :初始化 DLL,成功時回傳 1。
- Stock_Quit(HWND hWnd) :停止訊息傳送,成功時傳回 1。
- 資料結構偏移:
- 接收訊息: m_pReportV3在位元組 276 處(在m_wDataType 、 m_nPacketNum 、 m_文件 、 磁碟管理之後)。
- RCV_REPORT_STRUCTExV3 :與#pragma pack(1)對齊的字段,例如m_時間為 2、 m_fBuyPrice5為 134、 m_fSellVolume5為 154。
- 執行:
- JSL 載入 DLL 並定義表。
- Python 建立一個窗口,初始化 DLL,並執行訊息循環。
- 訊息觸發解析股票數據來填入表。
您的問題
- 使用窺視的二進位解析對於偏移量是否正確?我假設由於#pragma pack(1)而沒有填充。
- 訊息循環是否可以針對高頻資料進行最佳化?
- 我是否應該添加緩衝來減少表更新(例如每 100 行)?
我將非常感激您對潛在問題、效能增強或替代方法的回饋。先感謝您的專業知識!
此致敬意,謝謝專家!