|
|
|
|
@ -2,6 +2,7 @@
@@ -2,6 +2,7 @@
|
|
|
|
|
|
|
|
|
|
module QuoteSource.DDE ( |
|
|
|
|
initializeDde, |
|
|
|
|
destroyDde, |
|
|
|
|
DdeState, |
|
|
|
|
DdeCallback, |
|
|
|
|
ddeResultAck, |
|
|
|
|
@ -11,7 +12,6 @@ module QuoteSource.DDE (
@@ -11,7 +12,6 @@ module QuoteSource.DDE (
|
|
|
|
|
ddeXtypPoke, |
|
|
|
|
ddeCpWinAnsi, |
|
|
|
|
queryString, |
|
|
|
|
nullDdeState, |
|
|
|
|
accessData, |
|
|
|
|
unaccessData, |
|
|
|
|
withDdeData |
|
|
|
|
@ -29,8 +29,11 @@ import Control.Applicative
@@ -29,8 +29,11 @@ import Control.Applicative
|
|
|
|
|
import Control.Exception |
|
|
|
|
import Control.Monad |
|
|
|
|
import Data.Bits |
|
|
|
|
import Data.Binary.Get |
|
|
|
|
import Data.Typeable |
|
|
|
|
import Data.ByteString hiding (map) |
|
|
|
|
import Data.IORef |
|
|
|
|
import QuoteSource.XlParser |
|
|
|
|
import System.Win32.DLL |
|
|
|
|
import System.Win32.Types |
|
|
|
|
import Foreign |
|
|
|
|
@ -38,6 +41,8 @@ import Foreign.C.Types
@@ -38,6 +41,8 @@ import Foreign.C.Types
|
|
|
|
|
import Foreign.C.String |
|
|
|
|
import Foreign.Marshal.Array |
|
|
|
|
|
|
|
|
|
import qualified Data.ByteString.Lazy as BL |
|
|
|
|
|
|
|
|
|
data DdeException = ApiError String |
|
|
|
|
deriving (Show, Typeable) |
|
|
|
|
|
|
|
|
|
@ -92,31 +97,76 @@ foreign import WINDOWS_CCONV "wrapper"
@@ -92,31 +97,76 @@ foreign import WINDOWS_CCONV "wrapper"
|
|
|
|
|
|
|
|
|
|
data DdeState = DdeState { |
|
|
|
|
ddeInstance :: DWORD, |
|
|
|
|
appName :: HANDLE, |
|
|
|
|
topicName :: HANDLE, |
|
|
|
|
callback :: FunPtr DdeCallback |
|
|
|
|
appName :: String, |
|
|
|
|
topic :: String, |
|
|
|
|
appNameHandle :: HANDLE, |
|
|
|
|
topicHandle :: HANDLE, |
|
|
|
|
callback :: FunPtr DdeCallback, |
|
|
|
|
dataCallback :: DdeDataCallback |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
nullDdeState = DdeState { ddeInstance = 0, appName = nullPtr, topicName = nullPtr, callback = nullFunPtr } |
|
|
|
|
|
|
|
|
|
type DdeDataCallback = String -> (Int, Int, [XlData]) -> IO Bool |
|
|
|
|
type DdeCallback = CUInt -> CUInt -> HANDLE -> HANDLE -> HANDLE -> HANDLE -> LPDWORD -> LPDWORD -> IO HANDLE |
|
|
|
|
|
|
|
|
|
initializeDde :: String -> String -> DdeCallback -> IO DdeState |
|
|
|
|
initializeDde appName topicName callback = alloca (\instancePtr -> do |
|
|
|
|
cb <- mkCallbackPtr callback |
|
|
|
|
{-| |
|
|
|
|
- Callback for DDE messages |
|
|
|
|
- DdeState is wrapped in IORef, because this callback should be passed to ddeInitialize, which in turn returns DDE handle |
|
|
|
|
-} |
|
|
|
|
ddeCallback :: IORef DdeState -> CUInt -> CUInt -> HANDLE -> HANDLE -> HANDLE -> HANDLE -> LPDWORD -> LPDWORD -> IO HANDLE |
|
|
|
|
ddeCallback state msgType format hConv hsz1 hsz2 hData dwData1 dwData2 |
|
|
|
|
| msgType == ddeXtypConnect = handleConnect state hsz1 hsz2 |
|
|
|
|
| msgType == ddeXtypPoke = handlePoke state hsz1 hData |
|
|
|
|
| otherwise = return nullHANDLE -- Do not handle other messages, they are boring |
|
|
|
|
where |
|
|
|
|
handleConnect state hsz1 hsz2 = do |
|
|
|
|
myDdeState <- readIORef state |
|
|
|
|
maybeAppName <- queryString myDdeState 256 hsz2 |
|
|
|
|
case maybeAppName of |
|
|
|
|
Just incomingAppName -> do |
|
|
|
|
return $ if incomingAppName == appName myDdeState |
|
|
|
|
then ddeResultTrue |
|
|
|
|
else ddeResultFalse |
|
|
|
|
Nothing -> return ddeResultFalse |
|
|
|
|
|
|
|
|
|
handlePoke state hsz1 hData = do |
|
|
|
|
myDdeState <- readIORef state |
|
|
|
|
maybeTopic <- queryString myDdeState 256 hsz1 |
|
|
|
|
case maybeTopic of |
|
|
|
|
Nothing -> return ddeResultFalse |
|
|
|
|
Just topic -> withDdeData hData (\xlData -> case runGetOrFail xlParser $ BL.fromStrict xlData of |
|
|
|
|
Left (_, _, errmsg) -> return ddeResultFalse |
|
|
|
|
Right (_, _, table) -> do |
|
|
|
|
rc <- (dataCallback myDdeState) topic table |
|
|
|
|
return $ if rc |
|
|
|
|
then ddeResultAck |
|
|
|
|
else ddeResultFalse ) |
|
|
|
|
|
|
|
|
|
initializeDde :: String -> String -> DdeDataCallback -> IO (IORef DdeState) |
|
|
|
|
initializeDde appName topic callback = alloca (\instancePtr -> do |
|
|
|
|
ddeState <- newIORef $ DdeState { |
|
|
|
|
ddeInstance = 0, |
|
|
|
|
appName = appName, |
|
|
|
|
appNameHandle = nullHANDLE, |
|
|
|
|
topic = topic, |
|
|
|
|
topicHandle = nullHANDLE, |
|
|
|
|
callback = nullFunPtr, |
|
|
|
|
dataCallback = callback } |
|
|
|
|
cb <- mkCallbackPtr (ddeCallback ddeState) |
|
|
|
|
rc <- ddeInitialize instancePtr cb 0 0 |
|
|
|
|
instanceRaw <- peek instancePtr |
|
|
|
|
atomicModifyIORef' ddeState (\state -> (state { ddeInstance = instanceRaw, callback = cb }, ())) |
|
|
|
|
when (rc /= CUInt 0) $ throw $ ApiError "Unable to initialize DDE" |
|
|
|
|
|
|
|
|
|
withCString appName (\appNameRaw -> withCString topicName (\topicNameRaw -> do |
|
|
|
|
appNameHandle <- ddeCreateStringHandle instanceRaw appNameRaw ddeCpWinAnsi |
|
|
|
|
topicNameHandle <- ddeCreateStringHandle instanceRaw topicNameRaw ddeCpWinAnsi |
|
|
|
|
when (appNameHandle == nullHANDLE || topicNameHandle == nullHANDLE) $ throw $ ApiError "Unable to create strings handles" |
|
|
|
|
withCString appName (\appNameRaw -> withCString topic (\topicRaw -> do |
|
|
|
|
myAppNameHandle <- ddeCreateStringHandle instanceRaw appNameRaw ddeCpWinAnsi |
|
|
|
|
myTopicHandle <- ddeCreateStringHandle instanceRaw topicRaw ddeCpWinAnsi |
|
|
|
|
when (myAppNameHandle == nullHANDLE || myTopicHandle == nullHANDLE) $ throw $ ApiError "Unable to create strings handles" |
|
|
|
|
|
|
|
|
|
rc2 <- ddeNameService instanceRaw appNameHandle nullPtr 1 |
|
|
|
|
atomicModifyIORef' ddeState (\state -> (state { appNameHandle = myAppNameHandle, topicHandle = myTopicHandle }, ())) |
|
|
|
|
rc2 <- ddeNameService instanceRaw myAppNameHandle nullPtr 1 |
|
|
|
|
when (rc2 == nullHANDLE) $ throw $ ApiError $ "Unable to register application name: " ++ appName |
|
|
|
|
|
|
|
|
|
return DdeState { ddeInstance = instanceRaw, appName = appNameHandle, topicName = topicNameHandle, callback = cb } ))) |
|
|
|
|
return ddeState))) |
|
|
|
|
|
|
|
|
|
destroyDde :: DdeState -> IO () |
|
|
|
|
destroyDde state = do |
|
|
|
|
|