Call Clarion DLL from C++ and pass *CString

Hello All
I need to call Clarion DLL from C++ app.
For that purpose, here is console app calling Clarion DLL:

#include <windows.h>
#include <iostream>

typedef int(__stdcall* ClarionFuncType)(char*, char*, char*, char*, int);

int main() {
    SetConsoleOutputCP(CP_ACP);

    HMODULE hClarionDLL = LoadLibraryA("MyClarion.dll");
    if (!hClarionDLL) {
        std::cerr << "Can not read MyClarion.dll. Error: " << GetLastError() << std::endl;
        return 1;
    }

    ClarionFuncType MyFunction = (ClarionFuncType)GetProcAddress(hClarionDLL, "MyFunction");
    if (!MyFunction) {
        std::cerr << "Function MyFunction not found. Error: " << GetLastError() << std::endl;
        FreeLibrary(hClarionDLL);
        return 2;
    }

    char par1[256] = "Param1";
    char par2[256] = "Param2";
    char par3[256] = "Param3";
    char resultBuffer[1024] = { 0 };

    int retLen = MyFunction(par1, par2, par3, resultBuffer, sizeof(resultBuffer) / sizeof(char));

    std::cout << "Returned size: " << retLen << std::endl;
    std::cout << "resultBuffer content: " << resultBuffer << std::endl;

    FreeLibrary(hClarionDLL);
    return 0;
}

and here is Clarion DLL:

  PROGRAM

  MAP
    MyFunction(*cSTRING par1, *cSTRING par2, *cSTRING par3, *CSTRING r1, LONG r2), LONG, PASCAL, NAME('MyFunction')
    
  END

  CODE

MyFunction PROCEDURE(*CSTRING par1, *CSTRING par2, *CSTRING par3, *CSTRING r1, LONG r2)

temp  CSTRING(1024)

  CODE
  message('par1:   '&clip(par1)&' par2:   '&clip(par2)&' par3:   '&clip(par3))
  

  

As you can see, message display some wired things - instead of Param1, Param2 and Param3, here is what I see:

Try adding RAW to the end of that Map Prototype; otherwise, Clarion includes a parameter with the CString declared size.

Read the Help on RAW. There’s also a topic “Multi Language Programming” with more info on how strings are passed should you want to the prototype to pass the size to Clarion and not use RAW.

 MyFunction(*cSTRING par1, *cSTRING par2, *cSTRING par3, | 
            *CSTRING r1, LONG r2),LONG,PASCAL,NAME('MyFunction') , RAW  !++ ,Raw
  END

In a Message() you can add a Pipe | as shorthand for a <13,10> so each parameter appears on its own line making it a little easier to read.

Message('par1:   '&clip(par1)&'|par2:   '&clip(par2)&'|par3:   '&clip(par3))  !Pipe=13,10
Message('par1:   '&clip(par1)&'<13,10>par2:   '&clip(par2)&'<13,10>par3:   '&clip(par3))  !same as |

I’m on my phone atm
But you can also share an interface between C++ and CW and call either direction with it.

See clarion mag article
Interfacing with interfaces
Possibly by Gordon j smith

One thing to keep in mind if you use RAW, is that SIZE(pParam) can cause a GPF. So you should avoid using SIZE() in that circumstance.

Pass the size as a separate parameter and ensure that you test the value of the passed data in a safe way, avoiding SIZE().

@CarlBarnes Thanks but RAW doesn’t work. When compiling, it keeps saying: Error : CLARION function cannot use RAW.

@MarkGoldberg Thank you, I found this article on my backup and compiled c++ code together with Clarion DLL

class ClarionInterface {
public:
    virtual char* getPar1Val() = 0;
    virtual char* getPar2Val() = 0;
    virtual char* getPar3Val() = 0;
};

class ClarionData : public ClarionInterface {
public:
    char Par1Val[256];
    char Par2Val[256];
    char Par3Val[1024];

    virtual char* getPar1Val() { return Par1Val; }
    virtual char* getPar2Val() { return Par2Val; }
    virtual char* getPar3Val() { return Par3Val; }
};

but it does not work :frowning:
ChatGPT said:
Clarion DLL cannot use virtual functions from C++ classes, because Clarion does not know the virtual table (v-table) of C++ objects. The virtual function method is not applicable between Clarion DLL and C++ application.

Although you seem to declare INTERFACE correctly in Clarion, Clarion has no way to access these functions if they are virtual functions in C++.

@jslarve
I’d like to use RAW (it’s something ChatGPT suggested), but Clarion complains when compiling

Try “RAW,C” or “RAW,PASCAL” instead of just “RAW”…

Yes, I tried all possible combinations. The thing is that it is a Clarion function. When it is a function from a DLL, then RAW works.

Hi Daniel - Sorry if I steered you the wrong way. It has been many years since I’ve had to provide a Clarion DLL to a C++ user. I think what we did was provide a STRUCT on their side (a GROUP on our side) that used INT as pointers and for size. I am unable to access that code any more, The procedure definitely used the PASCAL convention, but I believe you are correct that we didn’t use RAW.

Two ideas … In Clarion prototype *CSTRING as LONG and it will get the address of the CString. Then Ref assign &= it to a &CSTRING Reference variable.

I changed R2 to be named r1_Size as in the call its passed sizeof(resultBuffer) / sizeof(char). Clarion allows a Reference to define the size with &= ( Address &':'& Size )

  MAP
    MyFunction(LONG par1_Addr, LONG par2_Addr, LONG par3_Addr, | 
               LONG r1_Addr, LONG r1_Size), LONG, PASCAL, NAME('MyFunction') 
  END
  
MyFunction PROCEDURE(LONG par1_Addr, LONG par2_Addr, LONG par3_Addr, | 
                     LONG r1_Addr, LONG r1_Size)
par1   &CSTRING 
par2   &CSTRING 
par3   &CSTRING 
r1     &CSTRING 
  CODE
  par1 &= (par1_Addr)   !Reference assign address to &CSTRING
  par2 &= (par2_Addr)
  par3 &= (par3_Addr)
  r1   &= (r1_Addr &':'& r1_Size)  !Was R2 which appears to be Size(R1) ?
  message('1_Addr: '&par1_Addr &'|2_Addr: '&par2_Addr &'|3_Addr: '&par3_Addr &'|r1_Addr: '&r1_Addr &'  Size: '& r1_Size)
  message('par1:   '&clip(par1)&'|par2:   '&clip(par2)&'|par3:   '&clip(par3))

Option 2 … In C++ prototype to pass the size before each *Char and pass it kind of like below.

typedef int(__stdcall* ClarionFuncType)(int, char*, int, char*, int, char*, int, char*, int);

...
int retLen = MyFunction(sizeof(par1), par1, sizeof(par2), par2, sizeof(par3), par3, sizeof(resultBuffer), resultBuffer, sizeof(resultBuffer) / sizeof(char));

This is safer so Clarion would have the size and not overrun the end. If you do not need to modify these you could add CONST *CSTRING par1 so the compiler prevents changes.

2 Likes

@CarlBarnes First option works fabulous, thank you very much.

1 Like

use the COM attribute… this is example of a complex CPP interface that hooks clarion into CPP and lets you merge the platforms together…

! UBS Get Set Data Methods declared in clarion.
! You include this interface in your clarion class.

Datamodels class,type,implements(IUBSD)
! clarion methods and the CPP iUBSD interface working together.
END

IUBSD               INTERFACE,com  
  
your clarion methods for the CPP virtual class

SetCstring         procedure(const *Cstring CstringData)

                    End

// in your CPP project.
 
class IUBS
{

public:
 
	virtual long _STD SetCstring(const *char) =0;      
};

1). create a class with the above interface.
2). create an instance of the class and return the Interface to clarion.

IUBSClassInstance : public IUBS
{
public:

// method of IUBS interface.
  ect... plenty of examples on learning CPP or more complex site.

} 

// create your external class instance and make your CPP project a dll
// and now your CPP code is part of your clarion application and your classes
// can call each other. you marshal the data.  

// The CPP external procedure callable from Clarion.
// You need to declare this external procedure as a module in clarion and also make a lib for your  CPP DLL. It may all seem a bit of a drama but its pretty simply really and a lot easier than call clarion from DOT NET where its a nightmare of marshal delegates and data types.

! Clarion declaration to call your CPP procedure or function to return your new CPP class interface.

   module('CPP DLL NAME')
        createIUBS().*IUBS,name('createIUBS'),C,RAW
  END

// CPP method to call from clarion to create your new class container and return the interface.

extern "C" IUBS *createIUBS()
{
    return new IUBSClassInstance();
}

You can make this example as complex as you like.

In your clarion class declare an instance of the CPP interface.
In your clarion class assign a instance of the ref from the CPP createIUBS.
myclass.IUBSDref &= createIUBS()

You will need to create another method in CPP to take the address of the class to delete it later on and pass the interface to it or the address of the new CPP class you created.

Now your free of DOT NET.

COM in clarions is the pascal stack passing and on your CPP declare your abstract CPP class methods as _STD.

sorry forgot how this forum formats its text…

1 Like

three graves accents and the word “clarion” starts the block.

Then terminate the block with 3 more graves accents.

This is interesting thank you.
Do you have small CPP example?

Have update the above topic with an example.

Thank you so much for details you shared.

dont forget to make sure your prototypes match as i left of the return long on the clarion definition. If any prototype has a mismatch return type the clarion stack state is fussy and may not be in a stable state.

@ironmansculler do you have any example, maybe just one procedure example of both C++ and Clarion side, please?

In the meantime, I’m struggling with new problem:
This C++ DLL code (below) I need on Clarion side (also as DLL). I even don’t know where to start. In theory, it should receive one string parameter. Thanks for your ideas.

extern "C" __declspec(dllexport) BSTR __stdcall Test_ProcessData(BSTR input)
{
    // suffix for input string.
    CComBSTR bstrSuffix(L" - Processed by Test.dll");

    int inputLen = SysStringLen(input);
    int suffixLen = SysStringLen(bstrSuffix);
    int totalLen = inputLen + suffixLen;

    BSTR bstrResult = SysAllocStringLen(NULL, totalLen);
    if (bstrResult)
    {
        memcpy(bstrResult, input, inputLen * sizeof(wchar_t));
        memcpy(bstrResult + inputLen, bstrSuffix, suffixLen * sizeof(wchar_t));
    }

    return bstrResult;
}

example of typed functions in clarion 6 to 10. Clarion 11 has special function pointer handling that supports C style function pointers. These proto types can be called as delegates from say DOT NET or in theory CPP. bstring is what your looking for. const *cstring as everyone knows. bstring in clarion is not in the help index but its there and works.

the examples below are derived from early example of Gordon Smith calling a function pointer as a faked thunk by address i think calling CPP.
Clarion supported typed functions since early days of the windows compiler.

Notice this “csEventdelegate ptr,”. and where it is used to thunk the call. “return ptr(”


This is an example of typed functions. 

 module('Clacalcscriptwin32.clw')
 
    TypeCalcscriptcontext(long appidNo, long eventno, bstring membernames, bstring propertyvalue),type,pascal,name('typecalcscriptcontext') ! delegate for event context event id     
    TypeCalcscriptcallback(long appidNo, long eventno, bstring membernames, bstring propertyvalue),TYPE ,pascal! calc script call back


Now in your example all you need is a clarion function typed external as a 
bstring and of course a lib file to go with it.

For more complex examples of function call backs in
 Clarion 6 to 10 where it may be required and mostly 
clarion developers dont do callbacks from say DOT NET to
 Clarion and call backs back to dot net but you can do it and
 this is generic for even VB Dot Net delegates.

Now lets look at function call backs and how they are
 prototyped in releases 6 to 10. 

    Module('callfunctionptr.clw')
           
       csEventdelegate(long appidNo,Long EventId,bstring Boundname,bstring membername,bstring Propertyvalue,long paramscount),byte,PASCAL ,TYPE
    callcsEventptr(csEventdelegate ptr,long appidNo,Long EventId,bstring Boundname,bstring membername,bstring Propertyvalue,long paramscount),pascal,BYTE,Proc,Name('callcsEventptr')  
  
 
 
           END
 
! the thunked call example, remember in clarion 11 
you dont need to do this call by address.
! ptr is the address of the external function to call.

callcsEventptr Function(  ptr,long appidNo,Long EventId,bstring Boundname,bstring membername,bstring Propertyvalue,long paramscount) 
    CODE
! Pre clarion 11 call by address.
    return  ptr(appidNo,  EventId,  Boundname, membername,  Propertyvalue,paramscount)