ZPlugin with C#

Hi there.

Did anyone here ever manage to call a function from a C# .net assembly from ZScript using [FileExecute, “AssemblyName.dll”, “FunctionName”]?

If so, which attributes did you use to make the C# method visible?

Do I have to (or can I) include the name of the class before the function name? Will “ClassName.FunctionName” work?

Does the assembly have to be marked (registered) for COM interop?

Tried most of these options, but I’m probably missing something. So far I´ve been unable to get even a Hello World message to pop up from my assembly.

I’m sorry, I can’t help you on this - I use C++ for all my dll stuff (and it makes porting to Mac OSX simpler).


For all the help I already had from you, you’d have my thanks even if you only used Java or Cobol!

File execute would make things easier, but I believe I can live without it! :wink:

Still, if anyone else would jump in and supply an answer, I’d be far from sad! And if I find anything out, I’ll share here.

Ok… maybe I can go on without C# callbacks, but… I’m a little stuborn, and won’t give up that easy…

Kinda found a way (cludge): Build a C++ wrapper to a C# assembly, exposing the methods the way they should be exposed.

Managed to create the basic project, build it and all, but still the same error: “the specified execute-routine could not be found”

In my DLL, I have the following code:

__declspec( dllexport ) float GetWheelPosition(void)
    return 1.0f;

and on the zscript:

    "Increases the draw size of the current brush",
    [ISet, Draw:Draw Size,  [FileExecute, Wrapper.dll, GetWheelPosition]]

No way to delete the post… but forget about it all the same: You switch out the debug directives and, just like that, it works again…


Now on to find out how to call Static C# methods from C++ functions

Great! :slight_smile: Glad you solved that. I was hunting around and found this, which might be useful:


Huge step forward!

I managed to make the wrapper using the link you gave me, but ZB kept on crashing whenever I called anything from the .net side of the force.

Had to dig a little (assembly bind error logs and the like) to find out what was going on, but as soon as I made it work, I thought I should post it here, in case there are other .net freaks around. :smiley:

The steps are quite easy:

  1. you create your C# dll with whatever logic you need on it (in my case, it would just return a float number)
  2. you create a C++ wrapper dll that references your C# dll and make a function call that will instantiate your c# class and execute your method. (static classes are next up on my experiments)
  3. IMPORTANT: you should prefix your c++ funcion with extern “C” __declspec( dllexport ) for it to be findable by ZB. Maybe this is not so for every compiler out there, but it was the only way for me to get it going from VS2010.
  4. after everything is built and ready comes the tricky part: dll placement! The C++ dll must be on the same folder of the script that calls it, but the .net dll must be on the ZBrush root folder along with ZBrush.exe (yeah, i don’t like it either).

Anyway, it is working now! On to the next stage!

UPDATE: using static methods work just as well, with way less code!

You can specify a full path for the C++ dll in the FileExecute command. The recommended way of doing this is to have a data folder for your plugin and put the dll in there. That way it is easier for the user to uninstall if necessary.

I always have a ‘CheckSystem’ routine that is called at the start of the plugin operation. This checks various things and includes setting a dllPath variable. There’s a special form you can use for ZBrush paths. Here’s the code for Transpose Master:


Then when using [FileExceute] I just use tha variable:

[VarSet, dllVersion, [FileExecute, [Var,dllPath], Version]]

I actually get to run the plugin itself from wherever I wish… but it looks for the .net DLL’s that are referenced inside it on the root folder of ZB.

And, since I’m here, let me ask you something:

I’m trying to get a number value through, passing it as a parameter, but it just does not get there. It either gets zeroed or changed into a huge float number, that is not even close to what I want… Below are the code snippets of my ZScript, C++ function and C# function

[MessageOk, [FileExecute, ZAirbrushWrapper.dll, GetBrushID, 25], " "]
 extern "C" __declspec( dllexport ) float GetBrushID(double brushId) {
     float result = ZAirbrushCallback:small_orange_diamond:StaticCaller:small_orange_diamond:GetBrushID(brushId);
     return result;
    public static int GetBrushID(double brushId)    {
      return 47;

And the value I get from this is something like 6,2568…E-316 … any thoughts on what may be happening?
BTW, I can get the 47 out… is the 25 that’s bugging me!

You’re not following the correct form for the [FileExecute] command. You can pass in a float but you need another comma between it and the function name (to allow for optional text input):

[FileExecute,File name including the extension (such as plugin.dll )
,Routine to call,Optional text input,Optional number input
,Optional memory block input,Optional memory block output]

Your C++ dll function parameters need to reflect this order. In fact, to simplify things I just use the same template for all my C++ functions although I only ever use the first three parameters:

#define DLL_EXPORT __declspec(dllexport)

extern "C"

   float DLL_EXPORT Version(char* pDontCare, double optValue, char* pOptBuffer1, int optBuffer1Size,
                            char* pOptBuffer2, int optBuffer2Size, char** zData);

   float DLL_EXPORT  MyFunction(char* pDontCare, double optValue, char* pOptBuffer1, int optBuffer1Size,
                            char* pOptBuffer2, int optBuffer2Size, char** zData);




I probably already told you this before, but you are my personal hero!

Will give this a try later on, when I get back home, but it makes a whole lot of sense!

When I read Optional text input,Optional number input, I assumed I could opt between them… working late (when you should be sleeping) makes you assume stupid things…:stuck_out_tongue:

Thanks once again.

Oh, and on a personal note: do you use an Intuos 4 (or 5) tablet? If so, would you be willing to give this little Frankenstein a try?

I use an Intuos 3 and would be willing to test out anything you need.

Hi, Nyx.

I hope I’ll have the first version up and runing by the end of the week.

The first feature I’m working on is stylus recognition: the ability to remember the last brush you used with each stylus tip and change back to that brush once you pick up another pen (or use the eraser, for that matter).

Once this first feature is ok, I intend to start working on Airbrush Wheel controls for a number of items on ZB, like DrawSize, Z & RGB intensity, FocusShift, etc.

I’m working with the Intuos4 driver, so I’m not sure how compatible it is going to be (and it’s going to be great to find out), but if you’re interested, send me a pm and I’ll send you a copy as soon as it is off the ground.

Sygnus, many thanks for the kind words - I’m glad to help when I can. And sadly I only have an old Intuos that you have to start with a handle (an Intuos 2), otherwise I would have liked to test your plugin.

It worked!!

Still got a small bug on the method that switches brushes as you switch your pens, but it is certainly due to working way past my sleeping hour (again).

Anyway, the wheel remote control is working perfectly. And as a bonus, you can use the button on the airbrush to switch between Draw Size, ZIntensity and RGB Intensity (tried to include Focal Shift, but it was not very useful).

But there’s always another question, yes? I was creating the buttons and assigning hotkeys directly from the script, as a parameter… and I found that sometimes the hotkey stops working after the first use. Also tried assigning the hotkey with ISetHotkey, but with the same result. The only way I found to bind the hotkeys for good was to alter the startup hotkeys txt. Am I doing something wrong?

I’ve not tested [ISetHotkey] all that much but I have noticed that in more recent versions of ZBrush the hotkey part of the zscript button code does not always work. It seems quite likely that this also affects [ISetHotkey].

If you’re wanting to set hotkeys on the fly then this could be a problem but it wouldn’t be too difficult to set up your plugin so that it modifies the StartupHotkeys on first run (so long as you notify the user!).

See? I like the way you think!

It really wouldn’t be hard… and it would be easier still to have it done from the C# server app, as it would also solve the problem of placing the dlls and scripts on the right places. A little ‘install’ button would do the trick.

By the way, what is the proper way to have a script loaded at the startup? Create a sub-folder on the zstartup/zplugs did not do the trick. Do I have to create a launcher script and put it on the ZPlugs root folder?

This may seem like a dumb question after all I went through but… if this is the case, how do I call a script from another?:o

If you create your zscript as a plugin then you put the compiled zscript (the ZSC file) in the ZStartup/ZPlugs folder and your plugin buttons will be available each ZBrush session. All you need to do to create a plugin is to make sure the button paths are the correct form, and that you create the necessary sub-palette (if you need one). So:

[ISubPalette,"ZPlugin:My Plugin Menu"] //creates a new menu in the ZPlugin palette

//a plugin button:

[IButton,"ZPlugin:My Plugin Menu:My Button","What this button does",    
   //do stuff
,,.5]// the '.5' is the button width - half a palette-width

[ISwitch,"ZPlugin:My Plugin Menu:My Switch",0,"What this switch does",
  // on code
  // off code

[ISlider,"ZPlugin:My Plugin Menu:My Slider",50,1,0,100,"What this slider does",
  //do stuff when value is changed

Do I have to do something special to create it as a plugin? What I did was what you described above: created a sub-palete on the ZPlugin menu and created my buttons inside it.

I actually managed to get this far… but since I’m using a few dlls, I thought it would be better if the plugin had its own subfolder… and when I moved it there, it did not load at startup anymore.

So my idea was to create a simple launcher scritp and have it start the one on the subfolder, but I don’t know how to call one script from anoter.

You can do this if you want - you just use this code:


But then the user will only see the button of the launch zscript until they press the button, which is OK but a bit messy. It’s surely no more difficult to have your main plugin ZSC in the ZPlugs and just point it to your plugin’s data folder when it needs it? That’s how all the Pixologic plugins work.

And no, there’s nothing more to do to create a plugin, provided you follow my code example.