Comment transformer un projet C++ .exe en .DLL pour exporter des fonct

Comment transformer un projet C++ .exe en .DLL pour exporter des fonct - C++ - Programmation

Marsh Posté le 08-02-2014 à 21:25:09    

Bonjour,
Voilà je suis devant un problème qui m'empêche d'avancer, je vais aller step par step pour l'exposer correctement dans l’espérance de trouver de l'aide.
 
J'ai reçu le code source d'un programme exemple codé en C++ qui permet la communication avec un appareil, ce programme utilise des fonctions présentes dans une librairie .dll mais je n'ai pas la documentation de cette API pour pouvoir l'utiliser.
Mon but c'est de communiquer avec cette appareil en utilisant un programme qui sera fait avec Windev (Je sais utiliser WLangage de Windev si j'ai une librairie .dll à condition que cette librairie soit composée de fonctions simples e.g MaFonction(Param1,Parm2,...) et les paramètres sont de types usuels.).
 
Or les fonctions de l'API .dll utilisées dans l'exemple que j'ai pu le compiler et l'exécuter sous forme de .exe terminal ( code source que j'ai réussi à le compiler sous Visual Studio 10) utilise, hélas, des paramètres sous forme de structures dont WLangage ne peut pas les reproduire.
 
Quand j’exécute l'exemple que j'ai eu , j'arrive aisément à communiquer avec cet appareil et en plus, les fonctions présentes dans ce code ont une forme simple que si je les exporte dans une .dll je pourrais les utiliser dans mon programme Windev avec WLangage.  
 
Ma question est la suivante: Comment je pourrait exporter les fonctions appelées dans le "main" de ce programme C++ le transformé et le compiler sous forme de .dll?
voici le main de ce programme:

Code :
  1. #include <vector>
  2. #include <time.h>
  3. #include "IO.h"
  4. #include "CSMARTPayout.h"
  5. std::vector<string> GetComPorts(); // Returns a vector containing a list of all the available ports on the host
  6. bool ParseKeys(CPayout* payout); // The method responsible for parsing and responding to key presses
  7. void DisplayCommands(); // Displays a list of commands available for this SDK
  8. unsigned char FormatComPort(const string& comPort); // Remove the x. COM from the COM port string
  9. DWORD WINAPI PollThread(LPVOID params); // The polling function, run as a seperate thread
  10. HANDLE StartPollThread(CPayout* payout); // The method to start the polling thread, returns a handle to it
  11. const int PollInterval = 250; // Interval between polls (ms)
  12. volatile bool IsPolling = false; // Indicates whether the poll thread should actively poll the unit
  13. volatile bool PollFailed = false; // Indicates whether a poll has failed, can be called by multiple threads
  14. #define MAX_PROTOCOL_VERSION 8 // Max protocol version supported by MAN (3/11/11).
  15. int main(int argc, char *argv[])
  16. {
  17. CPayout* Payout = new CPayout(); // The main class used to interface with the validator
  18. // Create a new thread here, this thread will not actively poll the unit until the
  19. // bool IsPolling is set to true.
  20. HANDLE payoutThread = StartPollThread(Payout);
  21. // Set output to std::cout
  22. Payout->SetOutputStream(&std::cout);
  23. WriteString("\n" );
  24. // Find out about the validator and create a connection struct to pass
  25. // to the validator class
  26. SSP_COMMAND command;
  27. command.BaudRate = 9600;
  28. command.Timeout = 1000;
  29. command.RetryLevel = 3;
  30. command.IgnoreError = 1;
  31. char* end;
  32. // Get port number
  33. std::vector<string> ports = GetComPorts();
  34. for (unsigned int i = 0; i < ports.size(); ++i)
  35.  WriteString(ports[i]);
  36. string in = "";
  37. unsigned int portNum = 0;
  38. do
  39. {
  40.  in = GetInputString("Select a port: " );
  41.  portNum = strtol(in.c_str(), &end, 0);
  42. } while (portNum < 1 || portNum > ports.size());
  43. command.PortNumber = FormatComPort(ports[portNum-1]);
  44. // Get ssp address
  45. unsigned int ssp = 0;
  46. do
  47. {
  48.  in = GetInputString("SSP Address (Default 0): " );
  49.  ssp = strtol(in.c_str(), &end, 0);
  50. } while (ssp < 0 || ssp > 32 || end == in.c_str());
  51. command.SSPAddress = (unsigned char)ssp;
  52. // Get protocol version to use
  53. unsigned int p = 0;
  54. do
  55. {
  56.  in = GetInputString("Protocol version: " );
  57.  p = strtol(in.c_str(), &end, 0);
  58. } while (p < 1 || p > MAX_PROTOCOL_VERSION);
  59. // Now connect to validator
  60. if (Payout->ConnectToPayout(command, p, 5) && Payout->GetUnitType() == 0x06)
  61. {
  62.  WriteString("Connected to MAN successfully" );
  63.  // Display the available commands
  64.  DisplayCommands();
  65. //####################################
  66. Payout->EnableValidator();
  67. WriteString("Apres Enable" );
  68.  // Begin checking input and poll fails
  69.  while  (true)
  70.  {
  71.   if (!ParseKeys(Payout))
  72.   {
  73.    IsPolling = false;
  74.    break; // If false break the loop
  75.   }
  76.   // This bool is set by the poll loop thread
  77.   if (PollFailed)
  78.   {
  79.    cout << "Poll failed" << endl;
  80.    if (Payout->ConnectToPayout(command, p, 5))
  81.    {
  82.     PollFailed = false;
  83.     StartPollThread(Payout);
  84.    }
  85.    else
  86.     break;
  87.   }
  88.  }
  89. }
  90. else if (Payout->GetUnitType() != 0x06)
  91.  WriteString("Incorrect unit detected, this SDK supports the MAN only" );
  92. else
  93.  WriteString("Failed to connect to the unit" );
  94. cout << "Program shutting down... ";
  95. // Close poll thread
  96. TerminateThread(payoutThread, 1);
  97. CloseHandle(payoutThread);
  98. // Delete payout
  99. delete Payout;
  100. cout << "Complete\n\nPress any key to exit..." << endl;
  101. _getch();
  102. }
  103. // This function searches through com ports 1 to 128 and determines if they
  104. // exist or not. The list of existing ports is sent as a response.
  105. std::vector<string> GetComPorts()
  106. {
  107. vector<string> ports;
  108. int index = 1;
  109. // Go through each possible port
  110. for (int i = 1; i <= 64; ++i)   
  111.     {
  112.  char* buffer = new char[8];
  113.  string port = "COM";
  114.  port += itoa(i, buffer, 10);
  115.  // First get the size
  116.  DWORD dwSize = 0;
  117.  LPCOMMCONFIG lpCC = (LPCOMMCONFIG) new BYTE[1];
  118.  BOOL ret = GetDefaultCommConfig(port.c_str(), lpCC, &dwSize);
  119.  delete [] lpCC;
  120.  // Now test the port
  121.  lpCC = (LPCOMMCONFIG) new BYTE[dwSize];
  122.  ret = GetDefaultCommConfig(port.c_str(), lpCC, &dwSize);
  123.  // If the port exists, add to the vector
  124.  if (ret != null)
  125.  {
  126.   string pusher = itoa(index++, buffer, 10);
  127.   pusher += ". ";
  128.   pusher += port;
  129.   ports.push_back(pusher);
  130.  }
  131.  delete[] buffer;
  132.  delete [] lpCC;
  133.     }
  134. return ports;
  135. }
  136. bool ParseKeys(CPayout* payout)
  137. {
  138. // If a keystroke is detected
  139. if (_kbhit())
  140. {
  141.  // Parse the key
  142.  switch (_getch())
  143.  {
  144.  case 'x': return false;
  145.  case 'e':
  146.   payout->EnableValidator();
  147.   break;
  148.  case 'd':
  149.   payout->DisableValidator();
  150.   break;
  151.  case 'p':
  152.   {
  153.    // Toggle the polling
  154.    if (IsPolling)
  155.    {
  156.     IsPolling = false;
  157.     WriteString("Stopped polling" );
  158.    }
  159.    else
  160.    {
  161.     IsPolling = true;
  162.     WriteString("Started polling" );
  163.    }
  164.    break;
  165.   }
  166.  case 'r':
  167.   {
  168.    // Stored notes
  169.    WriteString("Notes stored:\n" );
  170.    payout->OutputLevelInfo();
  171.    // Recycling status of notes
  172.    WriteString("Recycling status of notes:\n" );
  173.    for (int i = 0; i < payout->GetNumChannels(); ++i)
  174.    {
  175.     cout << payout->GetUnitData()[i].Value*0.01f << " " << payout->GetUnitData()[i].Currency[0] <<
  176.      payout->GetUnitData()[i].Currency[1] << payout->GetUnitData()[i].Currency[2] << " is ";
  177.     (payout->GetUnitData()[i].Recycling)?cout << "being sent to storage" << endl:
  178.      cout << "being sent to cashbox" << endl;
  179.    }
  180.    // Cashbox payout data
  181.    WriteString("\nCashbox payout data:\n" );
  182.    payout->GetCashboxPayoutOpData();
  183.    break;
  184.   }
  185.  case 'h': DisplayCommands(); break;
  186.  case 't':
  187.   {
  188.    payout->ResetValidator();
  189.    payout->CloseComPort(); // Force reconnection by closing com port
  190.    break;
  191.   }
  192.  case 'a':
  193.   {
  194.    char choice = GetInputChar("1. Standard payout\n2. Payout by Denomination\r\n" );
  195.    if (choice == '1') // Standard payout
  196.    {
  197.     string s = GetInputString("Enter amount and currency to payout\nFormat is Amount Currency e.g. 10.00 CODE: " );
  198.     // Break into 2 strings, first contains value, second currency
  199.     string s1 = "";
  200.     size_t pos = s.find_first_of(' ');
  201.     if (pos == s.npos)
  202.     {
  203.      WriteString("Input was not in the correct format" );
  204.      break;
  205.     }
  206.     s1 = s.substr(pos+1, s1.npos);
  207.     s.erase(pos, s.npos);
  208.     // Convert value to integer
  209.     float value = (float)atof(s.c_str());
  210.     // Make sure each char in the currency is uppercase and
  211.     // only 3 chars were entered
  212.     if (s1.length() != 3)
  213.     {
  214.      WriteString("Invalid currency" );
  215.      break;
  216.     }
  217.     for (int i = 0; i < 3; ++i)
  218.      s1[i] = toupper(s1[i]);
  219.     // If the value entered isn't 0 or negative, make the payout
  220.     if (value > 0)
  221.     {
  222.      payout->Payout((int)(value * 100), s1);
  223.     }
  224.    }
  225.    else if (choice == '2') // Payout by Denomination
  226.    {
  227.     // Get the dataset info from the validator
  228.     SUnitData* UnitData = payout->GetUnitData();
  229.     // Create a temporary array to hold denomination info
  230.     unsigned char* dataArray = new unsigned char[255];
  231.     int counter = 0, temp = 0, numDenoms = 0;
  232.     // Go through each channel and query the user on how many of each denomination
  233.     // they want to payout.
  234.     for (int i = 0; i < payout->GetNumChannels(); ++i)
  235.     {
  236.      cout << "Number of " << UnitData[i].Value*0.01f << " " << UnitData[i].Currency[0] <<
  237.       UnitData[i].Currency[1] << UnitData[i].Currency[2] << " to payout: ";
  238.      temp = atoi(GetInputString("" ).c_str());
  239.      if (temp != 0) // if they have entered a number
  240.      {
  241.       numDenoms++; // increase number of denominations to payout
  242.       memcpy(dataArray + counter, &temp, 2); // copy number of denominations to the data array
  243.       counter += 2;
  244.       memcpy(dataArray + counter, &UnitData[i].Value, 4); // copy value of denomination to data array
  245.       counter += 4;
  246.       // copy currency of denom to data array
  247.       dataArray[counter++] = UnitData[i].Currency[0];
  248.       dataArray[counter++] = UnitData[i].Currency[1];
  249.       dataArray[counter++] = UnitData[i].Currency[2];
  250.      }
  251.     }
  252.     if (numDenoms > 0) // if the user didn't enter 0 for all denominations
  253.     {
  254.      payout->PayoutByDenomination(numDenoms, dataArray);
  255.     }
  256.     delete[] dataArray; // delete temp array once command sent
  257.    }
  258.    else
  259.     WriteString("Invalid input\r\n" );
  260.    break;
  261.   }
  262.  case 'm':
  263.   {
  264.    // Empty has two choices, SMART or just empty. SMART keeps track of
  265.    // what notes were emptied, empty does not.
  266.    char choice = GetInputChar("1. Empty\n2. SMART Empty\n" );
  267.    if (choice == '1')
  268.     payout->Empty();
  269.    else if (choice == '2')
  270.     payout->SMARTEmpty();
  271.    else
  272.     WriteString("Invalid input" );
  273.    break;
  274.   }
  275.  case 'c':
  276.   {
  277.    // Note recycling
  278.    string s = GetInputString("Enter amount and currency to alter recycling on\nFormat is Amount Currency e.g. 10.00 CODE: " );
  279.    // Break into 2 strings, first contains value, second currency
  280.    string s1 = "";
  281.    size_t pos = s.find_first_of(' ');
  282.    if (pos == s.npos)
  283.    {
  284.     WriteString("Input was not in the correct format" );
  285.     break;
  286.    }
  287.    s1 = s.substr(pos+1, s1.npos);
  288.    s.erase(pos, s.npos);
  289.    // Convert value to integer
  290.    float value = (float)atof(s.c_str());
  291.    // Make sure each char in the currency is uppercase and
  292.    // only 3 chars were entered
  293.    if (s1.length() != 3)
  294.    {
  295.     WriteString("Invalid currency" );
  296.     break;
  297.    }
  298.    for (int i = 0; i < 3; ++i)
  299.     s1[i] = toupper(s1[i]);
  300.    char c = GetInputChar("1. Store note\n2. Stack note\r\n" );
  301.    // Validation
  302.    if ((c != '1' && c != '2'))
  303.    {
  304.     cout << "Invalid input" << endl;
  305.     break;
  306.    }
  307.    // Convert to bool
  308.    bool stack = atoi(&c)-1;
  309.    // Convert value to penny value
  310.    value *= 100;
  311.    // Send to either cashbox or storage depending on choice
  312.    (stack)?payout->SendNoteToCashbox((int)value, s1):payout->SendNoteToStorage((int)value, s1);
  313.    break;
  314.   }
  315.  default: break;
  316.  }
  317. }
  318. return true;
  319. }
  320. void DisplayCommands()
  321. {
  322. string s = "Command List\n";
  323. s += "e = enable\nd = disable\n";
  324. s += "p = start/stop polling\n";
  325. s += "a = make a payout\n";
  326. s += "m = empty or SMART empty the unit\n";
  327. s += "c = change note recycling options\n";
  328. s += "t = reset validator\n";
  329. s += "r = report\n\n";
  330. s += "h = display this list again\n";
  331. s += "x = exit\n";
  332. WriteString(s);
  333. }
  334. // Remove the "x. COM" from the com port string
  335. unsigned inline char FormatComPort(const string& portstring)
  336. {
  337. string s = portstring;
  338. s.erase(0, 6);
  339. return (unsigned char)atoi(s.c_str());
  340. }
  341. // The following section contains threading functions. Threading is used in this program
  342. // as it is vital that the validator is polled at regular intervals. Obviously the execution
  343. // of the main thread of the program needs to be halted occasionally - such as when waiting
  344. // for a user's input, so the polling of the validator needs to occur on a seperate thread
  345. // which will never be halted. The validator class looks after critical sections and thread
  346. // safing.
  347. // This function is called to create a new instance of the poll thread. The function returns a
  348. // handle to the thread.
  349. HANDLE StartPollThread(CPayout* Payout)
  350. {
  351. unsigned int hopThreadID;
  352. return CreateThread(NULL,
  353.      0,
  354.      PollThread,
  355.      Payout,
  356.      NULL,
  357.      (LPDWORD)&hopThreadID);
  358. }
  359. // This function is responsible for polling the validator at a regular interval.
  360. // It should be run as a seperate thread to the main execution thread.
  361. DWORD WINAPI PollThread(LPVOID params)
  362. {
  363. // Get validator instance from param
  364. CPayout* payout = reinterpret_cast<CPayout*>(params);
  365. // Keep looping until broken out of
  366. while (true)
  367. {
  368.  // If the validator is actively being polled
  369.  if (IsPolling)
  370.  {
  371.   // Attempt to send the poll
  372.   if (!payout->DoPoll())
  373.   {
  374.    // If it fails (command can't get to validator) then set the failed poll
  375.    // flag to true and exit the function. This will terminate the thread.
  376.    PollFailed = true;
  377.    return 0;
  378.   }
  379.   Sleep(PollInterval); // Wait for the interval
  380.  }
  381. }
  382. return 0;
  383. }

Reply

Marsh Posté le 08-02-2014 à 21:25:09   

Reply

Marsh Posté le 11-02-2014 à 13:18:30    

Il est techniquement possible de faire une DLL du code que tu postes, mais tu ne pourras probablement pas l'utiliser avec Windev sans modifications profondes, en particulier la gestion du thread de polling. Ta seule chance est d'écrire toi même le code pour parler SSP ou ccTalk, ce qui n'est pas complexe mais très compliqué si tu ne l'as jamais fait ...
 
Mon avis personnel: Contrôler un validateur I... T... avec Windev c'est du suicide [:atropos]
 
 
 
 
 

Reply

Marsh Posté le 11-02-2014 à 15:58:11    

Mouais, va falloir pas mal de modification au code. Pas dramatique lorsqu'on s'y connait, mais si tu débutes en C/C++, va être un peu hardcore pour toi.
 
De ce que j'ai compris du programme, il utilise une API C++ (CSMARTPayout.h) pour communiquer avec un appareil. Le programme en question démarre un thread qui intéroge le bidule à intervalle régulier. Le thread principal attends les commandes depuis le clavier qui affiche les infos récupérées par le thread.
 
Pour communiquer avec un autre langage, il te faudra une ABI C. Une ABI C++ est un cauchemar à interfacer dans n'importe quel langage autre que le C++, pas juste Windev. Pour faire cela, il faut utiliser la déclaration :

Code :
  1. extern "C" {
  2.   // déclaration des prototypes là dedans
  3. }


 
Et comme tu utilises visual studio, faudra rajouter les déclarations :

Code :
  1. __declspec(dllexport)


 
Par exemple :

Code :
  1. // Déclaration (header)
  2. extern "C" {
  3. __declspec(dllexport) void init_device(void);
  4. }
  5.  
  6. // Implémentation
  7. __declspec(dllexport) void init_device(void)
  8. {
  9.    // Code ici
  10. }


 
Les "declspec" sont nécessaires parce par défaut VisualC++ n'exporte aucun symbole des DLLs pour éviter de faire exploser les temps de compilation. Les 'extern "C"" sont pour forcer VisualC++ à utiliser une ABI C. Tu pourra récupérer l'adresse de la fonction via GetProcAddress(dll, "init_device" ).
 
Toute la récupération des infos via la saisie clavier dans le programme est bonne pour la poubelle, faudra découper ça en fonction C: chaque branche du "switch" = une fonction.
 
Reste à voir si démarer un thread ne foutra pas la merde avec Windev. À priori non, mais tu auras intérêt à faire quelques tests de stabilité...
 
C'est assez brut de fonderie mes explications, mais ne connaissant pas trop ton niveau, on va déjà voir si tu peux te démerder avec ça.


Message édité par tpierron le 11-02-2014 à 20:53:08
Reply

Marsh Posté le 11-02-2014 à 17:41:34    

FCKGW a écrit :


Mon avis personnel: Contrôler un validateur I... T... avec Windev c'est du suicide [:atropos]

Fixed!  :whistle:  
A+,


---------------
There's more than what can be linked! --    Iyashikei Anime Forever!    --  AngularJS c'est un framework d'engulé!  --
Reply

Marsh Posté le 11-02-2014 à 19:10:56    

Merci pour votre réponse les gars, j'ai réussi finalement à modifier ce code, comme m'a expliqué tpierron. Bien entendu je ne dois pas faire intervenir la saisie du clavier. Le DLL que j'ai fais il a qu'une seule fonction, c'est la fonction qui démarre le thread et qui donne les paramètres de la structure SSP_COMMAND à l'appareil pour démarrer la communication. Jusqu'à là tout roule comme je veux. Mais quand j'ai voulu rajouter la fonction (par exemple: EnableValidator();) mon programme Windev qui utilise cette DLL plante. je vous montre mon code dans une réponse complète.

Reply

Marsh Posté le 11-02-2014 à 21:02:28    

Voilà, donc en suivant l'explication de tpierron, la dll fonctionne parfaitement avec Windev voici la partie .h  :
 

Code :
  1. #ifdef FUNCSDLL_EXPORTS
  2. #define FUNCSDLL_API __declspec(dllexport)
  3. #else
  4. #define FUNCSDLL_API __declspec(dllimport)
  5. #endif
  6. namespace MISmart
  7. {
  8.     // This class is exported from the FUNCSDll.dll
  9.     class ConfigSmart
  10.     {
  11.     public:
  12.         static FUNCSDLL_API int Configuration(void);
  13.         static FUNCSDLL_API void Connexion(void);
  14.         static FUNCSDLL_API int FonctionA(int a, int b);
  15.         static FUNCSDLL_API int FonctionB(int a, int b);
  16.     };
  17. }


 
Pour le .cpp voici le code:
 

Code :
  1. //####################################  Class Export  ####################################
  2. //########################################################################################
  3. namespace MISmart
  4. {
  5.     int ConfigSmart::Configuration(void)
  6.     {
  7.  CPayout* Payout = new CPayout();
  8.  // Create a new thread here, this thread will not actively poll the unit until the
  9.  // bool IsPolling is set to true.
  10.  HANDLE payoutThread = StartPollThread(Payout);
  11.  // Set output to std::cout
  12.  Payout->SetOutputStream(&std::cout);
  13.  SSP_COMMAND command;
  14.  command.BaudRate = 9600;
  15.  command.Timeout = 1000;
  16.  command.RetryLevel = 3;
  17.  command.IgnoreError = 1;
  18.  // Get port number
  19.  std::vector<string> ports = GetComPorts();
  20.  unsigned int portNum = 2;//Deuxième PortCOM  
  21.  command.PortNumber = FormatComPort(ports[portNum-1]);
  22.  // Get ssp address
  23.  unsigned int ssp = 0;
  24.  command.SSPAddress = (unsigned char)ssp;
  25.  // Get protocol version to use
  26.  unsigned int p = 8;
  27.  // Now connect to validator
  28.  if (Payout->ConnectToPayout(command, p, 5) && Payout->GetUnitType() == 0x06)
  29.  {
  30.   Payout->EnableValidator();
  31.   //ConfigSmart::Connexion();
  32.   return 1;
  33.  }
  34.  else return 0;
  35. }
  36.     void ConfigSmart::Connexion()
  37.     {
  38.  Payout->EnableValidator();
  39.     }
  40.     int ConfigSmart::FonctionA(int a, int b)
  41.     {
  42.         return a * b;
  43.     }
  44.     int ConfigSmart::FonctionB(int a, int b)
  45.     {
  46.         if (b == 0)
  47.         {
  48.             throw invalid_argument("b cannot be zero!" );
  49.         }
  50.         return a / b;
  51.     }
  52. }


 
La première fonction ( ConfigSmart::Configuration(void) )appelée par Windev fonctionne très bien mais quand j'appelle la deuxième fonction ( ConfigSmart::Connexion() ) qui n'est que la l'appel de la Payout->EnableValidator(); la dll plante, j'ai même éliminé Payout->EnableValidator(); de la première fonction et d'appeler la deuxième fonction le résultat est le même c a d la DLL plante????????
est ce que quel qu'un pourrait m'éclaircir la dessus svp?

Reply

Marsh Posté le 11-02-2014 à 21:43:08    


Hmm, tu as déclaré ta variable "Payout" locale à ta fonction :

Code :
  1. int ConfigSmart::Configuration(void)
  2. {
  3.    CPayout* Payout = new CPayout();
  4.    // ...
  5. }


 
Quand bien même tu utilises "new", la référence à cet objet sera perdu au retour de la fonction.
 
Bref 2 solutions :

  • Déclare cette variable globale (singleton).
  • Retourne ce pointeur en valeur de retour, puis retransmet ça aux fonctions qui en ont besoin (préférable).


Reply

Marsh Posté le 11-02-2014 à 23:59:51    

Tout à fait tpierron merci, finalement il suffit de déclarer Payout en global pour toute les fonctions de namespace MISmart:

Code :
  1. namespace MISmart
  2. {
  3. CPayout* Payout = new CPayout();
  4.     int ConfigSmart::Configuration(void)
  5.     {
  6.  // Create a new thread here, this thread will not actively poll the unit until the
  7.  // bool IsPolling is set to true.
  8.  HANDLE payoutThread = StartPollThread(Payout);
  9. //...


 
Merci beaucoup tpierron pour ton aide précieuse

Reply

Sujets relatifs:

Leave a Replay

Make sure you enter the(*)required information where indicate.HTML code is not allowed