Question COM Interoperability With C Program

StoneCodeMonkey

Well-known member
Joined
Apr 17, 2009
Messages
56
Programming Experience
5-10
Hello,

I am just starting my first project that will need COM Interoperabilty and I am looking for some guidance. But first, a little background on what I am trying to achieve. I have a Trumpf VectorMark laser at work that runs an application called Quickflow. Basically it is a graphical editor that produces a flow diagram of a program that the equipment uses to run.

In the Quickflow application, one of the intructions allows you to point to a DLL or EXE file and call on a function or method. It allows you to pass arguments to the DLL/EXE and also accepts values back. The system already has a file called tool_dll.dll which has many useful functions.

The help directory also included a file called tool_dll.h which contains definitions for the functions that can be called on. Sample below.

VB.NET:
#ifndef TOOL_DLL_H
	#define TOOL_DLL_H

   #if defined (BUILD_DLL)
      #define DLL_EXP	__declspec(dllexport)
   #else
      #if defined (BUILD_APP)
         #define DLL_EXP	__declspec(dllimport)
      #else
         #define DLL_EXP
      #endif
   #endif


/**
 * -----------------------------------------------------------------------------
 * Description:   Calculates the MODULO10 check-character from the
 *                  given string ("data_in")
 * Require:       None
 * Restriction:   None
 *
 * @param data_in    Pointer to a buffer with the given string
 * @param data_out   Pointer to a buffer with the MODULO10 check-character
 * -----------------------------------------------------------------------------
 */
extern "C" DLL_EXP void MODULO10(char* data_in, char *data_out);

/**
 * -----------------------------------------------------------------------------
 * Description:   Writes the given string ("data_in") to a log file with the
 *                following name "c:\\QuickFlow\\Log\\YY_MM_DD_HH_LOG_H1.TXT". If
 *                the file doesn't exist a new one will be created.
 *                Tokens:
 *                > YY = Year (04 for 2004)
 *                > MM = Month of year (01 to 12)
 *                > DD = Day of month (01 to 31)
 *                > HH = Hour of day (01 to 24)
 * Require:       Directory "c:\\QuickFlow\\Log" must exist
 * Restriction:   Every hour a new log file will be created.
 *
 * @param data_in    Pointer to a buffer with the log text
 * @param data_out   Not used
 * -----------------------------------------------------------------------------
 */
extern "C" DLL_EXP void WRITE_LOGFILE_H1(char* data_in, char *data_out);

/**
 * -----------------------------------------------------------------------------
 * Description:   Writes the given string ("data_in") to a log file with the
 *                following name "c:\\QuickFlow\\Log\\YY_MM_DD_HH_LOG_H2.TXT". If
 *                the file doesn't exist a new one will be created.
 *                Tokens:
 *                > YY = Year (04 for 2004)
 *                > MM = Month of year (01 to 12)
 *                > DD = Day of month (01 to 31)
 *                > HH = Hour of day (01 to 24)
 * Require:       Directory "c:\\QuickFlow\\Log" must exist
 * Restriction:   Every hour a new log file will be created
 *
 * @param data_in    Pointer to a buffer with the log text
 * @param data_out   Not used
 * -----------------------------------------------------------------------------
 */

So, I figured I could use VB 2008 to create my own dll and add some functions that I need for management of the equipment.

I created a new project to test my theory. Code as follows, real simple to start with.
VB.NET:
<ComClass(Trumpf.ClassId, Trumpf.InterfaceId, Trumpf.EventsId)> _
Public Class Trumpf

#Region "COM GUIDs"
    ' These  GUIDs provide the COM identity for this class 
    ' and its COM interfaces. If you change them, existing 
    ' clients will no longer be able to access the class.
    Public Const ClassId As String = "e33c9f27-7f97-4ce8-9f75-2c07c31e8de4"
    Public Const InterfaceId As String = "f5f48cf1-5a41-477b-9b0c-899aacfbd51b"
    Public Const EventsId As String = "17656d74-7b4a-453d-aeb4-8a8126583508"
#End Region

    ' A creatable COM class must have a Public Sub New() 
    ' with no parameters, otherwise, the class will not be 
    ' registered in the COM registry and cannot be created 
    ' via CreateObject.
    Public Sub New()
        MyBase.New()
    End Sub

    Public Function GetSerialNumber() As String
        Dim DateCode As String = Today.ToString("yy").PadLeft(2, "0") + Today.DayOfYear.ToString("000")
        Return "S" + DateCode + 1.ToString("0000")
    End Function

End Class

I built my project and then opened up VB6, created a new project and set a reference to the new DLL. A couple of lines of code to test with ....
VB.NET:
Option Explicit

Sub Main()
    Dim t As New Trumpf.Trumpf
    Debug.Print t.GetSerialNumber
End Sub

The result was S100790001 exactly as expected. Pretty proud of myself at this point even though it was short lived.

I took my new DLL to the laser equipment (which has a Windows XP pc), saved it to the local HD, ran regasm which did not produce any errors.

At this point I launched the Quickflow editor, added and instruction to access the DLL, and ran the program. Which only resulted in the application throwing an error saying it couldn't open the file.

After scratching my head for a few minutes I had to accept the fact that I just didn't have a clue what was wrong or what to do next. I am really confused as to why the VB6 app so readily worked while the Quickflow program which accepts a file path to the DLL and a Name for the function to be called did not.

If anyone has any recommendations on what to try next I would greatly appreciate it.

Best regards,
 
Update, Found More Source Code

Ok,

I dug a little deeper into the file system on the laser equipment and found two more sample files. From the looks of it the source is C or C++, not really sure. I think the code is using Pointers to memory locations and that is how the Quickflow application is able to call on the DLL by function name. Can anyone tell me if VB.Net can emulate the "Pointer" funtioinality so I can create a DLL that works like the source below?

File: tool_dll.h
VB.NET:
// Datamatrix DLL Headerdatei
#ifndef TOOL_DLL_H
	#define TOOL_DLL_H
//---------------------------------------------------------------------------
 #if defined (BUILD_DLL)
    #define DLL_EXP	__declspec(dllexport)
 #else
    #if defined (BUILD_APP)
       #define DLL_EXP	__declspec(dllimport)
    #else
       #define DLL_EXP
    #endif
 #endif
//---------------------------------------------------------------------------

/*
Wandelt den Inputstring (data_in) über die entsprechenden Operation
in einen Outputstring um
*/
/* 
Converts the input string (data_in) on the operation 
Output in a string 
*/
extern "C" DLL_EXP void UPPERCASE(char* data_in, char *data_out);
extern "C" DLL_EXP void LOWERCASE(char* data_in, char *data_out);
extern "C" DLL_EXP void DEZ_TO_HEX(char* data_in, char *data_out);
extern "C" DLL_EXP void HEX_TO_DEZ(char* data_in, char *data_out);
extern "C" DLL_EXP void CUT_SPACES(char* data_in, char *data_out);

//Gibt die Länge des Inputstrings als dezimale Zahl retour
// Is the length of the input strings as a decimal number retour
extern "C" DLL_EXP void STRING_LEN(char* data_in, char *data_out);

//---------------------------------------------------------------------------
#endif // TOOL_DLL_H
File: tool_dll.cpp
VB.NET:
//---------------------------------------------------------------------------
#include <vcl.h>
#include <time.h>
#include <stdio.h>
#include <string.h>
#pragma hdrstop
//---------------------------------------------------------------------------
//  Wichtiger Hinweis zur DLL-Speicherverwaltung, wenn Ihre DLL die
//  statische Version der Laufzeitbibliothek verwendet:
//
//  Wenn Ihre DLL Funktionen exportiert, die String-Objekte (oder struct/class,
//  die verschachtelte Strings enthalten) als Parameter oder Funktionsergebnisse übergeben,
//  müssen Sie die Bibliothek MEMMGR.LIB zum DLL-Projekt und
//  auch zu anderen Projekten, die diese DLL benutzen, hinzufügen. Sie müssen auch
//  MEMMGR.LIB verwenden, wenn andere Projekte, die die DLL benutzen, new- oder delete-Operation auf
//  Klassen durchführen, die nicht von TObject abstammen und von der DLL exportiert werden.
//  Durch das Hinzufügen von MEMMGR.LIB werden die DLL und die aufrufenden EXEs
//  dazu veranlasst, BORLNDMM.DLL als Speicher-Manager zu verwenden. In diesen Fällen
//  muß die Datei BORLNDMM.DLL mit der DLL weitergegeben werden.
//
//  Um die Verwendung von BORLNDMM.DLL zu vermeiden, sollten String-Informationen
//  als "char *" oder ShortString übergeben werden.
//
//  Wenn Ihre DLL die dynamische Version der RTL verwendet, müssen Sie
//  MEMMGR.LIB nicht explizit hinzufügen, da dies implizit geschieht.
//---------------------------------------------------------------------------
//------------------------------------------------ --------------------------- 
// Important Note on the DLL memory management, if your DLL the 
// Static version of the run-time library: 
// 
// If your DLL functions exported, the string objects (or struct / class, 
// Nested strings) as a parameter or functional results over 
// You must MEMMGR.LIB to the library project and DLL 
// Also to other projects that use this DLL, adding. They must also 
// MEMMGR.LIB, if other projects that use the DLL, new or delete operation to 
// Classes that are not descended from TObject and DLL exported. 
// By adding MEMMGR.LIB the DLL and the calling EXEs 
// Led BORLNDMM.DLL as storage managers to use. In these cases 
// BORLNDMM.DLL the file with the DLL parties. 
// 
// To the use of BORLNDMM.DLL to avoid information should String 
// As "char *" or short string. 
// 
// If your DLL dynamic version of the RTL, you need to 
// MEMMGR.LIB not explicitly add, as this is done implicitly. 
//------------------------------------------------ ---------------------------
USEFORM("main.cpp", Form1);
USEUNIT("sioce_bl.cpp");
USEFORM("checkpassword.cpp", PasswordDlg);
USEFORM("inputdata.cpp", input_data_Dlg);
USEFORM("explore.cpp", exploreDlg);
USERES("tool_dll.res");
USEFORM("scrollbox.cpp", Form_scrollbox_dialog);
//---------------------------------------------------------------------------
int WINAPI DllEntryPoint(HINSTANCE hinst, unsigned long reason, void*)
{
   return 1;
}
//---------------------------------------------------------------------------
#define BUILD_DLL
#include <stdio.h>
#include <math.h>
#include "tool_dll.h"
#include "main.h"
#include "checkpassword.h"
#include "inputdata.h"
#include "explore.h"
#include "sioce_bl.h"
#include "scrollbox.h"
#include <Registry.hpp>
//---------------------------------------------------------------------------
void gsp_getcounter(int adresse, char* data);
void gsp_getstatus(int adresse, char* data);
void gsp_move_relativ(int adresse, char* data_in, char *data_out);
void gsp_move_absolut(int adresse, char* data_in, char *data_out);
void gsp_init_negativ(int adresse, char *data_out);
void gsp_init_positiv(int adresse, char *data_out);
bool read_registry(int adresse);
void read_registry_achsenparameter(void);
//---------------------------------------------------------------------------

sioce sio_gsp;

int   axis_type;
int   run_frequence;
int   ramp_frequence;
int   hr_frequence;
int   run_current;
int   stop_current;
int   gear_factor;

int   gear_factoren[5];

TRegistry *ptr_Registry;

//---------------------------------------------------------------------------
#define  QUICKLFOW_TOOLDLL_X_ACHSE_REGKEY    "\\software\\quickflow\\tooldll\\x_axis"
#define  QUICKLFOW_TOOLDLL_Y_ACHSE_REGKEY    "\\software\\quickflow\\tooldll\\y_axis"
#define  QUICKLFOW_TOOLDLL_Z_ACHSE_REGKEY    "\\software\\quickflow\\tooldll\\z_axis"
#define  QUICKLFOW_TOOLDLL_W_ACHSE_REGKEY    "\\software\\quickflow\\tooldll\\w_axis"
#define  QUICKLFOW_TOOLDLL_C_ACHSE_REGKEY    "\\software\\quickflow\\tooldll\\c_axis"
#define  AXIS_TYPE                           "Axis_Type"
#define  RUN_FREQUENCE                       "Run_Frequence"
#define  RAMP_FREQUENCE                      "Ramp_Frequence"
#define  HR_FREQUENCE                        "HR_Frequence"
#define  GEAR_FACTOR                         "Gear_Factor"
#define  RUN_CURRENT                         "Run_Current"
#define  STOP_CURRENT                        "Stop_Current"
//---------------------------------------------------------------------------

extern "C" DLL_EXP void UPPERCASE(char* data_in, char *data_out)
{
   wsprintf(data_out,"%s",StrUpper(data_in));
}
//---------------------------------------------------------------------------

extern "C" DLL_EXP void LOWERCASE(char* data_in, char *data_out)
{
   wsprintf(data_out,"%s",StrLower(data_in));
}
//---------------------------------------------------------------------------

extern "C" DLL_EXP void DEZ_TO_HEX(char* data_in, char *data_out)
{
   sprintf(data_out,"%lX",atol(data_in));
}
//---------------------------------------------------------------------------

extern "C" DLL_EXP void HEX_TO_DEZ(char* data_in, char *data_out)
{
	int i;
   unsigned char l,h;
   char hex_string[1024];
   unsigned char ziel[256];
   int len;
   long int wert;
   int potenz_zahl;

   len = strlen(data_in);
   if(len%2 != 0)
   {
      wsprintf(hex_string,"0%s",data_in);
   }
   else
   {
      wsprintf(hex_string,"%s",data_in);
   }
   len = strlen(hex_string);

   for(i=0;i<len;i+=2)
   {
		h = (unsigned char)toupper(hex_string[i]);
		l = (unsigned char)toupper(hex_string[i+1]);

      if(isdigit(h))
      {
      	h = (h-48) << 4;
      }
      else
      {
         if(isxdigit(h))
         {
            h = (h-55) << 4;
         }
      }

      if(isdigit(l))
      {
      	l = (l-48);
      }
      else
      {
         if(isxdigit(l))
         {
            l = (l-55);
         }
      }

      ziel[i/2] = h | l;
   }

   wert = 0;
   potenz_zahl = 0;

   for(i=len/2;i>0;i--)
   {
      wert += ziel[i-1] * (long int)(pow(256,potenz_zahl));
      potenz_zahl++;
   }

   wsprintf(data_out,"%ld",wert);
}
//---------------------------------------------------------------------------

extern "C" DLL_EXP void CUT_SPACES(char* data_in, char *data_out)
{
   char text[256];
   char *ptr_text;

   wsprintf(text,"%s",data_in);
   ptr_text = strchr(text,' ');

   if(ptr_text)
   {
      *ptr_text = 0x00;
   }

   wsprintf(data_out,"%s",text);
}

//---------------------------------------------------------------------------

extern "C" DLL_EXP void STRING_LEN(char* data_in, char *data_out)
{
   sprintf(data_out,"%ld",strlen(data_in));
}
//---------------------------------------------------------------------------

extern "C" DLL_EXP void CREATE_FAHZ(char* data_in, char *data_out)
{
   double   f_wert;
   char     text[256];

   f_wert = atof(data_in);

   memset(text,0x00,sizeof(text));
   wsprintf(text,"FAHZ%06d",(int)(f_wert*100));
   text[10] = 0x03;
   wsprintf(data_out,"%s",text);
}
//---------------------------------------------------------------------------
 
Update: VB.Net Test

Ok, on to pointers. I tested the following code to see if pointers would work.
VB.NET:
Imports System.Runtime.InteropServices

Module Module1

    'Test if VB can create pointers as in this C example.

    'extern "C" DLL_EXP void LOWERCASE(char* data_in, char *data_out)
    '{
    '   wsprintf(data_out,"%s",StrLower(data_in));
    '}

    Sub Main()
        'http://www.dotnetbips.com/articles/44bad06d-3662-41d3-b712-b45546cd8fa8.aspx
        Dim str As String = "HELLO WORLD"
        Dim ptrIn As IntPtr = Marshal.StringToHGlobalAuto(str)
        Dim ptrOut As IntPtr
        LOWERCASE(ptrIn, ptrOut)
        Dim mystring As String = Marshal.PtrToStringAuto(ptrOut)
        Console.WriteLine(mystring)
        Console.ReadLine()
    End Sub


    Private Sub LOWERCASE(ByVal ptrIn As IntPtr, ByRef ptrOut As IntPtr)
        Dim mystring As String = Marshal.PtrToStringAuto(ptrIn)
        mystring = mystring.ToLower
        ptrOut = Marshal.StringToHGlobalAuto(mystring)
    End Sub

End Module

The application worked and I was able to pass a pointer in and then read the string using the pointer passed back out. My only concern is that I need to do some type of memory managment.

As I am trying to develop a DLL that I can use with the Quickflow application mentioned at the start of this post I am hoping Quickflow will manage the memory correctly.

Since the built in DLL (tool_dll.dll) on the equipment resides in the same folder as the Quickflow.exe I think I can just drop my DLL in the same folder and be able to call on it.

The Quickflow application instruction has 4 fields that can be used. At a minimum 3 are required.
  1. File path to DLL
  2. Method name to be called on.
  3. Variable name to store the returned value in.
  4. Optional: Variable to be passed into the method call.

Wish me luck.
Regards
 
Back
Top