Joined: 02 Dec 2003
|Posted: Mon Mar 20, 2006 11:38 am Post subject: Programming Tip #18: Reusing Components in DLLs
|Programming Tip #18: Reusing Components in DLLs
Synopsis allows for the access and reuse of existing code through an interface to DLLs. DLL stands for Dynamic Link Library. As its name suggests, it is a library or depository of software components or code that can be accessed dynamically, i.e. at run-time. One of the main purposes of DLLs is to allow for the sharing of code between applications.
Each piece of functionality in a DLL is accessible through a function. The concept is similar to that of a Synopsis component, in fact Synopsis components are modeled after functions. A function allows you to invoke it, passing some number of arguments. Once the function finishes its job, it may return output arguments.
To interface with a DLL function, you need to tell Synopsis what the target DLL function looks like. This means supplying the name of the function and a description of the arguments. Because a DLL function has a universal coding interface, it is the description of the arguments that can get a little complex.
To understand this mechanism, let’s have a look at a sample DLL that is found in the Synopsis installation. If you look at the directory “Sample Programs” you will find a subdirectory called “ComponentModifier”. The content of this subdirectory is an actual Microsoft Visual Studio .NET project. Let’s look at the source code of this project, contained by the file main.cpp:
This code is C/C++ code. From the perspective of the Synopsis user, this fact is actually irrelevant. A DLL can be created using a variety of computer languages but when it is compiled, the resulting DLL format is the same. When you connect to a DLL using Synopsis, you could really care less what the original source language was.
The important part of the source code is the function “setWidgetForeColor”. This function allows you to change the text and foreground color of a UI component in a Windows form. The function has three arguments:
void * intPtr
char * text
char * colorName
The first, called, “intPtr” is an identifier of a UI component. Think of it is a kind of address that tells the function where the UI component is actually stored in computer memory.
The second argument, called “text”, is the text that we want to assign to the UI component.
The third argument, called “colorName”, is the name of the color (e.g. Black, Red. Green, etc.) that we want to assign to the UI component’s foreground color or an RGBA combination, for example “A=256, R=46, G=92, B=102”.
Because the universal description of the DLL function arguments is technical, we need to describe them to Synopsis in the appropriate technical terms. Once Synopsis knows the function you want to access and the associated argument list, it can connect and call the function for you, essentially reusing the DLL code as Synopsis component.
Let’s have a look now at the sample program IntPtr.vpd found in the “Sample Programs” directory of the Synopsis installation. This program allows us to connect to the DLL function setWidgetForeColor and looks like this:
The usage of the Windows Form, Window Event, If Equal components should be pretty straightforward if you have gone through the other programming tips. Basically they just display the form and wait for the user to click on the button (named “Button 1”).
When the button is clicked, the UI Component Interface component is called. The arguments of this component are the name of the Windows form and the name of the Button in the form: “Win Form 1” and “Button 1” in input arguments 0 and 1, respectively. The service on the UI Component Interface is “Get IntPtr”. An IntPtr is a technical term for the address in memory of an object. We need this service to tell the setWidgetForeColor function in our DLL which UI component we want to set.
Now we come to the heart of the discussion, the DLL Func Call component, found in the System tab of the component tray. When we drag and drop the component to the workspace, we see that it is unspecialized as it does not have any information about any function arguments:
Double click on the component to bring up the component property dialog. Note that the interface for this newly dragged component is very different from the corresponding component in our program. This is because we have specified a function declaration whereas the “Function parameter declaration” property of the new component is empty. Let’s have a look at the declaration for the DLL Func Call component in our program:
Double click in the edit area of the “Function parameter declaration” property to bring up a larger editor. We can see the function declaration for our target function:
Note that each line specifies the prototype for an argument of the function. Each argument line gives, in order: 1) a user-friendly name for the function argument, 2) a user-friendly description for the argument and 3) a type declaration for the argument. You can put fields inside quotes (“) to include spaces.
The name and description of an argument is not really important and is more for the convenience of the Synopsis user. If you look at the input arguments of the component (by right clicking it and selecting the “Input Argument(s)” option) you will see that whatever you supply for the names and descriptions appear in the corresponding fields for the input arguments:
The important information in the function declaration is the types of the arguments. Also, the types must appear in the correct order, i.e. in the same order as the actual declaration of the function. So, going back to our source code, we note that the order is:
void * intPtr
char * text
char * colorName
So our Synopsis function declaration has to match this order. Hence, the first line corresponds to the intPtr argument, the second to the text argument and the third to the colorName argument.
To know which type declarations to use, match the actual function type with the list provided in the help module. The intPtr argument has type void *, or void ptr (the * means pointer). So the corresponding Synopsis tag is “void_ptr”. The second and third arguments have a string type, indicated by char *, meaning character pointer. So the corresponding Synopsis tag is char_ptr.
By providing this declaration, we tell Synopsis how to mimic the interface of the DLL function, allowing us to pass any Synopsis data through to the function. Let’s look again at the arguments of the Synopsis DLL Func Call component:
So we have the three arguments of the DLL function appearing in input data ports 2, 3, and 4, respectively. Input data port 0 allows us to specify which DLL to load. In our program this is “Sample Programs\ComponentModifier\Debug\ComponentModifier.dll”, relative to the Synopsis installation. Input data port 1 allows us to specify the name of the DLL function, setWidgetForeColor.
The resulting output arguments look like this:
The three declared arguments also appear in output data ports 2, 3, and 4. These return the values of the arguments passed to the DLL function after the call is made. This is to allow for some functions that may modify the values of the incoming arguments. In our DLL function, no modification to the input arguments is made, so the values in these output data ports will be the same as the corresponding input data ports. The output data port 0 returns the name of the function called as a matter of convenience. The output data port 1 returns an optional value returned by the DLL function.
The code sample above shows that the function setWidgetForeColor returns “void”, meaning it returns no functional value. So we need to tell Synopsis this via the “Function return type” property. Double click the DLL Func Call component to set this property:
It is set to “void” to correspond to the functional return type. If the function returned an integer value, for example, we would have to set this to “int”. If it returned a floating point number, we would have to select the “float” type.
Because our function returns nothing, the value of the output data port 1 of the DLL Func Call component will be empty after invocation of the target DLL function.
In our sample program, we specified some arbitrary values for the text and color arguments. In input data port 3, we entered “Hello there!!!” for the text and “Red” for the color. The idea is that when the user clicks on the button in the form, the text of the button will be changed to whatever we supplied and its foreground color will change to the color given as well. To select a color you can click on the “…” browser button of the input argument editor and select “Color Browser” to visually compose a color name or RGBA color code. This is an example of how Synopsis allows you “translate” between high-level thinking and low-level technology. Synopsis is providing a unified way to reuse existing code.
To specify which UI component is to be affected, we connect the output data port of the UI Component Interface component to input data port 2 of the DLL Func Call component, which corresponds to the intPtr argument of our DLL function. Recall that the “Get IntPtr” service of the UI Component Interface component returns the memory address of the button in the form. We pass this value blindly to the DLL function which knows how to deal with the address value. Again we see how Synopsis allows us to refer to something in layman terms (i.e. the name of the button) and interface with something else (i.e. the DLL code) that requires nasty, nitty-gritty, crawl-on-your-hands-and-knees technical information such as memory locations of code, which we don’t care about.
You can try running this program and experiment by selecting different arguments for the text and color arguments to see how the program behaves with different input.
Using the DLL Func Call component essentially boils down to specifying the name, arguments and return types of the DLL function that you want to call. Once defined, Synopsis then allows for seamless integration of the external code, effectively reusing the DLL function as a Synopsis component, no different than any other available from the component tray.