Die Daten werden in eine RCV_DATA- Struktur eingebettet, wobei m_pReportV3 auf ein Array von RCV_REPORT_STRUCTExV3 -Datensätzen verweist. JSL kann die DLL laden und Binärdaten mit Spähen analysieren, während Python die Windows-Nachrichtenschleife über ctypes verarbeiten kann.
Challenge
JSL allein kann keine Windows-Nachrichtenschleife erstellen, daher habe ich Python integriert, um dies zu verwalten. Die DLL wird mit einem Fenster-Handle und einer Nachrichten-ID initialisiert und sendet dann Daten an dieses Fenster. Ich muss diese Daten in JSL analysieren und eine JMP-Tabelle mit den angegebenen Feldern füllen.
My Proposed Solution
Unten sehen Sie meine Implementierung. Sie verwendet JSL zur Definition der Tabelle und zum Parsen der Daten sowie Python zur Verarbeitung der Nachrichtenschleife. Ich habe Kommentare hinzugefügt, die jeden Schritt erklären. Könnten Sie sie bitte überprüfen und Verbesserungen vorschlagen?
// 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");Wichtige Details
- DLL-Funktionen :
- Stock_Init(HWND hWnd, UINT Msg, int nWorkMode) : Initialisiert die DLL, gibt bei Erfolg 1 zurück.
- Stock_Quit(HWND hWnd) : Stoppt das Senden von Nachrichten und gibt bei Erfolg 1 zurück.
- Datenstruktur-Offsets :
- RCV_DATA : m_pReportV3 bei Byte 276 (nach m_wDataType , m_nPacketNum , m_File , m_bDISK ).
- RCV_REPORT_STRUCTExV3 : Mit #pragma pack(1) ausgerichtete Felder, z. B. m_time bei 2, m_fBuyPrice5 bei 134, m_fSellVolume5 bei 154.
- Ausführung :
- JSL lädt die DLL und definiert die Tabelle.
- Python erstellt ein Fenster, initialisiert die DLL und führt die Nachrichtenschleife aus.
- Nachrichten lösen ParseStockData aus, um die Tabelle zu füllen.
Fragen an Sie
- Ist die Binäranalyse mit Spähen für die Offsets korrekt? Ich bin davon ausgegangen, dass aufgrund von #pragma pack(1) kein Padding vorhanden ist.
- Könnte die Nachrichtenschleife für hochfrequente Daten optimiert werden?
- Sollte ich eine Pufferung hinzufügen, um Tabellenaktualisierungen zu reduzieren (z. B. alle 100 Zeilen)?
Ich freue mich über Ihr Feedback zu möglichen Problemen, Leistungsverbesserungen oder alternativen Ansätzen. Vielen Dank im Voraus für Ihre Expertise!
Freundliche Grüße, Danke, Experten!