最新消息

[公告2014/05/30] 如有需要將部落格中,任何一篇文章的程式碼使用在商業用途,請與我聯繫。

[公告2015/04/26] Line版的 iInfo程式與投資應用 群組已上線想加入的朋友們,請先查看 "入群須知" 再與我聯繫 Line : aminwhite5168,加入請告知身分與回答 "入群須知" 的問題。

[公告2018/04/22] 台北 Python + Excel VBA 金融資訊爬蟲課程,課程如網頁內容 金融資訊爬蟲班:台北班 Python 金融資訊爬蟲、EXCEL VBA 金融資訊爬蟲

[公告2019/01/08] 請注意:我再次重申,部落格文章的程式碼,是要提供各位參考與學習,一旦網頁改版請自行修改,別要求東要求西要我主動修改,你們用我寫東西賺錢了、交差了,請問有分我一杯羹嗎?既然賺錢沒分我,請問有什麼理由要求我修改,如果沒能力改,就花錢來找我上課。

[公告2019/12/01] 若各位有 Excel VBA 案子開發需求,歡迎與我聯繫,可接案處理。

[公告2020/05/22] 頁面載入速度慢,起因為部分JS來源(alexgorbatchev.com)失效導致頁面載入變慢,目前已做調整,請多見諒。

2017年3月11日 星期六

MultiCharts 呼叫外部 DLL ,DLL覆寫資料問題

近期使用 VC++ 開發 MultiCharts 使用的DLL,遇到當2個策略或指標同時使用同一個DLL抓取資料,第一個先執行的策略或指標產生的資料,被第二個後執行的策略或指標產生的資料所覆蓋,這問題後來筆者用了一個簡單的方式克服了這個問題,順道分享一下。
在 MultiCharts 使用函數、指標、策略呼叫DLL,執行狀態設為on,MultiCharts 會將 DLL函數Lock並且將資料Keep於記憶體中,直到使用狀態改為off,才將DLL函數UnLock與釋放記憶體空間。
這樣在執行單一函數、指標、策略下可正常運作看不出任何問題,但是同時執行2個以上的函數、指標、策略就會發生資料覆蓋的問題,對 MultiCharts 來說DLL是單一資源,其所使用CPU、IO、Memory、Network等資源都是單一,如果多個函數、指標、策略同時執行存取單一資源,會發生資源使用權互搶,看誰先搶到誰就先執行,就這是所謂的競賽現象,後執行的就會覆蓋先執行的資料,這樣導致MultiCharts 發生未知的錯誤。

為了解決以上資源共用的問題,筆者在DLL中加了一個小技巧來處理這樣的問題,就是將每次DLL產生的資料存在一個專屬空間中,並給於一個ID,後續要存取這部分資料時,只要帶入這個ID即可取得對應的完整資料,如此一來解決了多個函數、指標、策略執行時資料會被覆蓋的問題。
這裡舉一個例子做說明,使用VC++ 2010實作一個DLL,以讀取外部檔案資料並傳給MultiCharts為主。
架構圖如下:

建立一個名為 FileInfo 專案的DLL,在 FileInfo.cpp 中輸入以下程式碼。
// FileInfo.cpp : Defines the initialization routines for the DLL.
//

#include "stdafx.h"
#include "FileInfo.h"

#ifdef _DEBUG
#define new DEBUG_NEW
#endif

// CFileInfoApp

BEGIN_MESSAGE_MAP(CFileInfoApp, CWinApp)
END_MESSAGE_MAP()

// CFileInfoApp construction
OI g_OI;

CFileInfoApp::CFileInfoApp()
{
 // TODO: add construction code here,
 // Place all significant initialization in InitInstance
}

// The one and only CFileInfoApp object

CFileInfoApp theApp;

// CFileInfoApp initialization

BOOL CFileInfoApp::InitInstance()
{
 CWinApp::InitInstance();

 return TRUE;
}

int APIENTRY ReadFileText(CHAR * FilebName)
{
 return g_OI.Init(FilebName);
}

int APIENTRY GetTextCount(int nIndex)
{
 return g_OI.GetCount(nIndex);
}

char* APIENTRY GetDataText(int nIndex, int nNum)
{
 return g_OI.GetFileData(nIndex, nNum); 
}

int APIENTRY ClearSpace(int nIndex)
{
 return g_OI.Release(nIndex);
}
在FileInfo.h中輸入以下程式碼。
// FileInfo.h : main header file for the FileInfo DLL
//

#pragma once

#ifndef __AFXWIN_H__
 #error "include 'stdafx.h' before including this file for PCH"
#endif

#include "resource.h"  // main symbols
#include "OI.h"

// CFileInfoApp
// See FileInfo.cpp for the implementation of this class
//

class CFileInfoApp : public CWinApp
{
public:
 CFileInfoApp();

// Overrides
public:
 virtual BOOL InitInstance();

 DECLARE_MESSAGE_MAP()
};
在FileInfo.def輸入以下程式碼。
; FileInfo.def : Declares the module parameters for the DLL.

LIBRARY

EXPORTS
    ; Explicit exports can go here
 ReadFileText @1
 GetTextCount @2
 GetDataText @3
 ClearSpace @4
建立OI class,在OI.cpp中輸入以下程式碼。
// OI.cpp : implementation file
//

#include "stdafx.h"
#include "FileInfo.h"
#include "OI.h"

static HANDLE g_hMutex = CreateMutex(NULL, FALSE, NULL);
static vector <INFO*> g_List;
static int g_nIndex = 0;

OI::OI()
{

}

OI::~OI()
{
}


// OI message handlers

INFO *OI::FindData(int index)
{
 INFO *info = NULL;

 if(index == 0) return NULL;

 for (int i = 0 ; i < (int) g_List.size() ; i++)
 {
  info = g_List.at(i);
  if (info && info->index == index)
   break;
 }

 return info;
}

BOOL OI::ReadText(CHAR * FilebName, int nIndex)
{
 CHAR line[MAX_SIZE];
 fstream fin;

 fin.open(FilebName, ios::in);
 if(!fin) return FALSE;

 INFO * info = FindData(nIndex);
 if(!info) return FALSE;

 while(fin.getline(line, sizeof(line), '\n'))
  info->Data.Add(line);

 fin.close();
 return TRUE;
}

int OI::Init(CHAR * FilebName) 
{
 DLLMutexLock();
 int nIndex;
 
 //建立專屬空間與給予ID
 INFO * Info = new INFO();
 Info->index = ++g_nIndex;
 nIndex = Info->index;

 //將空間存入一個ID序列
 g_List.push_back(Info);

 //取得資料存入該空間
 ReadText(FilebName, nIndex);
 DLLMutexUnLock();
 return nIndex;
}

int OI::GetCount(int nIndex)
{
 DLLMutexLock();
 INFO * info = FindData(nIndex);
 if(!info)
 {
  DLLMutexUnLock();
  return 0;
 }
 DLLMutexUnLock();
 return info->Data.GetUpperBound() + 1;
}

CHAR * OI::GetFileData(int nIndex, int nNum)
{
 DLLMutexLock();
 CString csTemp, cs;
 INFO * info = FindData(nIndex);
 if(!info)
 {
  DLLMutexUnLock();
  return 0;
 }
 csTemp = info->Data.GetAt(nNum);
 cs.Format("nIndex = %d, nNum = %d, %s", nIndex, nNum, csTemp);
 OutputDebugString(cs);
 csTemp.Trim();
 DLLMutexUnLock();
 return (char*)(LPCTSTR)csTemp;
}

int OI::Release(int index)
{
 int ret = 0;

 DLLMutexLock();
 for (int i = 0 ; i < g_List.size() ; i++)
 {
  INFO *info = g_List.at(i);
  if (info && info->index == index)
  {
   g_List.erase(g_List.begin() + i);
   delete info;
   ret = 1;
   break;
  }

 }
 DLLMutexUnLock();

 return ret;
}

void OI::DLLMutexLock()
{
 WaitForSingleObject(g_hMutex, INFINITE);
}

void OI::DLLMutexUnLock()
{
 ReleaseMutex(g_hMutex);
}
在OI.h輸入以下程式碼。
#pragma once

#include <fstream>
#include <iostream>
#include <vector> 

using namespace std;

#define MAX_SIZE 100

typedef struct{
 UINT index;
 CArray <CString, CString> Data;
}INFO;

class OI : public CWnd
{
public:
 OI();
 virtual ~OI(); 
 int Init(CHAR * FilebName);
 int GetCount(int nIndex);
 CHAR * GetFileData(int nIndex, int nNum);
 int Release(int index);
private:
 BOOL ReadText(CHAR * FilebName, int nIndex);
 INFO *FindData(int index);
 void DLLMutexLock();
 void DLLMutexUnLock();
};
在MultiCharts 的PLE中建立一個名為Readfile函數,回傳數值資料。
Inputs : 
   istrFileName(StringSimple), 
   iastrData[x,y](StringArrayRef);
   
var:   ii(0),
   vIndex(0),
   vCount(0),
   vRecord(Spaces(100));

DefineDLLFunc: "C:\Code\FileInfo\Release\FileInfo.dll", int, "ReadFileText", lpstr;
DefineDLLFunc: "C:\Code\FileInfo\Release\FileInfo.dll", int, "GetTextCount", int;
DefineDLLFunc: "C:\Code\FileInfo\Release\FileInfo.dll", lpstr, "GetDataText", int, int;
DefineDLLFunc: "C:\Code\FileInfo\Release\FileInfo.dll", int, "ClearSpace", int;

vIndex = ReadFileText(istrFileName);
vCount = GetTextCount(vIndex);

for ii = 0 to vCount - 1 begin
 vRecord = GetDataText(vIndex, ii); 
 iastrData[ii, 1] = Midstr(vRecord, 0, InStr(vRecord, ",") - 1);
 vRecord = Midstr(vRecord, InStr(vRecord, ",") + 1, strlen(vRecord));
 iastrData[ii, 2] = Midstr(vRecord, 0, InStr(vRecord, ",") - 1);
 vRecord = Midstr(vRecord, InStr(vRecord, ",") + 1, strlen(vRecord));
 iastrData[ii, 3] = vRecord; 
end;

ClearSpace(vIndex);

ReadFile = vCount;
建立名為Amin_Test6的指標。
input:iName("TXF");
vars:ii(0);
array:aData[800,3]("");

once cleardebug;

if currentbar = 1 then begin
 value1 = ReadFile("C:\Users\Amin\Desktop\TXF.csv", aData);
end;

for ii = 0 to value1 begin
 if d = JulianToDate(StringToDate(aData[ii, 1])) Then begin
  value2=StrToNum(aData[ii, 3]);
  break;
 end;
end;

plot1(value2, "TXF", iff(value2> 0, red, green));
建立名為Amin_Test7的指標。
input:iName("MXF");
vars:ii(0);
array:aData[800,3]("");

once cleardebug;

if currentbar = 1 then begin
 value1 = ReadFile("C:\Users\Amin\Desktop\MTX.csv", aData);
end;

for ii = 0 to value1 begin
 if d = JulianToDate(StringToDate(aData[ii, 1])) Then begin
  value2=StrToNum(aData[ii, 3]);
  break;
 end;
end;

plot1(value2, "MTX", iff(value2> 0, red, green));
資料格式:

執行結果:

透過Dbgview來看看DLL內執行的狀態。